How to build a simple PHP MVC framework

MVC design pattern

By on Tue Mar 30 in PHP, Programming



Today I am going to show how to create a simple PHP application following the MVC pattern (Model-View-Controller). I was inspired by a PHP course I taught some years ago, and in which I built a simple e-commerce with the students. This e-commerce was based on a simple MVC framework based on PHP. Then, people who have continued with code and programming already had a smattering of what means MVC before get their hands on a real framework.

MVC frameworks are widely used in the industry because they offer a lot of advantages for rapid and structured development. There are MVC frameworks for most of the programming languages you may know, from DotNet to PHP. Unfortunately, those frameworks might have a steep learning curve. This is due to the fact that people need to learn how to write code in the framework ecosystem.

Personal note: in 2010 I was already developing software for more than 5 years, and I was looking for a good solution in order to build a web application for my boss. Briefing with a former colleague of mine (thanks Davide C.!), I started using Symfony 1.4. I used the “RTFM approach” (Read The Friendly Manual…) before writing any code. In two months I realized a medium-complex application (registration, ACL, dashboard, frontend, etc).

After that, I worked on Zend Framework, Symfony 2.0 and 5, and Laravel (currently working on 5.8), and also on microframeworks like Silex (not maintained anymore) and Lumen. Without any doubt, my favorite framework is Laravel. Despite some “magical things” that can scare people, Laravel offers a lot of out-of-the-box features that you can simply activate with the right configuration setting.

What does mean MVC?

MVC is a design pattern used to decouple data (Models), the user-interfaces (Views), and application logic (Controllers). To be able to follow this “How to”, you need to have a good knowledge of PHP and OOP (Object Oriented Programming).

Build a simple PHP MVC framework

Independently you are using Docker, XAMPP, or whatever for your development environment, let’s create a simple structure for the simple PHP MVC framework. I use to have a folder called “Solutions” for all my projects, then enter your folder, create a new folder called “simple-php-mvc” and then enter that folder.
Let’s create the basis folders for your MVC:

  1. app
  2. config
  3. public
  5. routes

Starting small, let’s create the two most important files of our simple PHP MVC: index.php and htaccess.

The htaccess configuration file

Enter the public folder, and let’s create a file called index.php

Now, at the root level of your project, let’s create a new file called .htaccess
Then open it, and put this code inside the htaccess:

<IfModule mod_rewrite.c>
RewriteEngine On

# Stop processing if already in the /public directory
RewriteRule ^public/ - [L]

# Static resources if they exist
RewriteCond %{DOCUMENT_ROOT}/public/$1 -f
RewriteRule (.+) public/$1 [L]

# Route all other requests
RewriteRule (.*) public/index.php?route=$1 [L,QSA]

Htaccess is a configuration file for the Apache web server, and the mod_rewrite directive tells to Apache that every request will end up to the index.php located in the folder named public. What does it mean? It means that if you browse https://simple-php-mvc/page1, https://simple-php-mvc/page2 or https://simple-php-mvc/page3, all of them will end up in the index.php under public, that is the entry point of your PHP MVC framework. This is a big advantage because you can now handle your request in one place, understand what resource is requested and provide the right response.
Another thing: using htaccess and drive the traffic under the public folder, the rest of your project’s structure will be hidden to anyone.

This is how your project looks like right now:

PHP MVC application folder structure
Folder structure

Bootstrap your PHP MVC framework

Now you need a way to bootstrap your app and load the code you need. We already said that index.php under the public folder is the entry point, for that reason we include the necessary files from there.

First of all, we load the config file, here is the content of index.php:

// Load Config
require_once '../config/config.php';

Now we can create a config.php file under the config folder.

Inside the config file, we can store the settings of the framework, for example, we can store the name of our app, the path of the root, and of course, the database connection parameters:

//site name
define('SITE_NAME', 'your-site-name');

//App Root
define('APP_ROOT', dirname(dirname(__FILE__)));
define('URL_ROOT', '/');
define('URL_SUBFOLDER', '');

