I am glad to meet you here again. In the third part I told you I will be talking of Slim's Dependency Injection Container(DIC or simply DI) and to illustrate/apply that I will be using slim-views
package which could allow us to have nice HTML documents instead of simple HTTP Response
;
If you didn't start this series with us, here is the break down of previous parts:
- Workouts With Slim 3
- Workouts With Slim 3 - Part 2: Your First Application
- Workouts With Slim 3 - Part 3: Routing
What's Dependency Injection Container?
Before I dive into that let me first show you with a simple example what Dependency Injection itself is. Say you have a class users
as following:
<?php
class Users{
//...
private $users;
public function fetchUsers( )
{
$this->users = $databaseHandler->prepare(' SELECT * FROM users ');
//...
}
//...
}
Image 1- Sample Users class
There is nothing really special with this sample class. It's supposed, with the help of the method fetchUsers()
get the list of users from the table users
. Right here the class will be in need of the database connection for it to be able to access the database records, which is the variable $databaseHandler
used to prepare the SQL
request.
You may tell me all we have to do is to set up the database connection inside the same class, maybe in the constructor
like this:
<?php
class Users{
//...
private $users;
protected $databaseHandler = null;
// DB settings
$private $host = 'host';
$private $dbname = 'dbase';
$private $dbpwd = 'pwd';
//....
public function __construct()
{
try{
//We set the $databaseHandler variable
$this->databaseHandler = new PDO(....);
}catch($e){
$e->getMessage();
}
}
public function fetchUsers( )
{
$this->users = $databaseHandler->prepare(' SELECT * FROM users ');
//...
}
//...
}
Image 2- illustration of tied classes. Image by pixabay
Yes. This one too works. But that will definitely bind our database connection to the class. Whenever you would like to share this code with another developer
- He would have to use PDO
- He has to alter this class before using it
- If the database connection hangs, the whole class would do same
This solution causes some few problems and doesn't really look clean. The class depends a lot of the database: which is called a dependency
.
With that you may also be thinking of having a different class database
which the users
class can extend like this:
<?php
class Database{
protected $databaseHandler = null;
// DB settings
$private $host = 'host';
$private $dbname = 'dbase';
$private $dbpwd = 'pwd';
//....
public function __construct()
{
try{
//We set the $databaseHandler variable
$this->databaseHandler = new PDO(....);
}catch($e){
$e->getMessage();
}
}
}
Image 3- Sample database class
Then we create the users
class which extends the database class in order to have access to the database instance:
<?php
class Users extends Database{
//...
private $users;
public function fetchUsers( )
{
// can use $databaseHandler which comes from the parent class
$this->users = $databaseHandler->prepare(' SELECT * FROM users ');
//...
}
//...
}
Image 4- Users class extending a database class
Image 5- Illustration of inheritance - Image by whyu.org
Here too, even though the two classes are well separated, they're still bound. Users
class can't go somewhere without the Database
class. And the biggest problem is that:
- You can't extend more than one class, so if our class needed another option(dependency) like a logging system it will be hard for us to tie these three classes.
- The two classes are tightly coupled
- If a method is deleted in the
super class
, then we will have to re-factor.
Instead of that, all we're going to do is to create the users
class independently of any dependency it may have. Then we can prepare it to receive its dependencies as parameters of the constructor
:
<?php
class Users{
//...
private $users;
$private $db;
public function __construct($databaseHandler, $anotherDependency = '', ...)
{
$this->db = $databaseHandler;
}
public function fetchUsers( )
{
// can use $databaseHandler which comes from the parent class
$this->users = $this->db->prepare(' SELECT * FROM users ');
//...
}
//...
}
Image 6- Users class without Inheritance
Image 7- Illustration of Slim Container with many entries - Image by nimahia
This can finally allow us to inject
or throw into the users'
any dependency class and it can make use of it without even knowing about it. *This principle is called Dependency Injection.
What does that have to do with Slim 3 framework. Well, Slim framework does only request
and response
stuff. If you want to extend slim's power you need to bring third-party modules/packages. To make that powerful and easy for us, Slim has put in place a dependency injection container
which will be in charge of feeding the main Slim app with any dependence we would've added to the container of our dependencies(Dependency Injection Container).
This is a very basic way of explaining what a Dependency injection is. Slim's DIC is quite great because it's built on the famous DI pimple. With that we can take Slim 3 from micro-framework to a powerpuf one just by bringing in anything we need. You can so far perceive one of the greatest advantages of Slim 3 which is that you use only what is needed, no garbage to keep.
How is the DIC is used in Slim 3?
When you want to inject any dependency in Slim 3 one way to do that is to get the container class object as following:
<?php
//...
$SomeSettings = require 'settings.php';
$container = new SlimContainer($SomeSettings);
// Then you can use the container variable to inject any needed service
// you can create as many as possible services
$container['service'] = function ($container) {
// get the setting in here
$config = $container->get('settings');
// return the service class instance
return new MyServiceClass();
};
// ...
// Now while creating our Slim App,we can give it this container
$app = new SlimApp($container);
Image 8- Slim app with container entry
From here the container entry service
can be accessed from anywhere in our Slim $app
and the entry holds an instance of MyServiceClass
like this:
$app->service->aServiceMethod();
Templating
By default Slim doesn't fully support the MVC model. There is no Model
, no View
, only the Controller
which handles HTPP requests
and returns just another HTTP response
. So in order to benefit from full view system(templating) which will allow us to use HTML document as our view instead of raw HTTP responses, Slim has some services we can call(inject ) in it and get that power: Slim-Twig and PHP-View.
If you want to understand the real use of this templating here, run the application we did in the previous part, then on your browser choose to show the source code. You should see there is no HTML code. And you can't design a website without HTML. If you wanted to see some HTML code you would have added it just in your routes and you would have ended up mixing your HTML and your PHP code: It's not professional, not maintainable, not clean and a very bad practice.
Template come in to solve this problem. We'll just have to tell Slim, "hey, just go there and look for the needed HTML and display it along with the HTTP response".
How does that work in real Slim Application?
To illustrate how exactly all this work together and how useful it's, let me use the sample application I create in [beginning]() of this tutorial.
So let use Slim's DIC to inject the Slim-Twig
module/service so that we can add our templates easily. Since I have chosen to use Slim-Twig
note that we will be using Twig templating engine actually in the template.
Let start by adding two new folders at the root of our application templates
and cache
. The first one will contain our templates and the second one will store Twig's cache.
Then let's require the Twig-View
package from composer to our project:
composer require slim/twig-view
Image 9- Require Twig-View package with composer
This will take some few seconds and you should have it appearing in your vendor folder. At the end my composer.json
file looks like:
{
"require": {
"slim/slim": "^3.0",
"slim/twig-view": "^2.1"
}
}
Image 9- composer.json file
and the the folder structure is like:
-- myapp
- public
- index.php
- cache
- templates
- home.twig
- vendor
- ...
Image 9- Application folder structure
Now here is the full code in our index.php
file:
<?php
// Let's ask PHP to display all errors whenever they
// occur in our slim code,
// otherwise Slim will kind of swalow them, it will
// only show in the command like.
// Make sure you set this before other codes
// The value mast become `false` before deployment
ini_set('display_errors', true);
// Call composer to autoload(make them available)
// all classes from the the `vendor` folder
// This file is in charge of doing that, and it's
// located at vendor/autoload.php
// Your folders structure might be different from mine,
// make sure your adjust this relatively
require __DIR__ . '/../vendor/autoload.php';
// Let's announce to our application that we will be using
// the Slim application class(`vendor/slim/slim/slim/App.php`) by calling its namespace. We don't need to require it with its
// full path `vendor/slim/slim/slim/App.php`. Composer autoload
// has already done that for us up there.
use SlimApp;
// Use the PSR-7 HTTP Messages interfaces
use PsrHttpMessageServerRequestInterface as Request;
use PsrHttpMessageResponseInterface as Response;
// Some settings for twig
$settings = [
'view' => [
'template_path' => '../templates',
'twig' => [
'cache' => '../cache',
'debug' => true,
'auto_reload' => true,
],
],
];
// Create an instance of the container
$container = new SlimContainer($settings);
// Create new container service for Twig-Views
$container['views'] = function($container){
// Get the settings
$path = $container->get('view')['template_path'];
$opt = $container->get('view')['twig'];
// Return an instance of twig-view
return new SlimViewsTwig($path, $opt);
};
// We now get a new instance/Object of slim app itself
// and we save it in a variable we can name `$app`
// You can name this variable anything
// We now inject the container inside our app
$app = new App($container);
// We add our first route which will respond to the home page
// request, usually located at `/` or root.
$app->get('/', function(Request $request, Response $response, $args){
// Do anything here, like:
// echo "Welcome to Slim Town!";
// Now we can render an HTML document(template)
// Notice the container service key name 'views' set previously
// It's now accessible here as an object
return $this->views->render($response, 'home.twig');
});
// We add a route for the about page
$app->get('/about', function(Request $request, Response $response, $args){
// Do anything here, like:
echo "About Us <br>";
echo" This is our about page ";
// Then return an HTTP response
return $response;
});
// Once we have the instance of SlimApp we can ask it to start running
// the application by calling Slim's run() function
$app->run();
Image 10- Source code in public/index.php
And the source code inside our twig template:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>slim 3</title>
</head>
<body>
<h1>Welcome to slim town!</h1>
</body>
</html>
Image 10- Source code in templates/home.twig
Run your application now and you get this:
Image 11- Application result
Conclusion
Even though I made the talk much, this is actually a simple thing. I needed to let you know what exactly is going on. We will have the chance in this series to explore the Slim's DIC and Views. In fact the next part in this series is going to be on Views
. We will be crafting a very simple static website with Slim 3. Stay tuned we're getting there.
Last updated 2024-01-11 UTC