Tools Blog Learn Quizzes Smile API Log In / Sign Up
Tools Blog Learn Quizzes Smile API Log In / Sign Up
« Return to the tutorials list
We have updated our privacy policy to let you know that we use cookies to personalise content and ads. We also use cookies to analyse our traffic and we share information about your use of our site and application with our advertising and analytics partners. By using this website or our application you agree to our use of cookies. Learn more about the way this website uses cookies or remove this message.

Develop your own MVC application in PHP

October 14, 2016 Difficulty: 35 / 50 Tweet
railroad-traffic-controller-lights

In this tutorial I will show you how easy it is to create your own MVC application in PHP without using a framework. MVC or model-view-controller is an architectural pattern which is a fancy phrase for 'a method of structuring your application' so that you can write reusable code and maintainable code.

While the model handles data, the controller is responsible for directing the application flow through interacting with the model and sending data to the view. In my view, an MVC is not complete if it can't handle more than one view, possibly even at the same time (Ex: normal request versus Ajax).

In other words ...

Model - "translates" or "models" any structured object in the application to any persistent storage (database, file, whatever).

Controller - Answers one question - What am I doing? The controller does what needs to be done affecting as many "models" as needed ... without necessarily knowing about how the models are being "stored" in the database. The controller would also feed data (coming from the model) to the view, but again, the controller knows nothing about how the view will show that data.
Example: Send an invoice to a user and and mark that as "pending payment".
The model is going to actually mark the invoice, but it is the controller who will tell it to do that.


Example: Display the users invoices
The controller would get a set of invoices from the model and push a data set to the view, regardless of what the view will actually do to that data... show it as an HTML table or as a JSON object.

View - This answers only one question ... what do I show to the user ? How do I render the result ?

File structure

For the purpose of this tiny project you will need to have some basic knowledge of Composer - the PHP package manager and be able to execute basic CLI commands.

Our file structure will be like this:

  • public
    Stores one index.php file - the entry point of our app and various other static assets like CSS, images, etc ... This should be the only folder that is accessible from the web... all other PHP files shouldn't exist in the publicly available virtual host folder.
  • ../app Our main application folder.
  • ../app/bootstrap.php A file that initializes the whole process and can in turn reference various configuration files or can store the configuration in it. Also, it can call a "routes" file or it can store the routes in it. For the purpose of this tutorial we won't have external configuration files or routes, we will put them both in bootstrap.php.
  • ../app/Controllers/* This is where we store our controller files. I am using a naming convention - my files will be formed from [ACTION] + [CONTROLLER]. Like this: HomeController.php, RegistrationController.php, etc...
  • ../app/Models/* This is where we store our model files. I am using the same convention as above, but this time file names relate to objects I use in the app - UsersModel.php, InvoicesModel.php, etc...
  • ../app/Views/* This is where we display stuff. For the purpose of this I only have one file here -> default.php ... which will list whatever I send it from the controller.
  • ../app/vendor/* The default folder for composer included packages. We will include "guzzlehttp/guzzle": "^6.2" ... which we will use to fetch data from remote API endpoints. However, you can just include whatever packages you want/need in your app.

Let's dive into it

Below is our bootstrap.php file. It has the following role(s):

  • Define a few globally available configuration things
  • Auto-load our files
  • Route our app to the appropriate Controller

    
        namespace Codepunker\InvoicesApp;

        define('BASE', __DIR__);
        define('BASE_URI', 'http://InvoicesApp.co');
        define('ASSETS_URI', 'http://InvoicesApp.co');
    
        //load all our classes as we instantiate them following the guidance of the name spaces.
        spl_autoload_register(function($class) {
            require_once __DIR__ . str_replace(['Codepunker\\InvoicesApp', '\\'], ['', '/'], $class) . '.php';
        });
        
        // include the composer autoloader
        require 'vendor/autoload.php';

        $req = $_SERVER['REQUEST_URI'];
        $qs = $_SERVER['QUERY_STRING'];
        
        // this is our pseudo-router ... we don't have a set of classes that interpret requests so we just rely on the plain old super globals

        if(!empty($qs)) {
            $req = substr($req, 0, strpos($req, '?')); // this is to be expanded by you guys...
        }

        switch ($req) {
            case ('/'):
                $buff = new Controllers\HomeController([ 
                    BASE . '/Views/default.php'
                ]);
                break;
            case ('/about'):
                $buff = new Controllers\AboutController([ 
                    BASE . '/Views/default.php'
                ]);
                break;
            default:
                $buff = new Controllers\DefaultController([ 
                    BASE . '/Views/default.php'
                ]);
                break;
        }

        echo $buff->out();        
    

As you've probably noticed we take into account only two routes: '/' and '/about', the third one is just a default which is beyond the scope of this... If you don't want to create it, just kill the script if none of your routes are hit.

Also, from the bootstrap file you can see there's only one view and we pass that to the controller as an array - you can expand on it and add as many view files as you want, depending on how you like to structure your templates.

Create a base controller which will act as a "template" for all other controllers

This base controller I'm proposing is basically a "template" that will be used by the other controllers, thus forcing us to respect a particular standard we've created.

    
    namespace Codepunker\InvoicesApp\Controllers;

    /**
    * Our base controller
    */
    class BaseController
    {
        protected $data;
        private $content;

        public function __construct(array $views)
        {
            $this->loadView($views);
        }

        /**
         * @return array data passed to the view
         */
        protected function getData(): array {
            return $this->data;
        }

        /**
         *  
         * @param  array  $views an array of views to be loaded
         * @return string the compiled view
         */
        protected function loadView(array $views) {
            ob_start();
                $data = $this->getData(); //data needed in the view
                foreach ($views as $k => $view) {
                    include $view;
                }
                $content = ob_get_contents();
            ob_end_clean();
            $this->content = $content;
        }

        /**
         * get's the buffer and returns it
         * @return string the buffer
         */
        public function out() : string {
            return $this->content;
        }
    }
    

