0% found this document useful (0 votes)
369 views111 pages

PHP MVC

The document discusses Model-View-Controller (MVC) architecture and PHP Composer. MVC is a design pattern that separates an application into three main components: the model, the view, and the controller. The model manages the application's data logic and rules, the view displays the data to the user, and the controller handles input and communication between the model and view. PHP Composer is a dependency manager that allows developers to declare and install libraries and their dependencies, making it easier to incorporate third-party code into a PHP project.

Uploaded by

kronza
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
369 views111 pages

PHP MVC

The document discusses Model-View-Controller (MVC) architecture and PHP Composer. MVC is a design pattern that separates an application into three main components: the model, the view, and the controller. The model manages the application's data logic and rules, the view displays the data to the user, and the controller handles input and communication between the model and view. PHP Composer is a dependency manager that allows developers to declare and install libraries and their dependencies, making it easier to incorporate third-party code into a PHP project.

Uploaded by

kronza
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 111

MVC Architecture

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

composer require intervention/image

● The composer downloads the specific version of the library into the vendor
directory under your project and provides you with an autoloader.
<?php

// include composer autoload


require "vendor/autoload.php";

// import the Intervention image manager class file


use Intervention\Image\ImageManagerStatic as Image;

$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

● The most important file to you for now is autoload.php file.


● Load the autoload.php file in the bootstrap.php file using the require_once
construct:

<?php
require_once __DIR__ . '/../vendor/autoload.php';

● Finally, you can use the User class in the index.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:

● First, the models directory is deleted.


● Second, the User.php is under the DDUMCA/Auth folder.
● And the User class is namespaced with DDUMCA/Auth. Notice how namespaces
map to the directory structure.
● This also helps you find a class file more quickly by looking at its namespace.

<?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

Package name (/) [admin/my_project]: vendor/myproject (/*this is the library name/*/)


Description []: How to use Composer in PHP
Author [temp <temp@gmail.com>, n to skip]: temp<temp@gmail.com>1
Minimum Stability []: dev
Package Type (e.g. library, project, metapackage, composer-plugin) []:
License []:
Define your dependencies.
Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes]? No
Do you confirm generation [yes]? Y
HOW TO USE COMPOSER IN PHP
Done! Iʼve finished creating the composer.json file with the following content:

{
"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:

Method 1: Enter a Command on the terminal

● For instance, I want to use the latest version of the phpro/grumphp library (itʼs used for checking the code
quality).

composer require phpro/grumphp

● 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:

Method 1: Using the composer command


● From the terminal, enter the following command:

composer remove vendor/package

● 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:

composer remove phpro/grumphp


Remove a Library from the PHP Project
Method 2: Update the composer.json file
● Go to the composer.json file, navigate to the require section, delete the folder you
want (for instance, phpro/grumphp), and then enter the composer update
command.
Metadata
● In order to know where to find the libraries that you define as dependencies,
Composer keeps a repository of packages and versions, known as Packagist.
● This repository keeps a lot of useful information for developers, such as all the
versions available for a given package, the authors, some description of what the
package does (or a website pointing to that information), and the dependencies
that this package will download.
● You can also browse the packages, searching by name or categories.
● But how does Packagist know about this? It is all thanks to the composer.json file
itself.
● In there, you can define all the metadata of your application in a format that
Composer understands.
Metadata
{
"name": "picahielos/bookstore",
"description": "Manages an online bookstore.",
"minimum-stability": "stable",
"license": "Apache-2.0",
"type": "project",
"authors": [
{
"name": "Antonio Lopez",
"email": "antonio.lopez.zapata@gmail.com"
}
],
// ...
}
Metadata
● The configuration file now contains the name of the package following the
Composer convention: vendor name, slash, and the package name—in this case,
picahielos/bookstore.
● We also add a description, license, authors, and other metadata.
● If you have your code in a public repository such as GitHub, adding this
composer.json file will allow you to go to Packagist and insert the URL of your
repository.
● Packagist will add your code as a new package, extracting the info from your
composer.json file.
● It will show the available versions based on your tags or branches.
● In order to learn more about it, visit the official documentation at
https://getcomposer.org/doc/04-schema.md.
MVC Pattern
● In MVC applications, we usually have one file that gets all the requests, and routes
them to the specific controller depending on the URL.
● This logic can generally be found in the index.php file in our root directory.

<?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

public function getInt(string $name) {


return (int) $this->get($name);
}
public function getNumber(string $name) {
return (float) $this->get($name);
}
public function getString(string $name, bool $filter = true)
{
$value = (string) $this->get($name);
return $filter ? addslashes($value) : $value;
}
}
MVC Pattern - Filtering parameters from requests
● These three methods in the preceding code allow the user to get parameters of a
specific type.
● Let's say that the developer needs to get the ID of the book from the request.
● The best option is to use the getInt method to make sure that the returned value is
a valid integer, and not some malicious code that can mess up our database.
● Also note the function getString, where we use the addSlashed method.
● This method adds slashes to some of the suspicious characters, such as slashes or
quotes, trying to prevent malicious code with it.
MVC Pattern - Filtering parameters from requests
<?php Code to get the GET and POST parameters as
namespace Bookstore\Core; well as the cookies from our Request class
class Request { using our FilteredMap.
// ...
private $params;
private $cookies;
public function __construct() {
$this->domain = $_SERVER['HTTP_HOST'];
$this->path = explode('?', $_SERVER['REQUEST_URI'])[0];
$this->method = $_SERVER['REQUEST_METHOD'];
$this->params = new FilteredMap(
array_merge($_POST, $_GET)
);
$this->cookies = new FilteredMap($_COOKIE);
}
// ...
public function getParams(): FilteredMap {
return $this->params; With this new addition, a developer could get the
} POST parameter price with the following line of code:
public function getCookies(): FilteredMap {
return $this->cookies; $price =
} $request->getParams()->getNumber('price');
}
MVC Pattern - Mapping routes to controllers
● If you can recall from any URL that you use daily, you will probably not see any PHP file as
part of the path, like we have with http://localhost:8000/init.php.
● Websites try to format their URLs to make them easier to remember instead of depending
on the file that should handle that request.
● Also, as we've already mentioned, all our requests go through the same file, index.php,
regardless of their path.
● Because of this, we need to keep a map of the URL paths, and who should handle them.
● Sometimes, we have URLs that contain parameters as part of their path, which is different
from when they contain the GET or POST parameters.
● For example, to get the page that shows a specific book, we might include the ID of the
book as part of the URL, such as /book/12 or /book/3.
● The ID will change for each different book, but the same controller should handle all of
these requests.
● To achieve this, we say that the URL contains an argument, and we could represent it by
/book/:id, where id is the argument that identifies the ID of the book.
● Optionally, we could specify the kind of value this argument can take, for example,
number, string, and so on.
MVC Pattern - Mapping routes to controllers
● Controllers, the ones in charge of processing requests, are defined by a method's class.
● This method takes as arguments all the arguments that the URL's path defines, such as the
ID of the book.
● We group controllers by their functionality, that is, a BookController class will contain the
methods related to requests about books.
● Having defined all the elements of a route—a URL-controller relationship—we are ready to
create our routes.json file, a configuration file that will keep this map.
● Each entry of this file should contain a route, the key being the URL, and the value, a map of
information about the controller.
{
"books/:page": {
"controller": "Book",
"method": "getAllWithPage",
"params": {
"page": "number"
}
}
}
MVC Pattern - Mapping routes to controllers
● Controllers, the ones in charge of processing requests, are defined by a method's class.
● This method takes as arguments all the arguments that the URL's path defines, such as the
ID of the book.
● We group controllers by their functionality, that is, a BookController class will contain the
methods related to requests about books.
● Having defined all the elements of a route—a URL-controller relationship—we are ready to
create our routes.json file, a configuration file that will keep this map.
● Each entry of this file should contain a route, the key being the URL, and the value, a map of
information about the controller.
{
"books/:page": {
"controller": "Book",
"method": "getAllWithPage",
"params": {
"page": "number"
}
}
}
MVC Pattern - Mapping routes to controllers
● The route in the preceding example refers to all the URLs that follow the pattern /
books/:page, with page being any number.
● Thus, this route will match URLs such as /books/23 or /books/2, but it should not
match /books/one or /books.
● The controller that will handle this request should be the getAllWithPage method
from BookController; we will append Controller to all the class names.
public function getAllWithPage(int $page): string {
//...
}
● There is one last thing we should consider when defining a route.
● For some endpoints, we should enforce the user to be authenticated, such as when
the user is trying to access their own sales. We could define this rule in several ways,
but we chose to do it as part of the route, adding the entry "login": true as part of the
controller's information.
MVC Pattern - Mapping routes to controllers
{ "sales": {
//... "controller": "Sales",
"books": { "method": "getByUser" ,
"controller": "Book",
“method": "getAll" "login": true
}, },
"book/:id": { "sales/:id": {
"controller": "Book", "controller": "Sales",
"method": "get", "method": "get",
"params": {
"id": "number" "login": true,
} "params": {
}, "id": "number"
"books/search": { }
"controller": "Book", },
"method": "search" "my-books": {
},
"login": { "controller": "Book",
"controller": "Customer", "method": "getByUser",
“method": "login" "login": true
}, }
routes that define all the views }
MVC Pattern - Mapping routes to controllers
{ "book/:id/return": {
// … "controller": "Book",
"book/:id/buy": {
"controller": "Sales", "method": "returnBook",
"method": "add", "login": true
"login": true "params": {
"params": { "id": "number"
"id": "number"
} }
}, }
"book/:id/borrow": { }
"controller": "Book",
"method": "borrow",
"login": true
"params": {
"id": "number"
}
}, actions that are trying to modify data rather than
requesting it
MVC Pattern - Router
● The main goal is to receive a Request object, decide which controller should handle
it, invoke it with the necessary parameters, and return the response from that
controller.
● The main goal of this section is to understand the importance of the router rather
than its detailed implementation, but we will try to describe each of its parts.
● src/Core/Router.php
MVC Pattern - Router
<?php
namespace Bookstore\Core;
use Bookstore\Controllers\ErrorController;
use Bookstore\Controllers\CustomerController;
class Router {
private $routeMap;
private static $regexPatters = [ 'number' => '\d+', 'string' => '\w' ];
public function __construct() {
$json = file_get_contents( __DIR__ . '/../../config/routes.json');
$this->routeMap = json_decode($json, true);
}
public function route(Request $request): string {
$path = $request->getPath();
foreach ($this->routeMap as $route => $info) {
$regexRoute = $this->getRegexRoute($route, $info);
if (preg_match("@^/$regexRoute$@", $path)) {
return $this->executeController($route,$path,$info,$request);
}
}
$errorController = new ErrorController($request);
return $errorController->notFound();
}
}
MVC Pattern - Router
● The constructor of this class reads from the routes.json file, and stores the
content as an array.
● Its main method, route, takes a Request object and returns a string, which is
what we will send as output to the client.
● This method iterates all the routes from the array, trying to match each with the
path of the given request.
● Once it finds one, it tries to execute the controller related to that route.
● If none of the routes are a good match to the request, the router will execute the
notFound method of the ErrorController, which will then return an error page.
MVC Pattern - URLs match with regular expressions
● While matching a URL with the route, we need to take care of the arguments for
dynamic URLs, as they do not let us perform a simple string comparison.
● PHP has a very strong tool for performing string comparisons with dynamic
content: regular expressions.
● A regular expression is a string that contains some wildcard characters that will
match the dynamic content.
● Some of the most important ones are as follows:
○ • ^ : This is used to specify that the matching part should be the start of the
whole string
○ • $ : This is used to specify that the matching part should be the end of the
whole string
○ • \d : This is used to match a digit
○ • \w : This is used to match a word
○ • + : This is used for following a character or expression, to let that
character or expression to appear at least once or many times
○ • * : This is used for following a character or expression, to let that
character or expression to appear zero or many times
○ • . : This is used to match any single character
MVC Pattern - URLs match with regular expressions
● In PHP, we have different functions to work with regular expressions.
● The easiest of them, and the one that we will use, is pregmatch.
● This function takes a pattern as its first argument (delimited by two
characters, usually @ or /), the string that we are trying to match as the
second argument, and optionally, an array where PHP stores the
occurrences found.
● The function returns a Boolean value, being true if there was a match,
false otherwise. We use it as follows for router:
preg_match("@^/$regexRoute$@", $path)
● The $path variable contains the path of the request, for example,
/books/2.
● We match using a pattern that is delimited by @, has the ^ and $
wildcards to force the pattern to match the whole string, and contains
the concatenation of / and the variable $regexRoute.
MVC Pattern - URLs match with regular expressions
● The content of this variable is given by the following method; add this as well to
your Router class:
private function getRegexRoute(string $route,array $info): string {
if (isset($info['params'])) {
foreach ($info['params'] as $name => $type) {
$route = str_replace( ':' . $name,
self::$regexPatters[$type], $route );
}
}
return $route;
}
● The method iterates the parameters list coming from the information of the
route. For each parameter, the function replaces the name of the parameter
inside the route by the wildcard character corresponding to the type of
parameter—check the static array, $regexPatterns.
● Examples:
○ • The route /books will be returned without a change, as it does not contain any
argument
○ • The route books/:id/borrow will be changed to books/\d+/borrow, as the URL
argument, id, is a number
MVC Pattern - Extracting the arguments of the URL
● In order to execute the controller, we need three pieces of data: the name of the class
to instantiate, the name of the method to execute, and the arguments that the method
needs to receive.
● We already have the first two as part of the route $info array for third add below in
router
private function extractParams(string $route,string $path):array {
$params = [];
$pathParts = explode('/', $path);
$routeParts = explode('/', $route);
foreach ($routeParts as $key => $routePart) {
if (strpos($routePart, ':') === 0) {
$name = substr($routePart, 1);
$params[$name] = $pathParts[$key+1];
}
}
return $params;
}
MVC Pattern - Extracting the arguments of the URL
● This last method expects that both the path of the request and the URL of the route
follow the same pattern.
● With the explode method, we get two arrays that should match each of their entries.
● We iterate them, and for each entry in the route array that looks like a parameter, we
fetch its value in the URL.
● For example, if we had the route /books/:id/borrow and the path /books/12/borrow,
the result of this method would be the array ['id' => 12].
MVC Pattern - Executing the controller
● Implementing the method that executes the controller in charge of a given route.
● We already have the name of the class, the method, and the arguments that the
method needs, so we could make use of the call_user_func_array native function that,
given an object, a method name, and the arguments for the method, invokes the
method of the object passing the arguments.
● Add some code in router class.
MVC Pattern - Executing the controller
private function executeController(string $route,string $path,array $info,
Request $request ): string {
$controllerName = '\Bookstore\Controllers\\' . $info['controller'] .
'Controller';
$controller = new $controllerName($request);
if (isset($info['login']) && $info['login']) {
if ($request->getCookies()->has('user')) {
$customerId = $request->getCookies()->get('user');
$controller->setCustomerId($customerId);
} else {
$errorController = new CustomerController($request);
return $errorController->login();
}
}
$params = $this->extractParams($route, $path);
return call_user_func_array([$controller, $info['method']], $params);
}
MVC Pattern - model
● As you can imagine, for an application integrated with MySQL, or any other database
system, the database connection is an important element of a model.
● We chose to use PDO in order to interact with MySQL, and as you might remember,
instantiating that class was a bit of a pain.
● Let's create a singleton class that returns an instance of PDO to make things easier.
● Add this code to src/Core/Db.php:

● 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




You might also like