Creating a PHP website using MVC – 4 Creating Model class

The Model class we create is the base class for all Models and it contains some common functions like getAll(), getOne(), getById(), search(), count() etc. These static methods will also be available for all other Models extending the base Model class. The model creates instances of itself for each result item for the query. i.e., if we call Book::getAll() we will get an array consisting of Book instances.

We might need to define item specific methods and properties into the classes though. In any of the inherited model classes don’t need to call any database calls directly. The model base class does this behind. When we call the method save() on an instance, it will get updated in the database and if it is not found in the database an entry is created.

It will be easy to understand if we look into an example. Create Model.class.php in the lib folder.

<?php
/**
 * Model base class
 */
class Model {
    
    protected static $tableName = '';
    protected static $primaryKey = '';
    protected $columns;
    
 function __construct() {
  $this->columns = array();
 }
    
    function setColumnValue($column,$value){
        $this->columns[$column] = $value;
    }
    
    function getColumnValue($column){
        return $this->columns[$column];
    }
    /**
     * Save or update the item data in database
     */
    function save(){
        $class = get_called_class();
        $query =  "REPLACE INTO " . static::$tableName . " (" . implode(",", array_keys($this->columns)) . ") VALUES(";
        $keys = array();
        foreach ($this->columns as $key => $value) {
            $keys[":".$key] = $value;
        }
        $query .= implode(",", array_keys($keys)).")";
        $db = Database::getInstance();
        $s = $db->getPreparedStatment($query);
        $s->execute($keys);
    }
    
    /**
     * Delete this item data from database
     */
    function delete(){
        $class = get_called_class();
        $query = "DELETE FROM " . static::$tableName . " WHERE ".static::$primaryKey."=:id LIMIT 1";
        $db = Database::getInstance();
        $s = $db->getPreparedStatment($query);
        $s->execute(array(':id'=>$this->columns[static::$primaryKey]));
    }
    
    /**
     * Create an instance of this Model from the database row
     */
    function createFromDb($column){
        foreach ($column as $key => $value) {
            $this->columns[$key] = $value;
        }
    }
    
    /**
     * Get all items
     * Conditions are combined by logical AND
     * @example getAll(array(name=>'Bond',job=>'artist'),'age DESC',0,25) converts to SELECT * FROM TABLE WHERE name='Bond' AND job='artist' ORDER BY age DESC LIMIT 0,25
     */
    static function getAll($condition=array(),$order=NULL,$startIndex=NULL,$count=NULL){
        $query = "SELECT * FROM " . static::$tableName;
        if(!empty($condition)){
            $query .= " WHERE ";
            foreach ($condition as $key => $value) {
                $query .= $key . "=:".$key." AND ";
            }
        }
        $query = rtrim($query,' AND ');
        if($order){
            $query .= " ORDER BY " . $order;
        }
        if($startIndex !== NULL){
            $query .= " LIMIT " . $startIndex;
            if($count){
                $query .= "," . $count;
            }
        }
        return self::get($query,$condition);
    }
    
    /**
     * Pass a custom query and condition
     * @example get('SELECT * FROM TABLE WHERE name=:user OR age<:age',array(name=>'Bond',age=>25))
     */
    static function get($query,$condition=array()){
        $db = Database::getInstance();
        $s = $db->getPreparedStatment($query);
        foreach ($condition as $key => $value) {
            $condition[':'.$key] = $value;
            unset($condition[$key]);
        }
        $s->execute($condition);
        $result = $s->fetchAll(PDO::FETCH_ASSOC);
        $collection = array();
        $className = get_called_class();
        foreach($result as $row){
            $item = new $className();
            $item->createFromDb($row);
            array_push($collection,$item);
        }
        return $collection;
    }
    
    /**
     * Get a single item
     */
    static function getOne($condition=array(),$order=NULL,$startIndex=NULL){
        $query = "SELECT * FROM " . static::$tableName;
        if(!empty($condition)){
            $query .= " WHERE ";
            foreach ($condition as $key => $value) {
                $query .= $key . "=:".$key." AND ";
            }
        }
        $query = rtrim($query,' AND ');
        if($order){
            $query .= " ORDER BY " . $order;
        }
        if($startIndex !== NULL){
            $query .= " LIMIT " . $startIndex . ",1";
        }
        $db = Database::getInstance();
        $s = $db->getPreparedStatment($query);
        foreach ($condition as $key => $value) {
            $condition[':'.$key] = $value;
            unset($condition[$key]);
        }
        $s->execute($condition);
        $row = $s->fetch(PDO::FETCH_ASSOC);
        $className = get_called_class();
        $item = new $className();
        $item->createFromDb($row);
        return $item;
    }
    
