MINI2, an extremely simple barebone PHP application on top of Slim
For my daily work I often needed to setup super-simple PHP applications, just some more or less static pages plus some dynamic pages with simple database calls, one or two simple forms and maybe a little bit of AJAX. You know, the typical agency stuff. This usually led to the question: Use a real framework or just mash some .php files together ? The result was MINI, an extremely simple naked PHP application (or maybe even a framework), more on that in another article or directly on GitHub.
As MINI is really just very very basic I tried to upgrade this project a little bit by building the same naked application from scratch, this time on top of Slim, the popular micro framework. The result is MINI2, a super-simple script that might be a very simple, but very powerful base for smaller PHP applications. As the application’s core logic is now delivered by Slim, the script itself is extremely compact and comes with just 2 (!) php files plus some view templates and the casual .js and .css stuff.
The features of MINI2
- built on top of Slim (you can easily update Slim without harming MINI2)
- RESTful routes
- extremely simple: the entire application is just 2 .php files (plus external dependencies plus view templates)
- makes “beautiful” clean URLs (like example.com/car/show/17, not example.com/index.php?type=car&action=show&id=17)
- uses Twig as template engine, others are possible (via Slim packages)
- uses pure PDO instead of ORM (it’s easier to handle)
- basic CRUD functions: create, read, update/edit and delete content
- basic search
- basic AJAX demo
- (optional) shows emulated PDO SQL statement for easy debugging
- (optional) compiles SCSS to CSS on the fly
- (optional) minifies CSS on the fly
- (optional) minifies JS on the fly
- (optional) dev/test/production switch
The requirements of MINI2
- PHP 5.3+
- MySQL
- mod_rewrite activated (when using Apache), document root routed to /public (tutorial below)
A simple screenshot
Installation
There’s a quick installation guideline on GitHub. If you are familiar with Vagrant then there’s also a one-click installation (yo, just a simple vagrant up command) that will create a fully configured Ubuntu 14.04 LTS with a completely setup MINI2 inside. Check the GitHub readme for more information.
The structure of MINI2
Two folders, Mini and public. public holds the index.php that contains your application’s logic plus your JavaScript files, the scss (SASS) / CSS files and for sure the .htaccess that makes beautiful URLs. The SASS-to-CSS compiling is totally optional, you can turn it off by simple commenting out one line of code later.
The folder Mini contains – beside installation stuff – just the model/model.php that holds a set of data-manipulating methods (the MVC-model) and the folder view that holds a set of Twig files (which is just HTML with shortcodes for easily echoing out PHP variables), so it’s the MVC-view. Before you ask: There’s no folder controller as index.php works as the controller.
In the root folder there’s the composer.json that contains the to-be-loaded dependencies and a rule for autoloading our files. When fetching the dependencies while installing MINI2, Composer will load Slim (the router, more on that later), Twig (the template engine that renders the HTML and the variables from .twig files) and some other small tools and put them into the automatically created vendor folder, you know the process. If you have no idea what this means, then MINI2 is not for you, sorry.
The subfolder modules inside public/scss just contains a .scss with variables (like $font-color: #222222;) for easier SASS rendering, a common way to modularize your scss.
The folder _install in Mini holds 3 .sql statements with demo data for a working demo setup, the folder _vagrant contains a Vagrantfile and a bootstrap.sh to create a 100% automatically installing Ubuntu 14.04 LTS box with MINI2 running inside, more on that in the install tutorial on GitHub.
How MINI2 works
MINI2 works like most other frameworks and uses URLs like this: example.com/songs, example.com/songs/editsong/17, example.com/subpage etc. instead of ugly messy index.php?xxx=111&yyy=222 URIs. By default an installation of MINI2 needs a simple configuration of your Apache (or nginx or IIS) that routes the user directly to /public/index.php. This is easy to set up and clearly described in the install tutorial. Beside the aesthetical point this also prevents access to anything else then the /public folder, which makes your application much much more secure and also protects any potential .git files/folder, swap/noise files etc. from being curl’ed etc.
Let’s look into public/index.php, step by step:
<?php /******************************* LOADING & INITIALIZING BASE APPLICATION ****************************************/ // Configuration for error reporting, useful to show every little problem during development error_reporting(E_ALL); ini_set("display_errors", 1); // Load Composer's PSR-4 autoloader (necessary to load Slim, Mini etc.) require '../vendor/autoload.php'; // Initialize Slim (the router/micro framework used) $app = new \Slim\Slim(); // and define the engine used for the view @see http://twig.sensiolabs.org $app->view = new \Slim\Views\Twig(); $app->view->setTemplatesDirectory("../Mini/view");
For development, the error reporting is set to maximum, then the Composer-autoloader is loaded, and Slim is initialized. As we use Twig as the template engine Twig is also initialized and the path to the view files is defined. By the way, I really like the way Slim handles this: $app->view. Beautiful!
/******************************************* THE CONFIGS *******************************************************/ // Configs for mode "development" (Slim's default), see the GitHub readme for details on setting the environment $app->configureMode('development', function () use ($app) { // pre-application hook, performs stuff before real action happens @see http://docs.slimframework.com/#Hooks $app->hook('slim.before', function () use ($app) { // SASS-to-CSS compiler @see https://github.com/panique/php-sass SassCompiler::run("scss/", "css/"); // CSS minifier @see https://github.com/matthiasmullie/minify $minifier = new MatthiasMullie\Minify\CSS('css/style.css'); $minifier->minify('css/style.css'); // JS minifier @see https://github.com/matthiasmullie/minify // DON'T overwrite your real .js files, always save into a different file //$minifier = new MatthiasMullie\Minify\JS('js/application.js'); //$minifier->minify('js/application.minified.js'); }); // Set the configs for development environment $app->config(array( 'debug' => true, 'database' => array( 'db_host' => 'localhost', 'db_port' => '', 'db_name' => 'mini', 'db_user' => 'root', 'db_pass' => 'your_password' ) )); });
Here we defined the configs for the development environment. To define for production you’d simply copy this block and fill the according credentials. More on that in the GitHub readme. Skip the $app->hook block for now and look at $app->config: This is self-explaining, right ? Okay, back to $app->hook: These are Slim’s hooks, blocks of code than are called at specific points in the application’s lifetime, in this case before the application is run. Here, in the default setup, these lines are just compiling our .scss files to .css and the style.css is minified, both via Composer-loaded libraries.
/******************************************** THE MODEL ********************************************************/ // Initialize the model, pass the database configs. $model can now perform all methods from Mini\model\model.php $model = new \Mini\model\model($app->config('database'));
Just one line. To better understand this, have a look into Mini/model/model.php (shortened for better explanation):
<?php namespace Mini\Model; use PDO; class Model { /** * The database connection * @var PDO */ private $db; /** * When creating the model, the configs for database connection creation are needed * @param $config */ function __construct($config) { // PDO db connection statement preparation $dsn = 'mysql:host=' . $config['db_host'] . ';dbname=' . $config['db_name'] . ';port=' . $config['db_port']; // note the PDO::FETCH_OBJ, returning object ($result->id) instead of array ($result["id"]) // @see http://php.net/manual/de/pdo.construct.php $options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING); // create new PDO db connection $this->db = new PDO($dsn, $config['db_user'], $config['db_pass'], $options); } /** * Get all songs from database */ public function getAllSongs() { $sql = "SELECT id, artist, track, link, year, country, genre FROM song"; $query = $this->db->prepare($sql); $query->execute(); return $query->fetchAll(); } }
This is just a simple class that expects an array with database configs when being constructed. When constructed, a database connection will be created and all methods – which are usually database calls and data-manipulating blocks – can be used. So, back to our index.php, let’s look again at that line:
/******************************************** THE MODEL ********************************************************/ // Initialize the model, pass the database configs. $model can now perform all methods from Mini\model\model.php $model = new \Mini\model\model($app->config('database'));
So we create the Model object above and pass the configs to it. We can now perform actions on the $model, like $model->getAllSongs(); ! Totally simple.
/************************************ THE ROUTES / CONTROLLERS *************************************************/ // GET request on homepage, simply show the view template index.twig $app->get('/', function () use ($app) { $app->render('index.twig'); }); // GET request on /subpage, simply show the view template subpage.twig $app->get('/subpage', function () use ($app) { $app->render('subpage.twig'); });
This is where the real action starts: Here the routes are defined, some might also call them controllers. The logic behind is extremely simple, everything inside $app->get(‘/’ … will be called when a GET request is made on the project’s homepage, like example.com, everything inside $app->get(‘/’subpage … will be called when a GET request is made on example.com/subpage. Super-simple. Slim’s router can handle GET (browser’s default when simply “surfing” websites), POST (usually when submitting a form) and most other REST verbs.
The line $app->render(‘index.twig’); says what you expect: The file Mini/view/index.twig (which is just pure HTML in this case) is loaded, so in this case a simple static page is shown. See the Twig documentation for more information. You’ll love Twig, as it’s indeed easier than pure PHP (!).
Now to some more complex routes:
// All requests on /songs and behind (/songs/search etc) are grouped here. Note that $model is passed (as some routes // in /songs... use the model) $app->group('/songs', function () use ($app, $model) { // GET request on /songs. Perform actions getAmountOfSongs() and getAllSongs() and pass the result to the view. // Note that $model is passed to the route via "use ($app, $model)". I've written it like that to prevent creating // the model / database connection in routes that does not need the model / db connection. $app->get('/', function () use ($app, $model) { $amount_of_songs = $model->getAmountOfSongs(); $songs = $model->getAllSongs(); $app->render('songs.twig', array( 'amount_of_songs' => $amount_of_songs, 'songs' => $songs )); }); // POST request on /songs/addsong (after a form submission from /songs). Asks for POST data, performs // model-action and passes POST data to it. Redirects the user afterwards to /songs. $app->post('/addsong', function () use ($app, $model) { // in a real-world app it would be useful to validate the values (inside the model) $model->addSong( $_POST["artist"], $_POST["track"], $_POST["link"], $_POST["year"], $_POST["country"], $_POST["genre"]); $app->redirect('/songs'); }); // GET request on /songs/deletesong/:song_id, where :song_id is a mandatory song id. // Performs an action on the model and redirects the user to /songs. $app->get('/deletesong/:song_id', function ($song_id) use ($app, $model) { $model->deleteSong($song_id); $app->redirect('/songs'); });
$app->group(‘/songs’ … “groups” everything that happens under example.com/songs, like example.com/songs itself, example.com/songs/editsong etc. Let’s look at $app->get(‘/’, … which defines what happens under example.com/songs(/). Here we show a twig file like before, but also pass some variables to the view: Mini/view/songs.twig will be shown, and the content of $amount_of_songs will be available inside that file via {{ amount_of_songs }} [that’s the syntax of Twig], like this (songs.twig):
<h2>We have {{ amount_of_songs }} songs</h2>
Okay, back to the index.php: The above code clearly shows where $amount_of_songs comes from: $amount_of_songs = $model->getAmountOfSongs(); ! As we have defined $model earlier we can use any method from that class and so get or manipulate data, like in this case simply fetching a number. Important: Note that as we use $model inside the $app->get block, the $model variable has to be passed inside, like shown in exactly that line and the $app->group line before: use ($app, $model .. defines which variable are known inside these methods. Without passing the $model we could not use $model inside.
Next block: $app->post( handles a POST request on example.com/songs/addsong. The method’s code is nearly self-explaining: We perform a method of $model and pass some variable from $_POST there. After that we route the user back to example.com/songs via $app->redirect(‘/songs’).
And basically: That’s it! Finally the index.php closes with
/******************************************* RUN THE APP *******************************************************/ $app->run();
to run the entire application for sure. Have a deeper look into all these files for a better understanding and some more examples, like the AJAX call or the search feature. Also, make sure you read into Slim’s excellent documention and the Twig docs.
Where to find MINI2 ?
On GitHub for sure, you might also bookmark the portal page php-mini.com and the Facebook page. I’m also trying to build a documentation on php-mini.com/documentation. There’s also the first version of MINI (1) on GitHub.