PHP MVC
PHP MVC
Introduction
● Model View Controller (MVC) is a
design pattern widely used in
different programming
languages.
● Most PHP frameworks use this
design pattern.
● This pattern divides the
application into three layers:
Model, View, and Controller.
● Each one of these has separate
tasks, and they are all
interconnected.
● There are different visual
representations for MVC, but an
overall and simple
representation can be seen in
the diagram
Introduction
The MVC architectural pattern makes complex application development much more
manageable. It enables multiple developers to work on an application at the same time.
Model
● The model layer is the backbone of the application and handles the data logic.
● Mostly, it is considered that model is responsible for CRUD operations on a
database, which may or may not be true.
● Model is responsible for the data logic, which means that data validation
operations can also be performed here.
● In simple words, models provide an abstraction for the data.
● The remaining application layers don't know or care how and from where the data
comes or how an operation is performed on data. It is the model's responsibility to
take care of all data logic.
● In today's complex framework structures, the overall MVC structure is changed, and
not only do models handle data operations, but also, every other application logic
is handled by models.
● The method followed is fat models and slim controllers, which means keep all the
application logic in models and the controllers as clean as possible.
Model
● It is the backend containing all of the data logic.
● It represents the business layer of the application.
● The model is the core section of this architectural pattern
and only contains application information.
● It has no instructions for displaying the data to the user.
● It is unaffected by the user interface.
● It is in charge of the logic and application rules.
View
● Views are what is visible to end users.
● All data related to this user and public is displayed in the views, so views can
be called the visual representation of the models.
● Views need data to display.
● It asks for some specific data or action from the controller.
● Views do not know or want to know from where the controller gets this data;
it just asks the controller to get it.
● Controller knows who to ask for this specific data and communicates with
the specific model. It means that views do not have any direct link to models.
● However, in the earlier diagram, we linked model to view directly.
● This is because in the advanced systems nowadays, views can directly take
data from models.
● For example, Magento controllers can't send data back to views. For the data
(that is, to get data directly from the database) and/or to communicate with
models, views communicate with blocks and helper classes.
● In modern practices, views can be connected to models directly.
View
● It forms the graphical user interface (GUI) and represents
the presentation layer of the application.
● It is used to visualize the data that the model contains.
Controllers
● Controllers respond to actions performed by a user in the views and
respond to the view.
● For example, a user fills a form and submits it. Here, the controller
comes in the middle and starts taking action on the submission of the
form.
● Now, the controller will first check whether the user is allowed to
make this request or not. Then, the controller will take the
appropriate action, such as communicating with the model or any
other operation.
● In a simple analogy, the controller is the middle man between views
and models. As we mentioned before in the models section,
controllers should be slim.
● So, mostly, controllers are only used to handle the requests and
communicate with models and views.
● All kinds of data operations are performed in models.
Controllers
● It is the application's brain that controls how data flows.
● It works on both the model and the view.
● It is used to manage the application flow, i.e., data flow in
the model object, as well as to update the view whenever
data changes.
MVC
● The MVC design pattern's sole job is to separate the
responsibilities of different parts in an application.
○ So, models are used to manage the application data.
○ Controllers are used to take actions on user inputs, and
views are responsible for the visual representation of
data.
● As we mentioned before, MVC separates the responsibilities
of each part, so it does not matter whether it accesses the
model from controllers or views; the only thing that matters
is that views and controllers should not be used to perform
operations on data, as it is the model's responsibility, and
controllers should not be used to view any kind of data by
the end user as this is the view's responsibility.
Why MVC?
● The MVC software framework assists us in separating the
various portions of the program (input logic, business logic,
and GUI) while maintaining a loose coupling between these
elements.
● Thus, the model contains the information (most reusable)
logic, whereas the view contains the GUI.
● The controller is responsible for input logic.
● This split aids in the management of complexity when
developing an application by allowing you to concentrate
on one area of the implementation at a time.
● The MVC Framework is a fantastic idea for several reasons
Why MVC?
● Simultaneous development
Because MVC decouples the several components of an
application, developers can work on separate components
in tandem without influencing or obstructing one another.
● Reusability
Because the view merely controls how the data is displayed
to the user, the same (or similar) view for one application
can be refactored for another with different data.
Why MVC?
● Enhanced scalability
if your application begins to experience performance issues
due to slow database access, you can update the database's
hardware without affecting other components.
● Low coupling
Because of the nature of the MVC framework, there is little
connectivity between models, views, and controllers.
● Improved extendibility
because the components are not interdependent, changing
one (to address issues or alter functionality) does not affect
the others.
Advantages of MVC
● Multiple developers can work on the model, controller, and
views at the same time.
● MVC allows for the logical grouping of related actions on a
controller. In addition, views for a particular model are also
grouped together.
● A model can have many views.
Disadvantages of MVC
● Because it offers new layers of abstraction and expects users
to conform to MVC decomposition requirements, framework
navigation might be challenging.
● Understanding various technologies becomes the norm.
MVC developers must be proficient in a variety of
technologies.
Composer
Introduction
● A dependency manager can be defined as a software tool to manage (install,
upgrade, configure, and remove) the various types of libraries required by a project
in a logical and meaningful way.
● The PHP Composer can be defined as a dependency manager or dependency
management tool specifically built for PHP.
● Using PHP Composer, you can easily download and incorporate useful and
essential packages (stable libraries created by other developers) into your project
via the terminal.
● PHP Composer is considered one of the most suggested tools inspired by the
node's npm and ruby's bundler. It can solve fundamental issues in the mainstream
of web development projects.
● Composer lets you declare the libraries on which your PHP project depends and
manage it for you. It installs and updates the libraries that you need for your
project.
Introduction
● If you are a Windows user, you can download the setup file from getcomposer.org
and install the PHP Composer.
● This process is quite self-explanatory. Just remember to set your PATH
environment variable so that you can call Composer from any directory.
● Packagist is the primary package repository for PHP Composer, and by default, the
composer uses packagist.org to search for packages.
● This is where you can publish your packages, and you can also look to use other
developer's packages.
Introduction
● Let's consider the scenario where you are working with images in your PHP
application, and you need to resize and compress these images.
● Here you can use the image processing functions that come with PHP by default.
● Then you start coding, and you realize that you are going to spend much time
developing this functionality as per your project needs.
● This is good, but it takes time to do that, which will increase the cost of the project.
● Here in this situation, you can use PHP Composer, and you can tell the Composer
that you want a specific version of the image processing library.
● you can easily find the required package on Packagist - Search for intervention
● The composer downloads the specific version of the library into the vendor
directory under your project and provides you with an autoloader.
<?php
$Image = Image::make("upload/image.jpg")->resize(200,
200)->save("img/thumbnail.jpg",100);
?>
● You can see that it will create a composer.json file inside the project folder.
● Here you can see a list of dependencies that have been used in this project and are
required to run this project.
● During a new installation, if the project contains a composer.json file, it can directly
establish dependencies using the Composer installation command at the terminal
or command prompt.
composer install
<?php
require_once __DIR__ . '/vendor/autoload.php';
// use the factory to create a Faker\Generator instance
$faker = Faker\Factory::create();
// generate data by accessing properties
echo $faker->name();
// generate fake email
echo $faker->email();
// generate fake text
echo $faker->text();
Need of Autoloading
● When you build PHP applications, you may need to use third-party libraries.
● if you want to use these libraries in your application, you need to include them in
your source files by using require or include statements.
● These require or include statements are fine as long as youʼre developing small
applications.
● But as your application grows, the list of require or include statements gets longer
and longer, which is a bit annoying and difficult to maintain.
● The other problem with this approach is that you're loading the entire libraries in
your application, including the parts you're not even using.
● This leads to a heavier memory footprint for your application.
Need of Autoloading
● To overcome this problem, it would be ideal to load classes only when they are
actually needed.
● That's where autoloading comes in.
● Basically, when you use a class in your application, the autoloader checks if itʼs
already loaded, and if not, the autoloader loads the necessary class into memory.
● So the class is loaded on the fly where itʼs needed—this is called autoloading.
● When youʼre using autoloading, you donʼt need to include all the library files
manually; you just need to include the autoloader file which contains the logic of
autoloading, and the necessary classes will be included dynamically.
● Specifically, Composer provides four different methods for autoloading files:
○ file autoloading
○ classmap autoloading
○ PSR-0 autoloading
○ PSR-4 autoloading
● As per the official Composer documentation, PSR-4 is the recommended way of
autoloading.
● Steps that you need to perform when you want to use Composer
autoloading.
○ Define the composer.json file in the root of your project or library. It
should contain directives based on the type of autoloading.
○ Run the composer dump-autoload command to generate the necessary
files that Composer will use for autoloading.
○ Include the require 'vendor/autoload.php' statement at the top of the file
where you want to use autoloading.
Autoloading: The files Directive
● File autoloading works similarly to include or require statements that allow you to
load entire source files.
● All the source files that are referenced with the files directive will be loaded every
time your application runs.
● This is useful for loading source files that do not use classes.
● To use file autoloading, provide a list of files in the files directive of the
composer.json file
{
"autoload": {
"files": ["lib/Foo.php", "lib/Bar.php"]
}
}
Autoloading: The files Directive
● we can provide a list of files in the files directive that we want to autoload with
Composer.
● After you create the composer.json file in your project root with the above contents,
you just need to run the composer dump-autoload command to create the necessary
autoloader files.
● These will be created under the vendor directory.
● Finally, you need to include the require 'vendor/autoload.php' statement at the top
of the file where you want to autoload files with Composer
● The require 'vendor/autoload.php' statement makes sure that the necessary files are
loaded dynamically.
<?php
require 'vendor/autoload.php';
// code which uses things declared in the "lib/Foo.php" or
"lib/Bar.php" file
?>
Autoloading: The classmap Directive
● Classmap autoloading is an improved version of file autoloading.
● You just need to provide a list of directories, and Composer will scan all the files in
those directories.
● For each file, Composer will make a list of classes that are contained in that file,
and whenever one of those classes is needed, Composer will autoload the
corresponding file.
{
"autoload": {
"classmap": ["lib"]
}
}
● Run the composer dump-autoload command, and Composer will read the files in
the lib directory to create a map of classes that can be autoloaded.
Autoloading: PSR-0
● PSR-0 is a standard recommended by the PHP-FIG group for autoloading.
● In the PSR-0 standard, you must use namespaces to define your libraries.
● The fully qualified class name must reflect the \<Vendor Name>\(<Namespace>\)*<Class
Name> structure.
● Also, your classes must be saved in files that follow the same directory structure as that of
the namespaces.
{
"autoload": {
"psr-0": {
"DDUMCA\\Library": "src"
}
}
}
● In PSR-0 autoloading, you need to map namespaces to directories.
● In the above example, weʼre telling Composer that anything which starts with the
DDUMCA\Library namespace should be available in the src\DDUMCA\Library directory.
Autoloading: PSR-0
● For example, if you want to define the Foo class in the src\DDUMCA\Library directory, you need to
create the src\DDUMCA\Library\Foo.php file as shown in the following snippet:
<?php
namespace DDUMCA\Library;
class Foo
{
//...
}
?>
● As you can see, this class is defined in the DDUMCA\Library namespace. Also, the file name
corresponds to the class name. Letʼs quickly see how you could autoload the Foo class.
<?php
require 'vendor/autoload.php';
$objFoo = new DDUMCA\Library\Foo();
?>
● Composer will autoload the Foo class from the src\DDUMCA\Library directory.
● So that was a brief explanation of file, classmap, and PSR-0 autoloading in Composer.
PSR-4 Autoloading
● PSR-4 is similar to PSR-0 autoloading in that you need to use namespaces, but you
donʼt need to mimic the directory structure with namespaces.
● In PSR-0 autoloading, you must map namespaces to the directory structure.
● As discussed, if you want to autoload the DDUMCA\Library\Foo class, it must be
located at src\DDUMCA\Library\Foo.php.
● In PSR-4 autoloading, you can shorten the directory structure, which results in a
much simpler directory structure compared to PSR-0 autoloading.
● Here's what the composer.json file looks with PSR-4 autoloading.
{
"autoload": {
"psr-4": {
"Tutsplus\\Library\\": "src"
}
}
}
PSR-4 Autoloading
● Itʼs important to note that weʼve added trailing backslashes at the end of namespaces.
● The above mapping tells Composer that anything which starts with the
DDUMCA\Library namespace should be available in the src directory.
● So you donʼt need to create the DDUMCA and Library directories.
● For example, if you request the DDUMCA\Library\Foo class, Composer will try to load
the src\Foo.php file.
● Itʼs important to understand that the Foo class is still defined under the
DDUMCA\Library namespace; itʼs just that you donʼt need to create directories that
mimic the namespaces.
● The src\Foo.php file contents will be identical to those of the
src\DDUMCA\Library\Foo.php file.
● PSR-4 results in a much simpler directory structure, since you can omit creating
nested directories while still using full namespaces.
● PSR-4 is the recommended way of autoloading, and itʼs widely accepted in the PHP
community. You should start using it in your applications if you havenʼt done so
already!
Composer Autoload - Example
Loading classes using the require_once construct
First, create the following directory structure with files:
.
├── app
│ ├── bootstrap.php
│ └── models
│ └── User.php
└── index.php
-----------------------------------
//bootstrap.php //index.php
<?php <?php
require './app/bootstrap.php';
require_once 'models/User.php';
$user = new User('admin’,'$ecurePa$$w0rd1');
● The User.php file in the models folder holds the User class:
<?php
class User{
private $username;
private $password;
public function __construct($username, $password){
$this->username = $username;
$this->password = password_hash($password);
}
public function getUsername(): string{
return $this->username;
}
}
● The bootstrap.php file uses the require_once construct to load the User class from
the User.php file in the models folder
● The index.php file loads the bootstrap.php file and uses the User class
● When you have more classes in the models folder, you can add more require_once
statement to the bootstrap.php file to load those classes.
● This application structure works well if you have a small number of classes.
● However, when the application has a large number of classes, the require_once
doesnʼt scale well.
● In this case, you can use the spl_autoload_register() function to automatically
loads the classes from their files.
● The problem with the spl_autoload_register() function is that you have to
implement the autoloader functions by yourself.
● And your autoloaders may not like autoloaders developed by other developers.
● Therefore, when you work with a different codebase, you need to study the
autoloaders in that particular codebase to understand how they work.
● This is why Composer comes into play.
Autoloading classes with Composer
You first create a new file called composer.json under the projectʼs root folder. The
project directory will look like this:
├── app
│ ├── bootstrap.php
│ └── models
│ └── User.php
├── composer.json
└── index.php
Autoloading classes with Composer
In the composer.json, you add the following code:
{
"autoload": {
"classmap": ["app/models"]
}
}
This code means that Composer will autoload all class files defined the app/models folder.
If you have classes from other folders that you want to load, you can specify them in classmap array:
{
"autoload": {
"classmap": ["app/models", "app/services"]
}
}
In this example, Composer will load classes from both models and services folders under the app folder.
● launch the Command Prompt on Terminal and navigate to the project directory. Then, type the following
command from the project directory:
composer dump-autoload
● Composer will generate a directory called vendor that contains a number of files like this:
.
├── app
│ ├── bootstrap.php
│ └── models
│ └── User.php
├── composer.json
├── index.php
└── vendor
├── autoload.php
└── composer
├── autoload_classmap.php
├── autoload_namespaces.php
├── autoload_psr4.php
├── autoload_real.php
├── autoload_static.php
├── ClassLoader.php
└── LICENSE
<?php
require_once __DIR__ . '/../vendor/autoload.php';
<?php
require './app/bootstrap.php';
$user = new User('admin', '$ecurePa$$w0rd1');
● From now, whenever you have a new class in the models directory, you need to run
the command composer dump-autoload again to regenerate the autoload.php file.
● For example, the following defines a new class called Comment in the Comment.php file
under the models folder:
<?php
class Comment
{
private $comment;
public function __construct(string $comment)
{
$this->comment = $comment;
}
public function getComment(): string
{
return strip_tags($this->comment);
}
}
● If you donʼt run the composer dump-autoload command and use the Comment
class in the index.php file, youʼll get an error:
<?php
require './app/bootstrap.php';
$user = new User('admin', '$ecurePa$$w0rd1');
$comment = new Comment('<h1>Hello</h1>');
echo $comment->getComment();
● Error:
Fatal error: Uncaught Error: Class 'Comment' not found
in...
● However, if you run the composer dump-autoload command again, the index.php
file will work properly.
Composer autoload with PSR-4
● PSR stands for PHP Standard Recommendation.
● PSR is a PHP specification published by the PHP Framework Interop Group or
PHP-FIG.
● The goals of PSR are to enable interoperability of PHP components and to provide
a common technical basis for the implementation of best practices in PHP
programming.
● PHP-FIG has published a lot of PSR starting from PSR-0.
● PSR-4 is auto-loading standard that describes the specification for autoloading
classes from file paths. https://www.php-fig.org/psr/psr-4/
● According to the PSR-4, a fully qualified class name has the following structure:
\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
● The structure starts with a namespace, followed by one or more sub namespaces,
and the class name.
Composer autoload with PSR-4
● To comply with PSR-4, you need to structure the previous application like this:
.
├── app
│ ├── DDUMCA
│ │ ├── Auth
│ │ │ └── User.php
│ │ └── Blog
│ │ └── Comment.php
│ └── bootstrap.php
├── composer.json
└── index.php
Composer autoload with PSR-4
The new structure has the following changes:
<?php
namespace DDUMCA\Auth;
class User{
// implementation
// ...
}
Composer autoload with PSR-4
● Third, the Comment.php is under the Acme/Blog folder. The Comment class has the namespace
DDUMCA\Blog:
<?php
namespace DDUMCA\Blog;
class Comment
{
// implementation
// ...
}
● Fourth, the composer.json file looks like the following:
{
"autoload": {
"psr-4": {
"DDUMCA\\":"app/DDUMCA"
}
}
}
● Instead using the classmap, the composer.json file now uses psr-4. The psr-4 maps the
namespace "DDUMCA\\" to the "app/DDUMCA" folder.
● Note that the second backslash (\) in the DDUMCA\ namespace is used to escape the first
backslash (\).
Composer autoload with PSR-4
● Fifth, to use the User and Comment classes in the index.php file, you need to run
the composer dump-autoload command to generate the autoload.php file:
composer dump-autoload
● Since the User and Comment classes have namespaces, you need to have the use
statements in index.php as follows:
<?php
require './app/bootstrap.php';
use DDUMCA\Auth\User as User;
use DDUMCA\Blog\Comment as Comment;
$user = new User('admin', '$ecurePa$$w0rd1');
$comment = new Comment('<h1>Hello</h1>');
echo $comment->getComment();
HOW TO USE COMPOSER IN PHP
● The first thing you need to do is create the composer.json file. This file contains all the
data for setting up Composer for the project. This can be done by adding the following
command to the terminal:
composer init
{
"name": "vendor/myproject",
"description": "How to use Composer in PHP",
"authors": [
{
"name": "temp",
"email": "temp@gmail.com"
}
],
"minimum-stability": "dev",
"require": { }
}
From now on, Composer settings (includes settings for libraries) will be saved in this composer.json file.
Adding PHP Libraries with Composer
● First, you need to determine which libraries your PHP project needs by searching them here.
● Once you've determined the libraries that your PHP project needs to use, integrate them into your PHP
project with Composer by two methods below:
● For instance, I want to use the latest version of the phpro/grumphp library (itʼs used for checking the code
quality).
● This command will require Composer to download the phpro/grumphp library. This library will be saved in
the vendor file in your PHP project.
● In addition, the vendor/autoload.php file is automatically created. This file is used for autoloading libraries
for the PHP project.
● Go back to the composer.json file, the following code will show up in the file:
"require": {
"phpro/grumphp": "^0.19.1"
}
Adding PHP Libraries with Composer
● From now on, when you want to share your PHP project, just copy only the
composer.json file instead of the whole vendor folder. When you paste the
composer.json file to another computer, Composer will automatically update the
vendor folder.
Adding PHP Libraries with Composer
Method 2: Enter a Command in the composer.json file
● Instead of entering a command directly from the terminal as method 1, you can edit the description of the
library in the composer.json file by adding the code with the following content into the require section:
"library-name":"^the-version"
● For example, I want to integrate the phpro/grumphp library version 0.19.1 into Composer, so I fill in the
require section with the following content:
"require": {
"phpro/grumphp": "^0.19.1",
}
● After that, enter the composer update to install the library. This command will automatically update the
latest version of some libraries that are necessary for the PHP project.
● The advantage of this second method is that you can list all libraries before entering the composer update
command. Therefore, this method is very convenient when you need to install many libraries at once.
● To use these above libraries for the project, you just need to enter the include vendor/autoload.php
command to the index.php file in the PHP project.
Remove a Library from the PHP Project
● To remove a library that was already integrated into a PHP project (saved in the
vendor file), you can follow one of two methods:
● In this command, vendor/package is the name of the library you want to remove.
● For example, I want to remove the phpro/grumphp library, so the command will be
like this:
<?php
require_once __DIR__ . '/vendor/autoload.php';
● The only thing that this file will do now is include the file that handles all the
autoloading from the Composer code.
● Later, we will initialize everything here, such as database connections,
configuration readers, and so on, but right now, let's leave it empty.
MVC Pattern - Working with requests
● The main purpose of a web application is to process HTTP requests coming from
the client and return a response.
● If that is the main goal of your application, managing requests and responses
should be an important part of your code.
● PHP is a language that can be used for scripts, but its main usage is in web
applications.
● Due to this, the language comes ready with a lot of helpers for managing requests
and responses.
● Still, the native way is not ideal, and as good OOP developers, we should come up
with a set of classes that help with that.
● The main elements for this small project are the request and the router.
MVC Pattern - Working with requests
● Create the src/Core directory for all the classes related to the framework.
● The first thing to consider is what a request looks like.
● A request is basically a message that goes to a URL, and has a method—GET or
POST for now.
● The URL is at the same time composed of two parts: the domain of the web
application, that is, the name of your server, and the path of the request inside the
server.
● For example, if you try to access http://bookstore.com/my-books, the first part,
http://bookstore.com, would be the domain and /mybooks would be the path.
● In fact, http would not be part of the domain, but we do not need that level of
granularity for our application. You can get this information from the global array
$_SERVER that PHP populates for each request.
MVC Pattern - requests class
● Our Request class should have a property for each of those three elements,
followed by a set of getters and some other helpers that will be useful for the user.
Also, we should initialize all the properties from $_SERVER in the constructor.
● Other than the getters for each property, you can add the methods getUrl, isPost,
and isGet.
● The user could find the same information using the already existing getters, but as
they will be needed a lot, it is always good to make it easier for the user.
● Also note that the properties are coming from the values of the $_SERVER array:
HTTP_HOST, REQUEST_URI, and REQUEST_METHOD.
MVC Pattern - request class
<?php
namespace Bookstore\Core;
class Request {
const GET = 'GET';
const POST = 'POST';
private $domain;
private $path;
private $method;
public function __construct() {
$this->domain = $_SERVER['HTTP_HOST'];
$this->path = $_SERVER['REQUEST_URI'];
$this->method = $_SERVER['REQUEST_METHOD'];
}
public function getUrl(): string {
return $this->domain . $this->path;
}
MVC Pattern - request class
public function getDomain(): string {
return $this->domain;
}
public function getPath(): string {
return $this->path;
}
public function getMethod(): string {
return $this->method;
}
public function isPost(): bool {
return $this->method === self::POST;
}
public function isGet(): bool {
return $this->method === self::GET;
}
}
MVC Pattern - Filtering parameters from requests
● Another important part of a request is the information that comes from the user,
that is, the GET and POST parameters, and the cookies.
● As with the $_SERVER global array, this information comes from $_POST, $_GET,
and $_COOKIE, but it is always good to avoid using them directly, without filtering,
as the user could send malicious code.
● You can implement a class that will represent a map—key-value pairs—that can be
filtered. Call it FilteredMap, and will include it in namespace, Bookstore\Core.
● We will use it to contain the parameters GET and POST and the cookies as two new
properties in our Request class.
● The map will contain only one property, the array of data, and will have some
methods to fetch information from it.
● To construct the object, we need to send the array of data as an argument to the
constructor:
MVC Pattern - Filtering parameters from requests
<?php
namespace Bookstore\Core;
class FilteredMap {
private $map;
public function __construct(array $baseMap) {
$this->map = $baseMap;
}
public function has(string $name): bool {
return isset($this->map[$name]);
}
public function get(string $name) {
return $this->map[$name] ?? null;
}
MVC Pattern - Filtering parameters from requests
● This class, just implements the singleton pattern and wraps the creation of a PDO
instance.
● In order to get a database connection, we just need to write Db::getInstance().
● Although it might not be true for all models, in our application, they will always have to
access the database.
● We could create an abstract class where all models extend.
● This class could contain a $db protected property that will be set on the constructor.
● With this, we avoid duplicating the same constructor and property definition across all our
models. Copy the following class into src/Models/ AbstractModel.php:
MVC Pattern - model
<?php
namespace Bookstore\Core;
use PDO;
class Db {
private static $instance;
private static function connect(): PDO {
$dbConfig = Config::getInstance()->get('db');
return new PDO(
'mysql:host=127.0.0.1;dbname=bookstore',
$dbConfig['user'],
$dbConfig['password']
);
}
public static function getInstance(){
if (self::$instance == null) {
self::$instance = self::connect();
}
return self::$instance;
}
}
MVC Pattern - model
<?php
namespace Bookstore\Models;
use PDO;
abstract class AbstractModel {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
}
MVC Pattern - model
● Finally, to finish the setup of the models, we could create a new exception that represents
an error from the database.
● It will not contain any code, but we will be able to differentiate where an exception is
coming from. We will save it in src/Exceptions/DbException.php:
<?php
namespace Bookstore\Exceptions;
use Exception;
class DbException extends Exception {
}
● Now that we've set the ground, we can start writing our models. It is up to you to organize
your models, but it is a good idea to mimic the domain objects structure.
● In this case, we would have three models: CustomerModel, BookModel, and SalesModel.
MVC Pattern - model
//create a CustomerModel class in src/Models/
CustomerModel.php
//create a BookModel class in src/Models/
BookModel.php
//create a SalesModel class in src/Models/
SalesModel.php
MVC Pattern - view
MVC Pattern - controller
● It is finally time for the director of the orchestra.
● Controllers represent the layer in our application that, given a
request, talks to the models and builds the views.
● They act like the manager of a team: they decide what
resources to use depending on the situation.
● As we stated when explaining models, it is sometimes difficult
to decide if some piece of logic should go into the controller or
the model.
● At the end of the day, MVC is a pattern, like a recipe that guides
you, rather than an exact algorithm that you need to follow step
by step.
● There will be scenarios where the answer is not straightforward,
so it will be up to you; in these cases, just try to be consistent.
MVC Pattern - controller
The following are some common scenarios that might be difficult to localize:
● The request points to a path that we do not support. This scenario is already covered
in our application, and it is the router that should take care of it, not the controller.
● The request tries to access an element that does not exist, for example, a book ID that
is not in the database. In this case, the controller should ask the model if the book
exists, and depending on the response, render a template with the book's contents,
or another with a "Not found" message.
● The user tries to perform an action, such as buying a book, but the parameters
coming from the request are not valid. This is a tricky one. One option is to get all the
parameters from the request without checking them, sending them straight to the
model, and leaving the task of sanitizing the information to the model. Another
option is that the controller checks that the parameters provided make sense, and
then gives them to the model. There are other solutions, like building a class that
checks if the parameters are valid, which can be reused in different controllers. In this
case, it will depend on the amount of parameters and logic involved in the
sanitization. For requests receiving a lot of data, the third option looks like the best of
them, as we will be able to reuse the code in different endpoints, and we are not
writing controllers that are too long. But in requests where the user sends one or two
parameters, sanitizing them in the controller might be good enough.
MVC Pattern - Controller
● Now that we've set the ground, let's prepare our application to use controllers.
● The first thing to do is to update our index.php, which has been forcing the
application to always render the same template. Instead, we should be giving this
task to the router, which will return the response as a string that we can just print
with echo.
● Update your index.php file with the following content:
<?php
use Bookstore\Core\Router;
use Bookstore\Core\Request;
require_once __DIR__ . '/vendor/autoload.php';
$router = new Router();
$response = $router->route(new Request());
echo $response;
MVC Pattern - Controller
● As you might remember, the router instantiates a controller class, sending the
request object to the constructor.
● But controllers have other dependencies as well, such as the template engine, the
database connection, or the configuration reader.
● Even though this is not the best solution (you will improve it once we cover
dependency injection in the next section), we could create an AbstractController
that would be the parent of all controllers, and will set those dependencies.
● src/Controllers/AbstractController.php:
MVC Pattern - Controller
● When instantiating a controller, we will set some properties that will be useful
when handling requests.
● Another thing that is useful to some controllers—but not all of them—is the
information about the user performing the action.
● As this is only going to be available for certain routes, we should not set it when
constructing the controller.
● Instead, we can have a setter for the router to set the customer ID when available;
in fact, the router does that already.
MVC Pattern - Controller
<?php
namespace Bookstore\Controllers;
use Bookstore\Core\Config;
use Bookstore\Core\Db;
use Bookstore\Core\Request;
use Monolog\Logger;
use Twig_Environment;
use Twig_Loader_Filesystem;
use Monolog\Handler\StreamHandler;
abstract class AbstractController {
protected $request;
protected $db;
protected $config;
protected $view;
protected $log;
MVC Pattern - Controller
public function __construct(Request $request) {
$this->request = $request;
$this->db = Db::getInstance();
$this->config = Config::getInstance();
$loader = new Twig_Loader_Filesystem( __DIR__ . '/../../views' );
$this->view = new Twig_Environment($loader);
$this->log = new Logger('bookstore');
$logFile = $this->config->get('log');
$this->log->pushHandler(
new StreamHandler($logFile, Logger::DEBUG)
);
}
public function setCustomerId(int $customerId) {
$this->customerId = $customerId;
}
protected function render(string $template, array $params): string
{
return $this->view->loadTemplate($template)->render($params);
}
}
MVC Pattern - Error Controller
<?php
namespace Bookstore\Controllers;
class ErrorController extends AbstractController {
public function notFound(): string {
$properties = ['errorMessage' => 'Page not found!'];
return $this->render('error.twig', $properties);
}
}
//CustomerController.php
//BookContoller.php
Dependency injection
● One of the signs of a potential source of problems is when you use the new
statement in your code to create an instance of a class that does not belong to your
code base—also known as a dependency.
● Using new to create a domain object like Book or Sale is fine. Using it to instantiate
models is also acceptable.
● But manually instantiating, which something else, such as the template engine, the
database connection, or the logger, is something that you should avoid.
● There are different reasons that support this idea:
Dependency injection
● If you want to use a controller from two different places, and each of these places
needs a different database connection or log file, instantiating those dependencies
inside the controller will not allow us to do that. The same controller will always
use the same dependency.
● Instantiating the dependencies inside the controller means that the controller is
fully aware of the concrete implementation of each of its dependencies, that is, the
controller knows that we are using PDO with the MySQL driver and the location of
the credentials for the connection. This means a high level of coupling in your
application—so, bad news.
● Replacing one dependency with another that implements the same interface is not
easy if you are instantiating the dependency explicitly everywhere, as you will have
to search all these places, and change the instantiation manually.
Dependency injection
● For all these reasons, and more, it is always good to provide the dependencies that
a class such as a controller needs instead of letting it create its own. This is
something that everybody agrees with. The problem comes when implementing a
solution. There are different options:
● We have a constructor that expects (through arguments) all the dependencies that
the controller, or any other class, needs. The constructor will assign each of the
arguments to the properties of the class.
● We have an empty constructor, and instead, we add as many setter methods as the
dependencies of the class.
● A hybrid of both, where we set the main dependencies through a constructor, and
set the rest of the dependencies via setters.
● Sending an object that contains all the dependencies as a unique argument for the
constructor, and the controller gets the dependencies that it needs from that
container
Dependency injection
● Each solution has its pros and cons. If we have a class with a lot of dependencies,
injecting all of them via the constructor would make it counterintuitive, so it would
be better if we inject them using setters, even though a class with a lot of
dependencies looks like bad design.
● If we have just one or two dependencies, using the constructor could be
acceptable, and we will write less code.
● For classes with several dependencies, but not all of them mandatory, using the
hybrid version could be a good solution.
● The fourth option makes it easier when injecting the dependencies as we do not
need to know what each object expects.
● The problem is that each class should know how to fetch its dependency, that is,
the dependency name, which is not ideal.
Dependency injection
● Open source solutions for dependency injectors are already available, but we think
that it would be a good experience to implement a simple one by yourself. The idea
of our dependency injector is a class that contains instances of the dependencies
that your code needs.
● This class, which is basically a map of dependency names to dependency
instances, will have two methods: a getter and a setter of dependencies.
● We do not want to use a static property for the dependencies array, as one of the
goals is to be able to have more than one dependency injector with a different set
of dependencies.
● Add the following class to src/Utils/DependencyInjector.php
Dependency injection
<?php
namespace Bookstore\Utils;
use Bookstore\Exceptions\NotFoundException;
class DependencyInjector {
private $dependencies = [];
public function set(string $name, $object) {
$this->dependencies[$name] = $object;
}
public function get(string $name) {
if (isset($this->dependencies[$name])) {
return $this->dependencies[$name];
}
throw new NotFoundException(
$name . ' dependency not found.'
);
}
}
Dependency injection
● Having a dependency injector means that we will always use the same instance of a given class
every time we ask for it, instead of creating one each time. That means that singleton
implementations are not needed anymore
● Replace the existing code with the following in the src/Core/Config.php file
<?php
namespace Bookstore\Core;
use Bookstore\Exceptions\NotFoundException;
class Config {
private $data;
public function __construct() {
$json = file_get_contents(
__DIR__ . '/../../config/app.json'
);
$this->data = json_decode($json, true);
}
public function get($key) {
if (!isset($this->data[$key])) {
throw new NotFoundException("Key $key not in config.");
}
return $this->data[$key];
}
}
Dependency injection
●
●
●
●
●