//DB Params
define('DB_HOST', 'your-host');
define('DB_USER', 'your-username');
define('DB_PASS', 'your-password');
define('DB_NAME', 'your-db-name');


We want to be able to load the future classes without any pain (see: dozen of include or require), then we’ll use the PSR-4 autoloading with Composer.
Composer is a dependency manager for PHP, it allows you to declare the libraries your project depends on and it will manage them for you. Really helpful!

First, at the root level, you must create a file called composer.json and add the following content:

    "name": "gmaccario/simple-mvc-php-framework",
    "description": "Simple MVC PHP framework: a demonstration of how to create a simple MVC framework in PHP",
    "autoload": {
        "psr-4": {
            "App\\": "app/"

Then, assuming that you already installed composer on your computer or container, execute the following command (at the root level of your project):

composer dump-autoload

If you check your root folder now, you can see a new folder called vendor that contains the autoload.php file and the composer folder.
Open the index.php and simply add the following code at the beginning of the file:

require_once '../vendor/autoload.php';

From now on, you can use App as a starting point of your namespaces, like this:

use App\Controllers\MyController;

Now, let’s learn what the MVC acronym stands for.


A model is an object that represents your data. The model will be modeled on your database table structure and it will interact with the database operations (create, read, update and delete).
For instance, if you have a Products table like that:

  id int(10) NOT NULL auto_increment,
  title varchar(255) collate utf8_unicode_ci NOT NULL,
  description text collate utf8_unicode_ci,
  price decimal(12,5) NOT NULL,
  sku varchar(255) collate utf8_unicode_ci NOT NULL,
  image varchar(255) collate utf8_unicode_ci NOT NULL,

First of all, let’s create a new folder called Models under app folder. Then let’s create a new file called Product under Models.
Your model Product will be:

namespace App\Models;

class Product
    protected $id;
    protected $title;
    protected $description;
    protected $price;
    protected $sku;
    protected $image;

    public function getId()
        return $this->id;

    public function getTitle()
        return $this->title;

    public function getDescription()
        return $this->description;

    public function getPrice()
        return $this->price;

    public function getSku()
        return $this->sku;

    public function getImage()
        return $this->image;

    public function setTitle(string $title)
        $this->title = $title;

    public function setDescription(string $description)
        $this->description = $description;

    public function setPrice(string $price)
        $this->price = $price;

    public function setSku(string $sku)
        $this->sku = $sku;

    public function setImage(string $image)
        $this->image = $image;

    public function create(array $data)


    public function read(int $id)


    public function update(int $id, array $data)


    public function delete(int $id)


And that’s it. With the methods, you’ll create the objects you need to be filled with the real values based on the model.


The view is responsible to take in the data from the controller and display those values. That’s it. There are a lot of template engines for PHP, from Twig to Blade. For this MVC tutorial for PHP, we’ll use only plain HTML to make things simple.

In order to create a new view, we must create a new file called product.php under the views folder.
Based on the product attributes, we can write a simple HTML like this:

<!DOCTYPE html>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="shortcut icon" href="favicon.png">
    <title>Simple PHP MVC</title>
    <link rel="stylesheet" href="">


        <h1>My Product:</h1>
            <li><?php echo $product->getTitle(); ?></li>
            <li><?php echo $product->getDescription(); ?></li>
            <li><?php echo $product->getPrice(); ?></li>
            <li><?php echo $product->getSku(); ?></li>
            <li><?php echo $product->getImage(); ?></li>
        <a href="<?php echo $routes->get('homepage')->getPath(); ?>">Back to homepage</a>

    <script src="" 
    <script src=""></script>


The view is now ready to get the product object ($product) and display its values.


The controller is the heart of the application logic. Is responsible for accepting the input and converting it to commands for the model or view.
Let’s create a new folder called Controllers under the app folder, and create a new controller file called ProductController.php. Here is the content:


namespace App\Controllers;

use App\Models\Product;
use Symfony\Component\Routing\RouteCollection;

class ProductController
    // Show the product attributes based on the id.
	public function showAction(int $id, RouteCollection $routes)
        $product = new Product();

        require_once APP_ROOT . '/views/product.php';

Very simple, isn’t it? Obviously, things might be more complex, we can create a parent Controller class, a view method, and other helper functions. But it’s enough for now.

The routing system

Now we need a mechanism to deal with the URLs. We want to use a friendly URL; in other words, we want to deal with web addresses that are easy to read and that include words that describe the content of the webpage. We need a routing system then.
We can create our own route system, or since we used composer for the autoload, we can dig into the extensive Symfony ecosystem packages and work smart!
So, let’s see how we can take advantage of the Symfony Routing Component. Here is the documentation:

First of all, install the component:

composer require symfony/routing
Install symfony/routing via composer.
Install symfony/routing via composer.

If you check inside the vendor folder now, you can see that a new folder called Symfony has been created.

Let’s start to implement the routing system for our MVC framework then. The goal is to display the values of the product with ID=1 when browsing the URL /product/1
Let’s create a new file called web.php under the routes folder. This file will contain all the routes of your application.


use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

// Routes system
$routes = new RouteCollection();
$routes->add('product', new Route(constant('URL_SUBFOLDER') . '/product/{id}', array('controller' => 'ProductController', 'method'=>'showAction'), array('id' => '[0-9]+')));

We use Route and RouteCollection classes from the Symfony Routing component in order to create and list all the routes we need. We start with a single product page.

Isn’t enough: we must install also this package:

composer require symfony/http-foundation

Here some explanation:

The HttpFoundation component defines an object-oriented layer for the HTTP specification.

And again from Symfony: In PHP, the request is represented by some global variables ($_GET, $_POST, $_FILES, $_COOKIE, $_SESSION, …) and the response is generated by some functions (echo, header(), setcookie(), …).

The Symfony HttpFoundation component replaces these default PHP global variables and functions with an object-oriented layer.

OK then, let’s create the routing engine. Add a new file called Router.php inside your app folder and put this code inside it:


namespace App;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Exception\NoConfigurationException;

class Router
    public function __invoke(RouteCollection $routes)
        $context = new RequestContext();
        $request = Request::createFromGlobals();

        // Routing can match routes with incoming requests
        $matcher = new UrlMatcher($routes, $context);
        try {
            $matcher = $matcher->match($_SERVER['REQUEST_URI']);
            // Cast params to int if numeric
            array_walk($matcher, function(&$param)
                    $param = (int) $param;
            // Issue #2: Fix Non-static method ... should not be called statically
            $className = '\\App\\Controllers\\' . $matcher['controller'];
            $classInstance = new $className();
            // Add routes as paramaters to the next class
            $params = array_merge(array_slice($matcher, 2, -1), array('routes' => $routes));

            call_user_func_array(array($classInstance, $matcher['method']), $params);
        } catch (MethodNotAllowedException $e) {
            echo 'Route method is not allowed.';
        } catch (ResourceNotFoundException $e) {
            echo 'Route does not exists.';
        } catch (NoConfigurationException $e) {
            echo 'Configuration does not exists.';

// Invoke
$router = new Router();

The code is straightforward and speaks for itself, but let’s explain it a bit: the URL matcher takes in the request URI and will check if there is a match with the routes defined in routes/web.php. If there is a match, the function call_user_func_array will do the magic, calling the right method of the right controller.
Moreover, we used the function array_walk to cast the numeric values into integer values, because in our class methods we used the explicit type declaration.

Now we can include the routes system in the index.php file:


// Autoloader
require_once '../vendor/autoload.php';

// Load Config
require_once '../config/config.php';

// Routes
require_once '../routes/web.php';
require_once '../app/Router.php';

Now that we prepared the routing system, we can browse /product/1 page and see the result. Obviously, the values are now empty. Let’s add some fake values to the product (inside ProductController.php):

public function read(int $id)
    $this->title = 'My first Product';
    $this->description = 'Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum ';
    $this->price = 2.56;
    $this->sku = 'MVC-SP-PHP-01';
    $this->image = '';

    return $this;

And browse again the page /product/1

You can now add your database connection, and return the values from the database, using a raw query or an ORM like Doctrine or Eloquent.

Additional notes to the routing: your routes could overlap with each other, for example, if you write /{pageSlug} before any other route, such as /register. You can easily overcome this problem simply by writing your general route /{pageSlug} at the end of all the routes. This route will become your fallback. Or, another solution is to add a prefix, such as /static/{pageSlug} or /public/{pageSlug}.

The homepage

Let’s prepare now the homepage route. Open the routes/web.php and add the new route:

$routes->add('homepage', new Route(constant('URL_SUBFOLDER') . '/', array('controller' => 'PageController', 'method'=>'indexAction'), array()));

Obviously, we need to create the new controller PageController:


namespace App\Controllers;

use App\Models\Product;
use Symfony\Component\Routing\RouteCollection;

class PageController
    // Homepage action
	public function indexAction(RouteCollection $routes)
		$routeToProduct = str_replace('{id}', 1, $routes->get('product')->getPath());

        require_once APP_ROOT . '/views/home.php';

And the new view:

<!DOCTYPE html>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="shortcut icon" href="favicon.png">
    <title>Simple PHP MVC</title>
    <link rel="stylesheet" href="">


            <a href="<?php echo $routeToProduct ?>">Check the first product</a>

    <script src="" 
    <script src=""></script>


Since we use the same code for the header and footer, we can create a layout folder and separate the code for those pieces of HTML.

In order to navigate the page, open your browser and browse this URL:


The exact URL depends on your settings. I use Docker for my local environment, and I usually set up the port, then my URL could be slightly different from yours, for example, I use


Note: if you installed your project into a subfolder as many users seem to do, you must set up URL_SUBFOLDER constant in the config file. For example, if you installed this project inside a subfolder called simple-mvc-php-framework, your final URL will be:


Improve the PHP MVC framework

Database connection and ORM, session, cookies, better page controller that accept different page parameters, or any other feature can be added very easily, but this article wants to show only the way to build a really simple PHP MVC.

Download the code

You can download the zip file, or clone the code of this article via Github.


Once you’re getting confident with the MVC paradigm, I suggest reading the documentation of Laravel (my favorite PHP MVC framework) or Symfony and start to get your feet wet! You’ll notice that the development becomes faster than use a pure PHP solution.

Thanks for your time, I hope you got some new information about the MVC paradigm. Feel free to fork the project on GitHub, and if you have any issues, check the issues tab or open a new one (if it is a real new one!).

Anyway, you can also send me a message but I prefer to answer on Github in order to share the knowledge with other people. I decided to close the comments on this page due to the huge amount of spam, and the huge amount of duplicated questions. I hope you can understand!

Note: this project is a simple starter kit, and it works well if your environment is correctly configured. I don’t use XAMPP, or another pre-configured stack package. In the past I used to create my LAMP installing the software individually, now I am using Docker. Then if you have some issue with your environment, it’s up on you to solve your issue.

And again: this is a simple starter kit with the goal of helping people to understand some concepts behind the MVC. If you want to create a professional project, go for Laravel or Symfony.

How useful was this post?

Click on a star to rate it!

Average rating 4.6 / 5. Vote count: 11

No votes so far! Be the first to rate this post.


  1. I got ; rout doesnt exists
    what do you mean by subfolder, what I should change there?
    I define(‘DB_HOST’, ‘localhost’);
    define(‘DB_USER’, ‘root’);
    define(‘DB_PASS’, ”);
    define(‘DB_NAME’, ‘product’);
    but I didnt understand this part
    very sad

    • Hi Nasib,

      usually, I work on the main directory, which means that you can browse the web application using http://localhost

      If you installed the application in a subfolder such as ‘myproject’, you have to browse the application in this way: http://localhost/myproject
      The subfolder then needs to be set up in your config file (SUBFOLDER constant), otherwise this triggers the error “route doesn’t exist”.

      In your comment, you are listing the database settings, that had nothing to do with the subfolder.

      Hope this can help you.


    • Hi Saber,

      usually, I work on the main directory, which means that you can browse the web application using http://localhost

      If you installed the application in a subfolder such as ‘myproject’, you have to browse the application in this way: http://localhost/myproject

      The subfolder needs to be set up in your config file (SUBFOLDER constant), otherwise this triggers the error “route doesn’t exist”.

      Hope this can help you.


  2. First, I would like to thank you for this wonderful lesson.. tnk u so mutch.
    can u give me a simple method to view data from database in this fromwork? tnk u 🙂

    NB: Comments response system is not working.. try to fix it ^^

    • Hey Linkin Park 😀

      Thanks for your comment.

      First, I don’t understand what do you means when you say the content system doesn’t work. Weird, I can see your comment and I can reply to you, so…

      About the database: you can simply execute a query inside the controller and then instantiate a model, populate it with the values and pass it to the view. Or create a list of models to pass it to the view. That’s it.

      Hope this can help you.

  3. Hello again
    I mean, I can’t reply to comments or ur comnt, so i had to post a new reply augain :/
    anny way, if it is possible, give me what I can do to display the results from the database, I’m a little weak at PHP lol and I will be grateful <3

    • Ahh got it.

      In effect, it depends a lot on your PHP skills level.

      Are you able to query a table? Anyway, you can start from the beginning here

      Then simply use a PDO object in your controller in order to populate the model values.

      Smt like that:

      query('SELECT * from product') as $row) {
      $product = new Product();
      // ...

      products[] = $product;

      And then you should be able to use the $products array in your view.

      Hope this can help you.

  4. Thank you Giuseppe for this – it is very helpful, and I appreciate the time it must must taken to put this together.

    However, coming from the perspective of someone who writes code in a the most simplest readable way, and shy away from OOP, I am not convinced exactly what benefits someone like me who works in very small teams writing bespoke web apps gets from MVC. For a start, it seems to require us to learn OOP, and commit to a framework, which will add complexity and learning curve. These are general concerns.

    Specifically, let’s consider a web app with a simple sign-in register. The sign-in page would have a submit button to take us to ‘signin.php’ to validate the credentials, and a HREF to ‘register.php’ to show the registration form, which in turn would have a button to submit the form and call ‘saveregdetails.php’.

    With a controller.php, would each of the submissions/HREFs route to controller.php which then looks at the inputs to decide which of the 3 PHP scripts to call? Then would each of these scripts must then refer back to the controller.php with a result so that it may decide which subsequent page (‘view’) to call?

    I understand having HREF and FORM ACTION links directly between the scripts will create a spaghetti network, but when I consider how we used to write C code with libraries of C functions which we call directly without any intermediate controller, the whole MVC thing looks puzzling. I do however understand the benefit of separating the database layer from the UI layer, but here too I find it easier to simply move the CRUD functions into separate scripts and reference them, although I still find returning the cursor for easier to code but see this means the view layers must refer to the column names of the SQL directly. But I find this easier.

    I realise my comment will be mocked but I honestly have a hard time accepting the benefits of MVC given the complexity it adds. I am not convinced by the argument that framework magic overcomes the complexity, especially if that framework takes ages to learn and wield.

    • Hello Bidd Bodd,

      ofc before starting with the MVC you need to have a good knowledge of PHP, especially OOP. OOP was introduced in the ’80s, probably earlier than that decade, then I really believe every software developer must have a good knowledge of that. Without OOP you will be stuck on programming language like PHP, then you can’t ever work on projects based on C++, Java, C#, and others. If you work only with PHP, you’re probably ended to work sometimes with WordPress (brrrrr) that uses OOP. So?

      Then, you said “who writes code in a the most simplest readable way“: OOP does not guarantee that you’ll write a good code, in the same way, that a bunch of functions does not guarantee it. But for sure OOP offers you a really good concept to follow in order to organize your code better and better. Then, design patterns can help you to organize your classes. And so on.

      I won’t mock you or your comment, is not my style, I am really interested in having a good conversation with people about programming, but obviously, I really believe OOP is a must-have for any developer.

      My Simple MVC PHP Framework is just an example, a starter kit or whatever you want to call it, but I want to bring you my personal experience: in 2010 I was looking for a good solution in order to rapidly implement a project for my boss. I didn’t want to reinvent the wheel or start from scratch. I was looking at the developer’s communities, I spoke with colleagues, and finally, I took the decision: I spent my time divided between the Symfony (version 1.4) official documentation and my code editor. I deployed the first version of that project in less than 20 days. I worked on Symfony for years, then I worked on Zend Framework and finally on Laravel (my favorite framework).

      So, in conclusion, if you want my opinion: if you need to work on some professional project, spend some time on Laravel or Symfony is never wasted time, but obviously, you need to have a good knowledge of OOP.

      Hope this can help you.

  5. I get Route does not exists.
    i tried to add my project name into the subfolder constant and it still does not work yet. Kindly help pls

    • Hi O,

      Can you give some other information?

      Which route? Product or the homepage?
      Which PHP version are you using?
      Which web server are you using?

      Any additional information will be helpful to understand your issue.

      Alternately, you can fork the in github and open an issue there.

      Thank you.



  6. How passing multiple path params on my route for example:

    I have created route on web.php
    $routes->add(‘datatable’, new Route(constant(‘URL_SUBFOLDER’) . ‘/datatable’, array(
    ‘controller’ => ‘ReportController’, ‘method’ => ‘actionDatatable’
    ), array(), array(), ”, array(), array(‘GET’)));

    And call route by ajax javascript for serverside but response is:
    Route does not exists.

    what am i doing wrong, thanks for your help

    • Hi Genaro,

      It’s 3 am right now then I will double check tomorrow morning.

      What I noticed is that all your parameters are simple GET parameters and not a route parameters. The question mark is what determine a query parameter, the brackets instead determine a route parameters.

      Then you can pass the parameters as you do and get them with a simple $_GET. And the route is a simple route without any parameter in it.

      About the main issue, route doesn’t exists: please check my other answers, and check my github because other people faced the same issue.

      If you want create something professional, my suggestion is to spend your time on laravel documentation and not on an example like mine.


  7. Add lines on class router.php

    $uri = $_SERVER[‘REQUEST_URI’];

    $signPosition = strpos($uri, “?”);
    if($signPosition ==! false){
    $uri = substr($uri, 0, $signPosition);

    and get params with class Request from packages symfony hhtp

    $matcher = $matcher->match($uri);

  8. Hello , sorry for bothering again, one more question.
    I am new in psr-4 so I watched tutorial on that, they say that name should be the same with class or with directory or with file name. but here you wrote very different :
    “name”: “gmaccario/simple-mvc-php-framework”,
    “description”: “Simple MVC PHP framework: a demonstration of how to create a simple MVC framework in PHP”,
    “autoload”: {
    “psr-4”: {
    “App\\”: “app/”
    so from where you took name : gmaccario? what it means
    and there is no any App directory so why “App\\”: “app/” it should stay there?
    thank you very much

    • Hi Nasib,

      they say that name should be the same with class or with directory or with file name

      Which name are you talking about? Instead of tutorials, I suggest you to read the official documentation for the PSR.

      The name in the composer is the name of the package, here the doc (please search for the name property):

      There is an app folder inside the project, while App (capitalized) is referring to the namespace of the classes. In composer, and more specifically in the PSR4 autoloading, that means that the namespace App is referring to the classes stored in the app folder.

      Please let me know if my answer is satisfying your questions.