[What is MVC? 】
MVC is a concept that allows you to harmoniously combine "three parts (namely, the full name of MVC, Model, View, and Controller)" to form a complex application. A car is a very good example of MVC in real life. When we look at a car, we look at two View (display) parts: interior and exterior. Both of these are inseparable from a Controller: the driver. The brake system, steering wheel and other control systems represent the Model: they take the control methods from the driver (Controller) and apply them to the interior and exterior (View).
[MVC on the Web]
The concepts covered by the MVC framework are quite simple and extremely flexible. The basic concept is that you have a single controller (such as index.php) that controls all applications within the framework that are based on parameter requests. This controller usually contains (minimally) a parameter that defines the model, an event, and a GET parameter. This way the controller can acknowledge all requests and run the appropriate events. For example, a request like this /index.php?module=foo&event=bar is probably used to load a class named foo, and then run foo::bar()[which is bar( )function]. The advantages of this are:
maintaining an interface for all applications
while maintaining countless codes in an application is very troublesome, because each piece of code has its own relative path, database link, verification, etc. Doing so saves you the trouble and allows you to merge and reuse code
[Why create your own MVC framework? 】
So far, I have not seen many MVC frameworks written in PHP. In fact, I only know of one - Solar, which is completely written in PHP5. The other is Cake, which is trying to be RoR (Ruby on Rails - an open source network framework for the Ruby language) of PHP. I myself have some dissatisfaction with both frameworks: they do not take advantage of the existing code included in PEAR, Smarty, etc.; the current Cake is still relatively messy; finally, Solar is a framework written mostly by one person. (I have no intention of saying that its author, Paul, is not a good person or a good programmer). These questions probably don't make you deny them, and chances are you don't care about them at all. But because of this, I ask you to look at them as much as possible.
[Old way]
If you go back to 2001 and look at the code you wrote, the author may find a file called template.txt, which looks like this: www.phpv.net Please indicate the source for reprinting
<?php
require_once('config.php'); // Other requires, DB info, etc.
$APP_DB = 'mydb';
$APP_REQUIRE_LOGIN = false; // Set to true if script requires login
$APP_TEMPLATE_FILE = 'foo.php'; // Smarty template
$APP_TITLE = 'My Application';
if ($APP_REQUIRE_LOGIN == true) {
if (!isset($_SESSION['userID'])) {
header("Location: /path/to/login.php");
exit();
}
}
$db = DB::connect('mysql://'.$DB_USER.':'.$DB_PASS.'@localhost/'.$APP_DB);
if (!PEAR::isError($db)) {
$db->setFetchMode(DB_FETCHMODE_ASSOC);
} else {
die($db->getMessage());
}
// Put your logic here
// Output the template
include_once(APP_TEMPLATE_PATH.'/header.php');
include_once(APP_TEMPLATE_PATH.'/'.$APP_TEMPLATE_FILE);
include_once(APP_TEMPLATE_PATH.'/footer.php');
?>
Oh my gosh, just looking at this code makes me cringe. The concept of this code is to ensure that every application can be adapted to this processing method. For example, I can simply copy template.txt into myapp.php, change a few variables, and voila, it will run. Still, this highly organized approach has some serious drawbacks: What
if my boss wanted the author to use myapp.php to output PDF in some cases, HTML in some cases, and SOAP in some cases (XML requests submitted directly) ,what do I do?
What should I do if this app requires IMAP or LDAP authentication?
How do I handle the different types of code (including edits, upgrades, and deletions)?
How do I handle multi-level authentication (admin vs. non-admin)?
How do I enable output caching? www.phpv.net Please indicate the source for reprinting
[New way]
Throw everything into this MVC framework, and you will find that life is so simple. Please compare the following code:
<?php
class myapp extends FR_Auth_User
{
public function __construct()
{
parent::__construct();
}
public function __default()
{
// Do something here
}
public function delete()
{ }
public function __destruct()
{
parent::__destruct();
}
}
?>
Note that this code is obviously not used to link to a database, determine whether a user is logged in, or output any other information. The controller has it all.
If I want to authenticate to LDAP, I can establish FR_Auth_LDAP. The controller can recognize certain output methods (such as $_GET['output']) and can convert to PDF or SOAP at any time. The event handler delete is only responsible for deletion and does not care about anything else. Because this module has an instance of the FR_User class, it can simply determine whether a user is logged in, etc. Smarty, as a template engine, naturally controls the cache, but the controller can also control part of the cache.
Going from the old way mentioned earlier to the MVC way may be a new and unfamiliar concept to many people, but once you switch to such a concept, it will be quite difficult to switch back.
[Build the bottom layer]
I am a fan of PEAR, especially the PEAR_Error class. PHP5 introduces a new built-in class "Exception" which replaces PEAR_Error. But PEAR_Error has some more practical features than Exception. Therefore, the MVC framework examples in this series of articles will use it for error handling. Anyway, I still have to use Exception to get the error from the constructor, since they can't pass back the error themselves.
The purpose of designing these basic classes is as follows:
Use PEAR to quickly add functions to basic classes
to create small, repeatedly practical abstract classes so that users can quickly develop applications in this framework.
Use phpDocumentor to generate documents for all basic classes.
The class hierarchy will look like this:
- FR_Object will provide basic functionality for all other objects to use (including logging, general setFrom(), toArray())
- FR_Object_DB is a small layer that provides database links to subclasses and other functions
- FR_Module is the bottom class of all applications (also called modules, models, etc.)
- FR_Auth is the bottom class of all verification mechanisms
· FR_Auth_User is a verification class used to verify all modules that need to verify whether the user is logged in
· FR_Auth_No is all "Fake validation classes" for modules that do not require validation
- FR_Presenter is the underlying class for all applications that handle loading and display
- FR_Presenter_Smarty is the presentation layer that includes the ability to load different drives. Smarty is a very good template class. It has a built-in caching mechanism and an active development group (Translator's Note: This is obviously an advertisement~)
· FR_Presenter_debug is the display layer of the debugging part. Relying on it, developers can debug applications and debug them
. FR_Presenter_rest is a REST presentation layer that allows developers to output applications in XML.
From the above basic class structure, you should be able to see this MVC framework. different parts. FR_Module provides everything needed by the module, while FR_Presenter provides different display methods. In the next article in this series, I'll create a controller that ties together all of the above base classes.
[Coding Standards]
Before you formally write code, you should sit down and discuss (or think about) coding standards with your partners (or yourself). The overall idea of MVC programming revolves around two points: code reusability (reducing coincidence) and code standardization. I recommend at least the following points to be considered:
The first thing to consider is variable naming and abbreviation standards. Don't get into a big fight with your partners because of this, but once the standards are set, they must be followed from beginning to end, especially when writing low-level code (base classes).
Customize a standard prefix to use on all functions, classes and global variables. Unfortunately, PHP does not support "namespace (namespace)". So to avoid confusion and conflicts with variable names, it is wise to use a prefix. I will use "FR_" as such a prefix throughout this article.
[Writing the bottom layer]
File level planning is very important. Basic hierarchical planning is simple and somewhat tightly defined:
/
config.php
index.php
includes/
Auth.php
Auth/
No.php
User.php
Module.php
Object.php
Object/
DB.php
Presenter.php
Presenter/
common.php
debug.php
smarty.php
Smarty/
modules/
example/
config.php
example.php
tpl/
example.tpl
tpl/
default/
cache/
config/
templates/
templates_c/
You might think that such a file hierarchy must represent a lot of code! True, but you can get it done. By the end of the series, you will find that your programming will become easier and your development speed will be greatly improved.
In the file hierarchy, all basic classes are in the includes folder. Each functional module uses a configuration file, at least one module file and one template file. All modules are contained in the modules folder. I've become accustomed to placing template files in a separate external folder, the tpl folder.
config.php - the central configuration file, containing all global configuration variables.
index.php - Controller, will be described in detail in the next article.
object.php - the underlying class for all basic classes, providing most of the functionality needed by the class. FR_Object_DB inherits this class and provides database links.
The basic concept of a structure is to have all subclasses inherit from a central class so that they all share some common features. You can put the function of linking to the database into FR_Object, but not all classes need this function, so FR_Object_DB has a reason for existence, and the author will discuss it later.
<?php
require_once('Log.php');
/**
*FR_Object
*
* The base object class for most of the classes that we use in our framework.
* Provides basic logging and set/get functionality.
*
* @author Joe Stump < [email protected] >
* @packageFramework
*/
abstract class FR_Object
{
/**
* $log
*
* @var mixed $log Instance of PEAR Log
*/
protected $log;
/**
*$me
*
* @var mixed $me Instance of ReflectionClass
*/
protected $me;
/**
* __construct
*
* @author Joe Stump < [email protected] >
* @access public
*/
public function __construct()
{
$this->log = Log::factory('file',FR_LOG_FILE);
$this->me = new ReflectionClass($this);
}
/**
* setFrom
*
* @author Joe Stump < [email protected] >
* @access public
* @param mixed $data Array of variables to assign to instance
* @return void
*/
public function setFrom($data)
{
if (is_array($data) && count($data)) {
$valid = get_class_vars(get_class($this));
foreach ($valid as $var => $val) {
if (isset($data[$var])) {
$this->$var = $data[$var];
}
}
}
}
/**
* toArray
*
* @author Joe Stump < [email protected] >
* @access public
* @return mixed Array of member variables keyed by variable name
*/
public function toArray()
{
$defaults = $this->me->getDefaultProperties();
$return = array();
foreach ($defaults as $var => $val) {
if ($this->$var instanceof FR_Object) {
$return[$var] = $this->$var->toArray();
} else {
$return[$var] = $this->$var;
}
}
return $return;
}
/**
* __destruct
*
* @author Joe Stump < [email protected] >
* @access public
* @return void
*/
public function __destruct()
{
if ($this->log instanceof Log) {
$this->log->close();
}
}
}
?>
auth.php – This is the underlying class for all authentication functionality. It is extended from FR_Module, and its main function is to define how a basic verification class works.
Just like FR_Module, some classes do not need to be connected to the database. In the same way, FR_Auth_No can be created and applied to classes that do not require authentication functions.
<?php
abstract class FR_Auth extends FR_Module
{
// {{{ __construct()
function__construct()
{
parent::__construct();
}
// }}}
// {{{ authenticate()
abstract function authenticate();
// }}}
// {{{ __destruct()
function __destruct()
{
parent::__destruct();
}
// }}}
}
?>
module.php - the heart of all modules
<?php
abstract class FR_Module extends FR_Object_Web
{
// {{{ properties
/**
* $presenter
*
* Used in FR_Presenter::factory() to determine which presentation (view)
* class should be used for the module.
*
* @author Joe Stump < [email protected] >
* @var string $presenter
* @see FR_Presenter, FR_Presenter_common, FR_Presenter_smarty
*/
public $presenter = 'smarty';
/**
* $data
*
* Data set by the module that will eventually be passed to the view.
*
* @author Joe Stump < [email protected] >
* @var mixed $data Module data
* @see FR_Module::set(), FR_Module::getData()
*/
protected $data = array();
/**
* $name
*
* @author Joe Stump < [email protected] >
* @var string $name Name of module class
*/
public $name;
/**
* $tplFile
*
* @author Joe Stump < [email protected] >
* @var string $tplFile Name of template file
* @see FR_Presenter_smarty
*/
public $tplFile;
/**
* $moduleName
*
* @author Joe Stump < [email protected] >
* @var string $moduleName Name of requested module
* @see FR_Presenter_smarty
*/
public $moduleName = null;
/**
* $pageTemplateFile
*
* @author Joe Stump < [email protected] >
* @var string $pageTemplateFile Name of outer page template
*/
public $pageTemplateFile = null;
// }}}
// {{{ __construct()
/**
* __construct
*
* @author Joe Stump < [email protected] >
*/
public function __construct()
{
parent::__construct();
$this->name = $this->me->getName();
$this->tplFile = $this->name.'.tpl';
}
// }}}
// {{{ __default()
/**
* __default
*
* This function is ran by the controller if an event is not specified
* in the user's request.
*
* @author Joe Stump < [email protected] >
*/
abstract public function __default();
// }}}
// {{{ set($var,$val)
/**
* set
*
* Set data for your module. This will eventually be passed toe the
* presenter class via FR_Module::getData().
*
* @author Joe Stump < [email protected] >
* @param string $var Name of variable
* @param mixed $val Value of variable
* @return void
* @see FR_Module::getData()
*/
protected function set($var,$val) {
$this->data[$var] = $val;
}
// }}}
// {{{ getData()
/**
*getData
*
* Returns module's data.
*
* @author Joe Stump < [email protected] >
* @return mixed
* @see FR_Presenter_common
*/
public function getData()
{
return $this->data;
}
// }}}
// {{{ isValid($module)
/**
*isValid
*
* Determines if $module is a valid framework module. This is used by
* the controller to determine if the module fits into our framework's
* mold. If it extends from both FR_Module and FR_Auth then it should be
* good to run.
*
* @author Joe Stump < [email protected] >
* @static
* @param mixed $module
* @return bool
*/
public static function isValid($module)
{
return (is_object($module) &&
$module instanceof FR_Module &&
$module instanceof FR_Auth);
}
// }}}
// {{{ __destruct()
public function __destruct()
{
parent::__destruct();
}
// }}}
}
?>
presenter.php - the core of the presentation layer.
<?php
class FR_Presenter
{
// {{{ factory($type,FR_Module $module)
/**
*factory
*
* @author Joe Stump < [email protected] >
* @access public
* @param string $type Presentation type (our view)
* @param mixed $module Our module, which the presenter will display
* @return mixed PEAR_Error on failure or a valid presenter
* @static
*/
static public function factory($type,FR_Module $module)
{
$file = FR_BASE_PATH.'/includes/Presenter/'.$type.'.php';
if (include($file)) {
$class = 'FR_Presenter_'.$type;
if (class_exists($class)) {
$presenter = new $class($module);
if ($presenter instanceof FR_Presenter_common) {
return $presenter;
}
return PEAR::raiseError('Invalid presentation class: '.$type);
}
return PEAR::raiseError('Presentation class not found: '.$type);
}
return PEAR::raiseError('Presenter file not found: '.$type);
}
// }}}
}
?>
In the next article, I will introduce the structure of the controller (Controller in MVC, index.php in this article). In the third article, I will introduce the presentation layer (View in MVC). In the fourth article, I will use a specific module as an example to create an application (Module or Model in MVC).