    /**
     * Get an item by the primarykey
     */
    static function getByPrimaryKey($value){
        $condition = array();
        $condition[static::$primaryKey] = $value;
        return self::getOne($condition);
    }
    
    /**
     * Get the number of items
     */
    static function getCount($condition=array()){
       $query = "SELECT COUNT(*) FROM " . static::$tableName;
        if(!empty($condition)){
            $query .= " WHERE ";
            foreach ($condition as $key => $value) {
                $query .= $key . "=:".$key." AND ";
            }
        }
        $query = rtrim($query,' AND ');
        $db = Database::getInstance();
        $s = $db->getPreparedStatment($query);
        foreach ($condition as $key => $value) {
            $condition[':'.$key] = $value;
            unset($condition[$key]);
        }
        $s->execute($condition);
        $countArr = $s->fetch();
        return $countArr[0];
    }
    
}

There are two static variables for storing the table name and the primary key. In the construct method, we define an array. This array contains all the column values for the Model.

Also Read:   Creating a PHP website using MVC – 2 Creating the Controller and Template

The save method runs the sql query ‘REPLACE INTO..’ which creates an entry if there is no entry for the current primary key and if the primary key is present, it updates the corresponding table.

The delete method deletes the current Model entry from the database.

The createFromdb() method is nothing but populates the columns array with its column values from the database row.

There are some static methods getAll(), getOne() etc. These are used to get a collection of Items or a single one according to the passed conditions. Note that we are using php late static binding. If we use the self keyword instead of static, we only get the empty strings we declared on the top of the Model class. So by using static keyword, we get the values which are declared in its extended classes.

Now we look into creating a User Model by extending the Model base class. Create a new folder called ‘model’ inside the ‘common’ folder. Inside the model folder, create a new file called User.class.php.

<?php
/**
 * User Model
 */
class User extends Model {
 
    protected static $tableName = TABLE_USERS;
    protected static $primaryKey = 'id';
    
    const PRIV_ADMINISTRATOR = 1;
    const PRIV_MODERATOR= 2;
    const PRIV_EDITOR= 3;
    const PRIV_MEMBER= 8;
    const PRIV_GUEST= 99;
    
    
    function setId($value){
        $this->setColumnValue('id', $value);
    }
    function getId(){
        return $this->getColumnValue('id');
    }
    
    function setUsername($value){
        $this->setColumnValue('username', $value);
    }
    function getUsername(){
        return $this->getColumnValue('username');
    }
    
    function setPassword($value){
        $this->setColumnValue('password', $value);
    }
    function getPassword(){
        return $this->getColumnValue('password');
    }
    
    function setEmail($value){
        $this->setColumnValue('email', $value);
    }
    function getEmail(){
        return $this->getColumnValue('email');
    }
    
    function setFullname($value){
        $this->setColumnValue('fullname', $value);
    }
    function getFullname(){
        return $this->getColumnValue('fullname');
    }
    
    function setPrivilege($value){
        $this->setColumnValue('privilege', $value);
    }
    function getPrivilege(){
        return $this->getColumnValue('privilege');
    }
}

First we set the tableName and the primary key.  There are some constants defined which are specific to users. Other than setting and getting column values and variables, there is nothing in the User Model. If we need to add some things like login, logout we can do it here in the User Model.

Also Read:   Multiscreen development in phaser 2.4.2

When you run this, you will get an error which says the User class not found. This is because of the autoloader. It needs to be changed to load the classes from common/model folder.

function __autoload($className){
    $paths = array(
        ROOT."/lib/",
        ROOT."/site/controller/",
        ROOT."/admin/controller/",
        ROOT."/common/",
        ROOT."/common/model/"
    );
    foreach($paths as $path){
        if(file_exists($path.$className.".class.php")){
            require_once($path.$className.".class.php");
            break;
        }
    }
}

That’s all about Model. If you are following along the series, in the second part of this tutorial series we already created an IndexController class. In the index method, we just passed the name ‘James Bond’ to the template.

Try this code instead and you will get a better understanding.

function index(){
        $u = User::getOne(array('username'=>'admin')); //Get one user with username==admin
        $this->setView('', 'main');
        $this->setVariable('testVar', $u->getFullname());
    }

You can see the output like,

mvc-screenshot

That concludes our mvc tutorial series basics. Later we might dig into this and add some more features.

Download the full source code here.