Next we will create a controller for the first route in bootstrap.php -> "/". Our home controller will look something like this.

    
    namespace Codepunker\InvoicesApp\Controllers;

    use \GuzzleHttp\Client as HttpClient;
    use Codepunker\InvoicesApp\Models\InvoicesModel as InvoicesModel;

    //shows some data on the homepage
    class HomeController extends BaseController
    {
        public function __construct(array $views)
        {
            $this->getHomePageData();
            parent::__construct($views);
        }
    
        //retrieve data from remote endpoint and store it using the model
        private function getHomePageData()
        {
            $client = new HttpClient();
            try {
                $res = $client->request('GET', 'https://www.remoteendpoint.com', [
                    'headers' => [
                        'User-Agent' => 'testing/1.0',
                        'Accept'     => 'application/json',
                    ]
                ]);
                
                $data = json_decode( $res->getBody() );
                $model = new InvoicesModel($data);
                $model->store();
                return $data;
            } catch (Exception $e) {
                echo "Sorry for the inconvenience";
            }
        }
    }
    

Now our view will be just a basic listing.

    
        foreach ($data as $k => $v) :
            //show me...
        endforeach;
    

The model

    
    namespace Codepunker\InvoicesApp\Models;

    //shows some data on the homepage
    class HomeController extends BaseController
    {
        protected $data;
        protected $table_name = "invoices_table";

        function __construct(array $data)
        {
            $this->data = $data;
        }

        public function store()
        {
            // of course in your app you'll have some sort of DB abstraction layer...
            try{
                $db = new \PDO("mysql:host=localhost;dbname=mydb;charset=utf8","username","pw");
                foreach($this->data as $k=>$v) {
                    $stmt = $db->prepare("INSERT INTO {$this->table_name} VALUES (NULL, :param, :anotherparam)");
                    $stmt->bindParam(':param', $v->param);
                    $stmt->bindParam(':anotherparam', $v->anotherparam);
                    $stmt->execute();
                }
            } catch(PDOException  $e ) {
                echo "Error: ".$e;
            }
        }
    }
    

Hope this tutorial will help demystify the MVC architectural pattern and will allow you to better understand how PHP frameworks are built. Thanks for reading through.

comments powered by Disqus

Better Docs For A Better Web - Mozilla Developer Network