Skip to content

A fully functioning DB application

Rosario Carvello edited this page May 23, 2024 · 46 revisions

Introduction

The purpose of this example is to provide a simple but fully functioning customer management application, and also for exposing all basic concepts of WebMC Framework we discussed right now:

  • Controller
  • Model
  • View
  • Template
  • Placeholders and Blocks
  • OOP
  • MVC Assembly
  • Database and ORM using autogenerated Database Bean

System Requirements

First of all, we need to define the system requirements, they are the following:
CRM User must be able:

  • To store, manage, and retrieve information about customers. The information must be about the name, email, nationality (Italian or guest), and assurance level (low, middle, or high) of any given customer;
  • To browse customers list
  • To select a customer from the list for showing, editing its data or, eventually, delete its record from the DB
  • To select a customer from the list for rapidly sending a mail
  • To add a new customer record to the DB

System Design

We organized the System Design under the following sections:

Database Design

First of all, we need to define the Database Design to store and manage customers. In our system it is very simple, it consists of the single table customer coded below:

--
-- Table `customer`
--
CREATE TABLE `customer` (
  `customer_id` int(11) NOT NULL,
  `name` varchar(45) NOT NULL,
  `email` varchar(100) NOT NULL,
  `nationality` varchar(4) DEFAULT NULL,
  `assurance` int(1) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- PK for table `customer`
--
ALTER TABLE `customer`
  ADD PRIMARY KEY (`customer_id`);

--
-- Unique index for email field
-- 
ALTER TABLE `customer` ADD UNIQUE(`email`);

--
-- AUTO_INCREMENT for table `customer`
--
ALTER TABLE `customer`
  MODIFY `customer_id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;

As you can see in the SQL code above, the customer table stores the name, email, nationality, and assurance level (listed in system requirements). It also defines an incremental primary key, required by DB normalization regarding no duplicate tuples, and a Unique index for the email field (a Non-Functional Requirement). Note that, when building a database, you should always be compliant with DB design principles.

GUI Design

Due to the simplicity of the system, we made a rapid "System Analysis" for establishing its GUI Design capable of covering all previous system requirements. So, we decided to provide two user scenarios, or better two web pages, for the purpose.

  • One for providing the customer's list, from which the user should be able to select a customer for editing it's data or, also, adding a new one.
  • The second for providing the showing or editing of a customer record previously selected, as well as to delete it, or for insert data regarding a new one.
    The following figures show you the GUI for these pages:

CustomersManager APP

The left side showing the GUI for browsing customers with the ability to select a customer record for editing, or sending a mail easily. There is also a function to add a new record. The right side showing the GUI for a customer record. The same GUI can be used for implementing two different operative mode can be done on a customer record. The first, in the upper left, is regarding the editing mode where the user can show and update data, or delete a customer record previously selected. The second, in the bottom right, is regarding the inserting mode when the user needs to add a new customer to DB.

Under a UML point of view, the right side may be considered as a single Use Case, the customer record, that can be extended with two different features: one regarding inserting, other for editing data.

In conclusion, the GUI Design may be defined with the following two GUI templates:

The first template, on the left, is used for implementing the browsing, the second, on the right, for the customer's record. In the last one, both the insert and editing functions are implemented as extensions on a common section showing form fields. Furthermore, all templates are built with a responsive design (look, for example, at the navigation bar) provided by Bootstrap, a wonderful HTML/CSS/JS web framework for designing amazing Web GUI. You may also note like some UI elements, in both templates, are designed to be shown when some circumstances occurred. For example, the red message "no customer" will be shown only when no customers are stored in the DB. Furthermore, the red section "Errors" or buttons regarding form submission will be respectively processed depending, on the occurrence of a DB error or by analyzing if the record is in inserting or editing mode.

Application Design

Finally, we provide the Application Design for organizing system development. We define the following two Web MVC Assemblies for implementing the application :

  • CustomersManager for browsing customers
  • CustomerRecord for customer data manipulation.
    Because the system is very simple we do not have the need for any Subsystems Design

System Implementation

The implementation details of both assemblies will be exposed in the section below

CustomersManager

The following sections contain the template and MVC source code of the CustomersManager assembly.

file templates\customers_manager.html.tpl

<!DOCTYPE html>
<html>
<head>
    <title>Customers Manager</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Bootstrap core CSS -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet"
          media="screen">

    <!-- Fonts Awesome -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.js"></script>
    <![endif]-->
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" 
            data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">CRM Simple</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="#">Home</a></li>
                <li class="active"><a href="{GLOBAL:SITEURL}/customers_manager">Customers</a></li>
            </ul>
        </div>
    </div>
</nav>

<div class="container">
    <div class="row">
        <div class="col col-12">
            <h1>Customers Manager</h1>
            <hr>
            <div class="table-responsive-lg">
                <table class="table table-hover">
                    <thead>
                    <tr>
                        <th scope="col">Actions</th>
                        <th scope="col">Customer name</th>
                        <th scope="col">Email</th>
                    </tr>
                    </thead>
                    <tbody>
                    <!-- BEGIN CustomersList -->
                    <tr>

                        <th scope="row">
                            <a class="btn btn-info" href="customer_record/open/{CustomerID}">
                            <i class="fa fa-edit"></i>&nbsp;Edit</a>
                            <a class="btn btn-warning" href="mailto:{CustomerEmail}"> 
                            <i class="fa fa-address-card-o"></i>&nbsp;Email</a>
                        </th>
                        <td>{CustomerName}</td>
                        <td>{CustomerEmail}</td>
                    </tr>
                    <!-- END CustomersList -->
                    </tbody>
                    <tfoot>
                        <!-- BEGIN NoCustomers -->
                        <tr>
                            <td colspan="3" class="text-danger text-center">
                                No customer
                            </td>
                        </tr>
                        <!-- END NoCustomers -->
                        <tr>
                            <td colspan="3">
                                <a class="btn btn-primary" href="customer_record"><i class="fa fa-plus"></i>&nbsp;Add a new customer</a>
                            </td>
                        </tr>
                    </tfoot>
                </table>
            </div>
        </div>
    </div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js"></script>
</body>
</html>

file controllers\CustomersManager.php

namespace controllers;

use framework\Controller;
use framework\Model;
use framework\View;
use models\CustomersManager as CustomersManagerModel;
use views\CustomersManager as CustomersManagerView;

class CustomersManager extends Controller
{
    protected $view;
    protected $model;

    /**
    * Object constructor.
    *
    * @param View $view
    * @param Model $mode
    */
    public function __construct(View $view=null, Model $model=null)
    {
        $this->view = empty($view) ? $this->getView() : $view;
        $this->model = empty($model) ? $this->getModel() : $model;
        parent::__construct($this->view,$this->model);
    }

    /**
    * Autorun method. Put your code here for running it after object creation.
    * @param mixed|null $parameters Parameters to manage
    *
    */
    protected function autorun($parameters = null)
    {
       $customers = $this->model->getCustomers();
       $this->view->setCustomersBlock($customers);
    }

    /**
    * Inizialize the View by loading static design of /customers_manager.html.tpl
    * managed by views\CustomersManager class
    *
    */
    public function getView()
    {
        $view = new CustomersManagerView("/customers_manager");
        return $view;
    }

    /**
    * Inizialize the Model by loading models\CustomersManager class
    *
    */
    public function getModel()
    {
        $model = new CustomersManagerModel();
        return $model;
    }
}

The controller just initializes View and Model. Then retrieve customers' data (from Model) to be processed by the View.

file models\CustomersManager.php

namespace models;

use framework\Model;

class CustomersManager extends Model
{
    /**
    * Object constructor.
    *
    */
    public function __construct()
    {
        parent::__construct();
    }

    /**
    * Autorun method. Put your code here for running it after object creation.
    * @param mixed|array|null $parameters Additional parameters to manage
    *
    */
    protected function autorun($parameters = null)
    {

    }


    public function getCustomers()
    {
        // Notice: we use PHP HereDoc to specify SQL string
        $this->sql = <<<SQL
        SELECT 
            customer_id as CustomerID,
            name as CustomerName,
            email as CustomerEmail   
        FROM
            customer
        ORDER BY
            name;
SQL;
        $this->updateResultSet();
        // The mysqli result set already has the format:
        // array( array('CustomerID'=>'','CustomerName'=>'','CustomerEmail'=>''),)
        return $this->getResultSet();
    }
}

The Model just implements the necessary SQL statement for retrieving customers from MySQL database.

file views\CustomersManager.php

namespace views;

use framework\View;

class CustomersManager extends View
{

    /**
     * Object constructor.
     *
     * @param string|null $tplName The html template containing the static design.
     */
    public function __construct($tplName = null)
    {
        if (empty($tplName))
            $tplName = "/customers_manager";
        parent::__construct($tplName);
    }

    /**
     * Render CustomersList Block
     *
     * @param \mysqli_result $customers
     * @throws \framework\exceptions\BlockNotFoundException
     * @throws \framework\exceptions\NotInitializedViewException
     * @throws \framework\exceptions\VariableNotFoundException
     */
    public function setCustomersBlock(\mysqli_result $customers)
    {
        if ($customers->num_rows > 0) {
            $this->hide("NoCustomers");
            $this->openBlock("CustomersList");
            while ($customer = $customers->fetch_object()) {
                $this->setVar("CustomerID", $customer->CustomerID);
                $this->setVar("CustomerName", $customer->CustomerName);
                $this->setVar("CustomerEmail", $customer->CustomerEmail);
                $this->parseCurrentBlock();
            }
            $this->setBlock();
        } else {
            $this->hide("CustomersList");
        }
    }
}

The View renders the customer list by processing the template Block CustomersList. If no data are retrieved from Model it shows an information text. To perform this action it uses the block hiding feature

CustomerRecord

In the next section, we show you the codes about record management for editing or inserting customers. The main news is regarding the Model and the auto-generated DataBase Bean class, from the MySQL customer table, we obtained through the ORM engine we previously discussed
The database Bean code is shown below

file models\beans\BeanCustomer.php

/**
 * Class BeanCustomer
 * Bean class for object oriented management of the MySQL table customer
 *
 * Comment of the managed table customer: Not specified.
 *
 * Responsibility:
 *
 *  - provides instance constructor for both managing of a fetched table or for a new row
 *  - provides destructor to automatically close database connection
 *  - defines a set of attributes corresponding to the table fields
 *  - provides setter and getter methods for each attribute
 *  - provides OO methods for simplify DML select, insert, update and delete operations.
 *  - provides a facility for quickly updating a previously fetched row
 *  - provides useful methods to obtain table DDL and the last executed SQL statement
 *  - provides error handling of SQL statement
 *  - uses Camel/Pascal case naming convention for Attributes/Class used for mapping of Fields/Table
 *  - provides useful PHPDOC information about the table, fields, class, attributes and methods.
 *
 * @extends MySqlRecord
 * @implements Bean
 * @filesource BeanCustomer.php
 * @category MySql Database Bean Class
 * @package models/bean
 * @author Rosario Carvello <rosario.carvello@gmail.com>
 * @version GIT:v1.0.0
 * @note  This is an auto generated PHP class builded with MVCMySqlReflection, a small code generation engine extracted from the author's personal MVC Framework.
 * @copyright (c) 2016 Rosario Carvello <rosario.carvello@gmail.com> - All rights reserved. See License.txt file
 * @license BSD
 * @license https://opensource.org/licenses/BSD-3-Clause This software is distributed under BSD Public License.
*/
namespace models\beans;
use framework\MySqlRecord;
use framework\Bean;

class BeanCustomer extends MySqlRecord implements Bean
{
    /**
     * A control attribute for the update operation.
     * @note An instance fetched from db is allowed to run the update operation.
     *       A new instance (not fetched from db) is allowed only to run the insert operation but,
     *       after running insertion, the instance is automatically allowed to run update operation.
     * @var bool
     */
    private $allowUpdate = false;

    /**
     * Class attribute for mapping the primary key customer_id of table customer
     *
     * Comment for field customer_id: Not specified<br>
     * @var int $customerId
     */
    private $customerId;

    /**
     * A class attribute for evaluating if the table has an autoincrement primary key
     * @var bool $isPkAutoIncrement
     */
    private $isPkAutoIncrement = true;

    /**
     * Class attribute for mapping table field name
     *
     * Comment for field name: Not specified.<br>
     * Field information:
     *  - Data type: varchar(45)
     *  - Null : NO
     *  - DB Index: 
     *  - Default: 
     *  - Extra:  
     * @var string $name
     */
    private $name;

    /**
     * Class attribute for mapping table field email
     *
     * Comment for field email: Not specified.<br>
     * Field information:
     *  - Data type: varchar(100)
     *  - Null : NO
     *  - DB Index: 
     *  - Default: 
     *  - Extra:  
     * @var string $email
     */
    private $email;

    /**
     * Class attribute for mapping table field nationality
     *
     * Comment for field nationality: Not specified.<br>
     * Field information:
     *  - Data type: varchar(4)
     *  - Null : YES
     *  - DB Index: 
     *  - Default: 
     *  - Extra:  
     * @var string $nationality
     */
    private $nationality;

    /**
     * Class attribute for mapping table field assurance
     *
     * Comment for field assurance: Not specified.<br>
     * Field information:
     *  - Data type: int(1)
     *  - Null : YES
     *  - DB Index: 
     *  - Default: 
     *  - Extra:  
     * @var int $assurance
     */
    private $assurance;

    /**
     * Class attribute for storing the SQL DDL of table customer
     * @var string base64 encoded $ddl
     */
    private $ddl = "Q1JFQVRFIFRBQkxFIGBjdXN0b21lcmAgKAogIGBjdXN0b21lcl9pZGAgaW50KDExKSBOT1QgTlVMTCBBVVRPX0lOQ1JFTUVOVCwKICBgbmFtZWAgdmFyY2hhcig0NSkgTk9UIE5VTEwsCiAgYGVtYWlsYCB2YXJjaGFyKDEwMCkgTk9UIE5VTEwsCiAgYG5hdGlvbmFsaXR5YCB2YXJjaGFyKDQpIERFRkFVTFQgTlVMTCwKICBgYXNzdXJhbmNlYCBpbnQoMSkgREVGQVVMVCBOVUxMLAogIFBSSU1BUlkgS0VZIChgY3VzdG9tZXJfaWRgKQopIEVOR0lORT1Jbm5vREIgREVGQVVMVCBDSEFSU0VUPXV0Zjg=";

    /**
     * setCustomerId Sets the class attribute customerId with a given value
     *
     * The attribute customerId maps the field customer_id defined as int(11).<br>
     * Comment for field customer_id: Not specified.<br>
     * @param int $customerId
     * @category Modifier
     */
    public function setCustomerId($customerId)
    {
        // $this->customerId = (int)$customerId;
		$this->customerId = (int) $this->real_escape_string($customerId);
    }

    /**
     * setName Sets the class attribute name with a given value
     *
     * The attribute name maps the field name defined as varchar(45).<br>
     * Comment for field name: Not specified.<br>
     * @param string $name
     * @category Modifier
     */
    public function setName($name)
    {
        // $this->name = (string)$name;
		$this->name = (string) $this->real_escape_string($name);
    }

    /**
     * setEmail Sets the class attribute email with a given value
     *
     * The attribute email maps the field email defined as varchar(100).<br>
     * Comment for field email: Not specified.<br>
     * @param string $email
     * @category Modifier
     */
    public function setEmail($email)
    {
        // $this->email = (string)$email;
		$this->email = (string) $this->real_escape_string($email);
    }

    /**
     * setNationality Sets the class attribute nationality with a given value
     *
     * The attribute nationality maps the field nationality defined as varchar(4).<br>
     * Comment for field nationality: Not specified.<br>
     * @param string $nationality
     * @category Modifier
     */
    public function setNationality($nationality)
    {
        // $this->nationality = (string)$nationality;
		$this->nationality = (string) $this->real_escape_string($nationality);
    }

    /**
     * setAssurance Sets the class attribute assurance with a given value
     *
     * The attribute assurance maps the field assurance defined as int(1).<br>
     * Comment for field assurance: Not specified.<br>
     * @param int $assurance
     * @category Modifier
     */
    public function setAssurance($assurance)
    {
        // $this->assurance = (int)$assurance;
		$this->assurance = (int) $this->real_escape_string($assurance);
    }

    /**
     * getCustomerId gets the class attribute customerId value
     *
     * The attribute customerId maps the field customer_id defined as int(11).<br>
     * Comment for field customer_id: Not specified.
     * @return int $customerId
     * @category Accessor of $customerId
     */
    public function getCustomerId()
    {
        return $this->customerId;
    }

    /**
     * getName gets the class attribute name value
     *
     * The attribute name maps the field name defined as varchar(45).<br>
     * Comment for field name: Not specified.
     * @return string $name
     * @category Accessor of $name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * getEmail gets the class attribute email value
     *
     * The attribute email maps the field email defined as varchar(100).<br>
     * Comment for field email: Not specified.
     * @return string $email
     * @category Accessor of $email
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * getNationality gets the class attribute nationality value
     *
     * The attribute nationality maps the field nationality defined as varchar(4).<br>
     * Comment for field nationality: Not specified.
     * @return string $nationality
     * @category Accessor of $nationality
     */
    public function getNationality()
    {
        return $this->nationality;
    }

    /**
     * getAssurance gets the class attribute assurance value
     *
     * The attribute assurance maps the field assurance defined as int(1).<br>
     * Comment for field assurance: Not specified.
     * @return int $assurance
     * @category Accessor of $assurance
     */
    public function getAssurance()
    {
        return $this->assurance;
    }

    /**
     * Gets DDL SQL code of the table customer
     * @return string
     * @category Accessor
     */
    public function getDdl()
    {
        return base64_decode($this->ddl);
    }

    /**
    * Gets the name of the managed table
    * @return string
    * @category Accessor
    */
    public function getTableName()
    {
        return "customer";
    }

    /**
     * The BeanCustomer constructor
     *
     * It creates and initializes an object in two way:
     *  - with null (not fetched) data if none $customerId is given.
     *  - with a fetched data row from the table customer having customer_id=$customerId
     * @param int $customerId. If omitted an empty (not fetched) instance is created.
     * @return BeanCustomer Object
     */
    public function __construct($customerId = null)
    {
        // $this->connect(DBHOST,DBUSER,DBPASSWORD,DBNAME,DBPORT);
        parent::__construct();
        if (!empty($customerId)) {
            $this->select($customerId);
        }
    }

    /**
     * The implicit destructor
     */
    public function __destruct()
    {
        $this->close();
    }

    /**
     * Explicit destructor. It calls the implicit destructor automatically.
     */
    public function close()
    {
        // unset($this);
    }

    /**
     * Fetchs a table row of customer into the object.
     *
     * Fetched table fields values are assigned to class attributes and they can be managed by using
     * the accessors/modifiers methods of the class.
     * @param int $customerId the primary key customer_id value of table customer which identifies the row to select.
     * @return int affected selected row
     * @category DML
     */
    public function select($customerId)
    {
        $sql =  "SELECT * FROM customer WHERE customer_id={$this->parseValue($customerId,'int')}";
        $this->resetLastSqlError();
        $result =  $this->query($sql);
        $this->resultSet=$result;
        $this->lastSql = $sql;
        if ($result){
            $rowObject = $result->fetch_object();
            @$this->customerId = (integer)$rowObject->customer_id;
            @$this->name = $this->replaceAposBackSlash($rowObject->name);
            @$this->email = $this->replaceAposBackSlash($rowObject->email);
            @$this->nationality = $this->replaceAposBackSlash($rowObject->nationality);
            @$this->assurance = (integer)$rowObject->assurance;
            $this->allowUpdate = true;
        } else {
            $this->lastSqlError = $this->sqlstate . " - ". $this->error;
        }
        return $this->affected_rows;
    }

    /**
     * Deletes a specific row from the table customer
     * @param int $customerId the primary key customer_id value of table customer which identifies the row to delete.
     * @return int affected deleted row
     * @category DML
     */
    public function delete($customerId)
    {
        $sql = "DELETE FROM customer WHERE customer_id={$this->parseValue($customerId,'int')}";
        $this->resetLastSqlError();
        $result = $this->query($sql);
        $this->lastSql = $sql;
        if (!$result) {
            $this->lastSqlError = $this->sqlstate . " - ". $this->error;
        }
        return $this->affected_rows;
    }

    /**
     * Insert the current object into a new table row of customer
     *
     * All class attributes values defined for mapping all table fields are automatically used during inserting
     * @return mixed MySQL insert result
     * @category DML
     */
    public function insert()
    {
        if ($this->isPkAutoIncrement) {
            $this->customerId = "";
        }
        // $constants = get_defined_constants();
        $sql = <<< SQL
            INSERT INTO customer
            (name,email,nationality,assurance)
            VALUES(
			{$this->parseValue($this->name,'notNumber')},
			{$this->parseValue($this->email,'notNumber')},
			{$this->parseValue($this->nationality,'notNumber')},
			{$this->parseValue($this->assurance)})
SQL;
        $this->resetLastSqlError();
        $result = $this->query($sql);
        $this->lastSql = $sql;
        if (!$result) {
            $this->lastSqlError = $this->sqlstate . " - ". $this->error;
        } else {
            $this->allowUpdate = true;
            if ($this->isPkAutoIncrement) {
                $this->customerId = $this->insert_id;
            }
        }
        return $result;
    }

    /**
     * Updates a specific row from the table customer with the values of the current object.
     *
     * All class attribute values defined for mapping all table fields are automatically used during updating of selected row.<br>
     * Null values are used for all attributes not previously setted.
     * @param int $customerId the primary key customer_id value of table customer which identifies the row to update.
     * @return mixed MySQL update result
     * @category DML
     */
    public function update($customerId)
    {
        // $constants = get_defined_constants();
        if ($this->allowUpdate) {
            $sql = <<< SQL
            UPDATE
                customer
            SET 
				name={$this->parseValue($this->name,'notNumber')},
				email={$this->parseValue($this->email,'notNumber')},
				nationality={$this->parseValue($this->nationality,'notNumber')},
				assurance={$this->parseValue($this->assurance)}
            WHERE
                customer_id={$this->parseValue($customerId,'int')}
SQL;
            $this->resetLastSqlError();
            $result = $this->query($sql);
            if (!$result) {
                $this->lastSqlError = $this->sqlstate . " - ". $this->error;
            } else {
                $this->select($customerId);
                $this->lastSql = $sql;
                return $result;
            }
        } else {
            return false;
        }
    }

    /**
     * Facility for updating a row of customer previously loaded.
     *
     * All class attribute values defined for mapping all table fields are automatically used during updating.
     * @category DML Helper
     * @return mixed MySQLi update result
     */
    public function updateCurrent()
    {
        if ($this->customerId != "") {
            return $this->update($this->customerId);
        } else {
            return false;
        }
    }

}
?>

As you can note, the class provides you with all the necessary methods for customer table management. It also extends the framework\MySqlRecord,and implements the framework\Bean interface, which are both frameworks facilities for interacting with MySQL, as well as, for classifying a Database Bean as a Model type could be consumed by an MVC Assembly.
For these reasons, as you can easily note in the following code regarding the models\CostomerRecord.php, we designed it, simply by extending the previously showed database Bean, and so we are able to interact with the table customer (managed by its parent Database Bean)

file models\CustomerRecord.php

namespace models;

use models\beans\BeanCustomer;

class CustomerRecord extends BeanCustomer
{
    /**
     * Get the list of allowed nationalities for a customer.
     *
     * @return array Array of nationalities
     */
    public function getCustomerNationalitiesList(){
        return  array (
            array ("it","Italian"),
            array( "out","Guest")
        );
    }

    /**
     * Get the list of allowed assurances level for a customer.
     *
     * @return array Array of assurances level
     */
    public function getCustomerAssurancesList(){
        return  array (
            array ("1","Low"),
            array( "2","Middle"),
            array( "3","High"),
        );
    }
}

As you can note, in the code above, we don't have any methods regarding data management like, for example, inserting or updating records. We just defined two methods providing customer nationalities and assurances levels we need when classifying a customer.

The template code is shown below file templates\customer_record.html.tpl

<!DOCTYPE html>
<html>
<head>
    <title>Customer Record</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Bootstrap core CSS -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet"
          media="screen">
    <!-- Fonts Awesome -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.js"></script>
    <![endif]-->
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
                    aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">CRM Simple</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="#">Home</a></li>
                <li class="active"><a href="{GLOBAL:SITEURL}/customers_manager">Customers</a></li>
            </ul>
        </div>
    </div>
</nav>

<div class="container">

    <h1>Customer Record</h1>
    {Operation}
    <hr>
    <form class="form-horizontal" method="post">
        <!-- BEGIN DBError -->
        <div class="alert alert-danger" role="alert">
            {Errors}
        </div>
        <!-- END DBError -->
        <div class="form-group">
            <label for="name" class="control-label col-xs-4">Name</label>
            <div class="col-xs-8">
                <div class="input-group">
                    <div class="input-group-addon">
                        <i class="fa fa-user-md"></i>
                    </div>
                    <input id="name" name="name" placeholder="Customer name" type="text" required="required"
                           class="form-control" value="{name}">
                </div>
            </div>
        </div>
        <div class="form-group">
            <label for="email" class="control-label col-xs-4">Email</label>
            <div class="col-xs-8">
                <div class="input-group">
                    <div class="input-group-addon">
                        <i class="fa fa-address-card-o"></i>
                    </div>
                    <input id="email" name="email" placeholder="Customer email" type="email" required="required"
                           class="form-control" value="{email}">
                </div>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-xs-4">Nationality</label>
            <div class="col-xs-8">
                <!-- BEGIN NationalitiesCheckboxes -->
                <label class="checkbox-inline">
                    <input type="radio" name="nationality" value="{nationality}" {is_checked}>
                    {nationality_text}
                </label>
                <!-- END NationalitiesCheckboxes -->
            </div>
        </div>
        <div class="form-group">
            <label for="assurance" class="control-label col-xs-4">Customer assurance</label>
            <div class="col-xs-8">
                <select id="assurance" name="assurance" required="required" class="select form-control">
                    <!-- BEGIN AssuranceOptions -->
                    <option value="{assurance}" {is_selected}>{assurance_text}</option>
                    <!-- END AssuranceOptions -->
                </select>
            </div>
        </div>
        <div class="form-group row">
            <div class="col-xs-offset-4 col-xs-8">
                <input type="hidden" name="customer_id" value="{customer_id}">
                <!-- BEGIN AddMode -->
                <input name="operation_insert" type="submit" class="btn btn-primary" value="Add customer">
                <!-- END AddMode -->
                <!-- BEGIN EditMode -->
                <input name="operation_update" type="submit" class="btn btn-primary" value="Update">
                <input name="operation_delete" id="delete" type="submit" class="btn btn-danger" value="Delete customer">
                <!-- END EditMode -->
                <a href="{GLOBAL:SITEURL}/customers_manager" class="btn btn-info">Close or Cancel</a>
            </div>

        </div>
    </form>

</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js"></script>

<script>
    function confirmDelete(){
        return confirm("Delete current customer ?");
    }
    $(document).ready(function() {
        $("#delete").click(function () {
            return confirmDelete();
        });
    });
</script>
</body>
</html>

The template designs different blocks we need for managing DB error, nationalities list and of assurances levels, as well as, the buttons manging inserting or editing actions will be submitted by the HTML form. Finally, it also contains the necessary Javascript and Jquery code for handling confirmation message when deleting a record.

Finally, we show the code for controllers\CustomerRecord.php

namespace controllers;

use framework\Controller;
use framework\Model;
use framework\View;

use models\CustomerRecord as CustomerRecordModel;
use views\CustomerRecord as CustomerRecordView;

class CustomerRecord extends Controller
{
    protected $view;
    protected $model;

    /**
     * CustomerRecord constructor.
     *
     * @param View|null $view
     * @param Model|null $model
     * @throws \framework\exceptions\TemplateNotFoundException
     */
    public function __construct(View $view = null, Model $model = null)
    {
        $this->view = empty($view) ? $this->getView() : $view;
        $this->model = empty($model) ? $this->getModel() : $model;
        parent::__construct($this->view, $this->model);
    }

    /**
     * Autorun method. Put your code here for running it after
     * object creation.
     *
     * @param mixed|null $parameters Parameters to manage
     */
    protected function autorun($parameters = null)
    {
        $this->handleFormActionsSubmission();
        $this->view->initFormFields($this->model);
    }

    /**
     * Manage open action of a customer record
     *
     * @param int $customerID The customer id you need to select
     * @throws \framework\exceptions\NotInitializedViewException
     */
    public function open($customerID)
    {

        $currentRecord = $this->model->select($customerID);
        if (!$currentRecord)
            $this->closeAndRedirect();
        $this->autorun();
        $this->render();
    }

    /**
     * Manage form actions submission.
     *
     */
    private function handleFormActionsSubmission()
    {
        try {
            if (isset($_POST["operation_update"])) {
                $this->handlePostFields();
                $this->model->updateCurrent();
            }
            if (isset($_POST["operation_delete"])) {
                $this->model->delete($_POST["customer_id"]);
                $this->closeAndRedirect();
            }
            if (isset($_POST["operation_insert"])) {
                $this->handlePostFields();
                $this->model->insert();
                $this->closeAndRedirect();
            }
        } catch (\mysqli_sql_exception $e) {
            $_SESSION["mysql_error"] = $e->getMessage();
        }
    }

    /**
     * Handle post fields and setting the corresponding
     * model values.
     *
     */
    private function handlePostFields()
    {
        $this->model->setName(@$_POST["name"]);
        $this->model->setEmail(@$_POST["email"]);
        $this->model->setNationality(@$_POST["nationality"]);
        $this->model->setAssurance(@$_POST["assurance"]);
    }

    /**
     * Closing and redirecting if no SQL error occurred
     *
     */
    private function closeAndRedirect()
    {
        if (!$this->model->isSqlError()) {
            header("location:" . SITEURL . "/customers_manager");
        }
    }


    /**
     * Init View by loading static design of /customer_record.html.tpl
     * managed by views\CustomerRecord class
     *
     */
    public function getView()
    {
        $view = new CustomerRecordView("/customer_record");
        return $view;
    }

    /**
     * Init Model by loading models\CustomerRecord class
     *
     */
    public function getModel()
    {
        $model = new CustomerRecordModel();
        return $model;
    }
}

The controller, depending on form submission, is able to perform the right action on the database by using the Model, which is the customer database bean. It interacts with the View below by passing it the model (look at autorun method). Also, note the default behavior for the controller is to show the record in Inserting Mode. For handling record update it provides the open method. Thi method just selects the given customer record and performs a redirection if the customer is not found in the table, then it executes the autorun method.

file 'views\CustomerRecord.php'

namespace views;

use framework\View;

class CustomerRecord extends View
{

    /**
    * Object constructor.
    *
    * @param string|null $tplName The html template containing the static design.
    */
    public function __construct($tplName = null)
    {
        if (empty($tplName))
            $tplName = "/customer_record";
        parent::__construct($tplName);
    }

    public function initFormFields(\models\CustomerRecord $model){
        $name = isset($_POST["name"]) ? $_POST["name"] : $model->getName();
        $email = isset($_POST["email"]) ? $_POST["email"] : $model->getEmail();
        $nationality = isset($_POST["nationality"]) ? $_POST["nationality"] : $model->getNationality();
        $assurance = isset($_POST["assurance"]) ? $_POST["assurance"] : $model->getAssurance();
        $customer_id = isset($_POST["customer_id"]) ? $_POST["customer_id"] : $model->getCustomerId();

        $this->setVar("name", $name);
        $this->setVar("email", $email);
        $this->initCustomerNationalities($model->getCustomerNationalitiesList(),$nationality);
        $this->initCustomerAssurances($model->getCustomerAssurancesList(),$assurance);
        $this->setVar("customer_id", $customer_id);

        if (empty($model->getCustomerId())) {
            $this->setVar("Operation", "New Customer");
            $this->hide("EditMode");
        } else {
            $this->setVar("Operation", "Edit Customer");
            $this->hide("AddMode");

        }

        if (!isset($_SESSION["mysql_error"])){
            $this->hide("DBError");
        } else {
            $this->setVar("Errors", $_SESSION["mysql_error"]);
            unset($_SESSION["mysql_error"]);
        }
    }

    private function initCustomerNationalities($nationalities,$checkedItem = ""){
        $this->openBlock("NationalitiesCheckboxes");
        foreach ($nationalities  as $nationality) {
            $this->setVar("nationality", $nationality[0]);
            if ($checkedItem == $nationality[0]) {
                $this->setVar("is_checked", "checked");
            } else {
                $this->setVar("is_checked", "");
            }
            $this->setVar("nationality_text", $nationality[1]);
            $this->parseCurrentBlock();
        }
        $this->setBlock();
    }

    private function initCustomerAssurances($assurances, $selectedIdem = ""){
        $this->openBlock("AssuranceOptions");
        foreach ($assurances  as $assurance) {
            $this->setVar("assurance", $assurance[0]);
            if ($selectedIdem == $assurance[0]) {
                $this->setVar("is_selected", "selected");
            } else {
                $this->setVar("is_selected", "");
            }
            $this->setVar("assurance_text", $assurance[1]);
            $this->parseCurrentBlock();
        }
        $this->setBlock();
    }
}

The View initializes the GUI elements depending on the Model (and by its status) it receives. Look at the initFormFields method. So behaviors of all GUI elements are depending on inserting or updating mode, which is strictly related to the database bean status. The status can be expressed with the following roles:

  • inserting mode = when no record is selected by the received bean
  • updating mode = an existing record is selected by the received bean

Conclusion

The example we just discussed is a simple demonstration for the basic features of the Web MVC framework and its ORM engine for easily building of database applications. In the next pages of the current wiki, we expose you how to improve this basic example with advanced functionalities like:

  • Internationalization
  • Hierarchical MVC (HMVC) for content decompositions ad reuse
  • Authentication and role-based access management
  • Component-Based development
Clone this wiki locally