[Total: 0    Average: 0/5]
  • Jim Mc

    Let me see if I can explain what I am seeing here, a bit clearer. I must admit, that the getUrl() function is missing from my functions.php, why I do not know, brain cloud perhaps. That said, if I send a request to veiw category 4 thus: category/category_4, what happens is, I get the correct view which would be category.php, with the category shown being 4. So far so good, however, when additional requests are sent for css, js, images, etc., for the page, the request is sent with category as part of the url: e.g. if the path should be site/css/someCssFile.css, it comes out: site/category/css/… what I am getting at here, is if there is a slash following the “controller” part e.g. category/… category gets added to the url. it is simple to explode “category/” to just category… and what comes after is an action with a parameter e.g. “category_4” thus controller/action is correct. As a workaround, I coded absolute urls to included files (css, js, etc.) All well and good, however ALL other relative paths on the page have category as part of the path, even though the relative path of the link is ./site/css/… or site/css or simply home, the request is sent as category/home… perhaps the funniest is category/addToCart… or if coded with controller in the link, category/cart/addToCart.

    On the other hand, if these requests are sent as category_4, or addToCart (product id is sent via post from the form) with no slashes in the request, everything is fine…. (the four is popped of with an explode(‘_’, ….). This problem existed from the beginning, when the request was sent car/viewCar which placed car/ in the path…. e.g. cannot send a controller/action type request… I have the application working, modified of course.

  • Vincent acent

    Is it a good practice to have use your database that way? I see some coupled class design here. If database class change for some reason, the model class will also change (for example, the database class’s name change). What do you think?

    • Vinod

      I’d say it is not a perfect practice if you change your database classes in the middle. I wanted to show how to quickly prototype something without going into creating a very flexible framework. You could create Database interfaces or something like that to implement different types of databases, that way you can de-couple the Model and its dependency with the Database.

      • Vincent acent

        Thanks for the answer. And don’t forget to inject the database object to the model. By the way, your model is based on active record pattern right?

      • Vinod

        Yes. The model instance represents a single row in the table.
        I’ll probably post an updated approach to this when I am not too busy.

      • Vincent acent

        Whoa an updated one? Cool. We are looking forward to it. Anyway, i have a question about this AR pattern. How do we model relationship between table in AR pattern? I have hard time trying to design this relationship. For example, i have users table and level table. User has many to one relationship with the level table. To model the relationships, i just created the row from the level table and inject it to the corresponds row from the users table. What do you think? Here is my code:

        class Level {
        //methods and properties
        public function getLevelName() {
        //return level name such as ‘Admin’, ‘Employer’, etc
        }
        }
        class User {
        private $level;
        public function __construct(Level $level) {
        $this->level = $level;
        }
        public function getLevel() {
        return $this->level->getLevelName();
        }
        }
        $level = new Level();
        $currentUser = new User($level);

      • Vinod

        For your use case represented, I believe you can use the built-in privilege system of User.
        But for your many-to-one pattern.
        I used primary keys for every model. So your level table should have one. and it will have columns – id,userId,levelName where id is primary key auto increment.
        Now it does behave like all other models. You can ask
        $Level::getAllUsersByLevel(“employee”)

        You can do level setup for a user like this
        $level = new Level();
        $level->setLevelName(“employee”);
        $level->setUserId($currentUser->getId());
        $level->save()

        Look that the User model has no dependency on the Level. Everything can be added like this later.
        If you access the Level everytime you access the User, you can include a getter for that it in the user class. Like this

        function getLevel(){
        if(!$level){
        $level = Level::getByUserId($this->id);
        }
        return $level;
        }

      • Vincent acent

        Whoa thanks!. That helped me a lot. I have insight now about how to implement one-to-many relationship. Oh yeah, what do you mean by built-in privilege system of user?

        By the way, i have new question (yeah, because i am still new in this kind of thing). For example, i have account and employer model. Employer has only one account. If employer created, the account will be created for that employer too. So there will be no account without the employer created first.
        The question is, what is the best way to implement this kind of relationship? (which i am sure is the one-to-one relationship) I have several solutions in my mind, but i could use some ideas from the others. Here is my solutions and my thoughts about it:
        1. Implement that logic in employer’s create() method. In employer’s create() method, it will create employer first, then call account’s create() method as the final step. If i use this solution, i need to override create method that inherited from the parent class (which basically the same as your model class) and i don’t think the overriding is a good idea because it could violates the LSP.
        2. Implement this logic in controller corresponds to the employer model. Controller will call the employer’s create method first and later call the account ‘s create method. But i am don’t think the controller is the one that responsible to handle this logic. Simply because it add new responsibility to controller, which is to make sure that the account is created only if employer has been created.

        I am gladly to hear your opinion of this particular problem i have. Thanks in advance.

      • Vinod

        The built-in privilege system is the one that used to set the type of a User. Check the getPrivilege() method. You can write a method in the User Model like this:

        function hasPrivilege($privilegeId){
        $currentPrivilege = $this->getPrivilege();
        if($currentPrivilege == 0){
        return FALSE;
        }
        if($currentPrivilege hasPrivilege(User::PRIV_EDITOR)){
        //if user is atleast an editor, then edit page
        }else{
        echo “You dont have access”;
        }

        For your second question, I will choose the solution 2. Because I believe Models should not contain any logic. It is the controller’s responsiblity to process things. Later at a date, when you want to have both things, i.e., with and without accout employer, it will be so easy to messup all the things by editing the Model.
        In MVC – Model is simply the data, View is for rendering and Controller is responsible for all other things.

      • Vincent acent

        Finally someone who can give some insight about this thing. Thanks a lot Vinod!! Hope you have time soon to update your code!!

  • The getUrl('car','view_all_cars') will return a string like this,

    http://localhost/mvc_blueprint/car/view_all_cars

  • Hi, You have to have a controller for every section for your page, For eg: If you have a section for cars, there should be a controller called CarController.class.php.

    In that controller, you can have functions for add_car, view_all_cars etc.

    That functions will load the templates from the view. So you can load any template from the view folder by this->setView('folder','file')

    You can implement navigation links to another page using the method getUrl() which is inside the functions.php.

    getUrl('car','view_all_cars') will give you an http link to the page. Hope you understood.

  • Anonymous

    Really? No reply? That's messed up

  • Anonymous

    well figured out this much: the link written as ./car routes the request through the root/index.php, looks for the CarController.class.php, with a default action of index, then serves up the car.php in the view folder. So the question now becomes, how to send an action other than default, to the same controller, or another controller…

  • Anonymous

    Nice little tutorial, however–it is pretty much useless, without some direction as to linking various pages within say for instance: a second page in the site area, accessed from the main navigation from the main.php page… that should make sense… Sure I can type in the url http://locolhost/mvc_blueprint/car/get, and the breadcrumbs say what they are supposed to say, however try that with another page that actually exists in view… yeah the page will display in the browser, but all the includes (scripts, styles) are tore up from the floor up, as there is no folder called 'http://localhost/mvc_blueprint/car/site/css/something.css…' I am certain I am doing something wrong, however that is not the point. I also noticed that in some of the paths (when stepping through with a debugger) it shows folder//file… (doubled slash) that is generated in the template.class with the line $filename = ROOT.$area."/view/".$this->folder."/".$this->file.".php";. Following this tutorial with no modifications, there is no folder in view, just a file called main.php… the little tidbit on navigating within the site itself is blatantly missing from every tutorial on the subject I have run across… it certainly is not in any of the text books I have here…

  • Yeah, the Model class depends on the tableName and primary keys. The relations such as 'UserPosts' is a standalone class which can call the db and populate itself. I did these things in the controller classes.

    Sure there are other ways to make it highly flexible. But I tried to limit lots of abstracts and inheritances.

  • You can rewrite the getAll method. I think replacing the '=' with 'LIKE' should work.

  • Hello. Nice tutorial there! But why don't you use the dependency injection technique for the database? Your class now is tightly coupled with the Database class. I have another question though. How do you model the table if the table has relations with other table? For example user has many posts. How i am gonna model that relations?

  • I want to make a find function (with like) but cannot do the prepaired statement like GetAll.. can you help?

  • Please check the source, your problem may have with the PHP not loading the User class. In the index.php file, there is an autoload function.

  • Fatal error: Class 'User' not found in C:wampwwwEat247sitecontrollerIndexController.class.php on line 17
    This is the error i get when i run the last part of this great tutorial. Please help me out.

  • Such a comprehensive tutorial. Thank you so much. I started out on your code as the base, added more views, some crud etd. but I havent been able to get a user registration / roles module working properly. Could you please help me out?

    Regards,
    https://twitter.com/nayanmedhi

  • Not much changed, but I've replaced the query "REPLACE INTO.." with "INSERT ..UPDATE ..ON DUPLICATE KEY UPDATE..

  • Anonymous

    After a long time trying to understand what exactly is a model, your article helped me understand it clearly. Thanks. But, I have one question though, has the way you define a model changed after writting this article and now? if so, then let us know pls.