批改状态:合格
老师批语:

其实日常开发中无论是仿站还是自己开发都基本按这个思路。如进行仿站一般都是从View开始,然后Model,最后想着Controller从Model数据库中读取数据展示到View中给客户浏览。而自己开发则是先考虑数据库Model的设计,怎么展示给客户View和如何Controller将数据库中数据传递给View。
下面就是简单的实现:
//Model.php(模型)class Model{function getData(){$dsn="mysql:host=localhost;dbname=test;charset=UTF8";try{$pdo=new PDO($dsn,'root','root');$sql="SELECT * from user";return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);}catch(PDOException $e){echo '数据库连接错误:'.$e->getMessage();exit();}}}
//View.php(视图)class View{function fetch(array $data){$html="<table border=1 cellspacing='0'>";$html.="<thead><tr bgColor='lightgray'><th>ID号</th><th>帐号</th><th>密码</th></tr></thead><tbody>";foreach($data as $user):$html.="<tr><td>{$user['id']}</td>";$html.="<td>{$user['uname']}</td>";$html.="<td>{$user['pwd']}</td></tr>";endforeach;$html.="</tbody></table>";return $html;}}
//Controller.php(控制器)require_once 'Model.php';require_once 'View.php';class Controller{// 参数注入当前方法中,仅给当前方法使用function index(Model $model, View $view){return $view->fetch($model->getData());}}// 测试输出代码$model=new Model();$view=new View();echo (new Controller())->index($model,$view);
在MVC模式中控制器Controller类中依赖模型类Model实例对象和视图类View实例对象function index(Model $model, View $view),此时我们可以在 当前成员方法中使用参数注入的方式,解决在当前类中使用其它类实例对象的问题,参数注入其实就是参数传递,它可以传递普通变量,也可是对象、闭包。
若是其它类的实例对象需要在当前类的众多成员方法中使用时( 共享或复用 ),若仍然采用将对象参数注入到成员方法中,将要多处写参数注入,此时建议方案就是能完 构造函数的参数注入 方式,将其它类的实例对象赋值给类的成员变量,这样当前类的所有成员方法都可访问,从而实现了其它类的共享或复用。
// 类的共享或复用class Controller{private $model=null;private $view=null;// 参数注入构造函数中,赋值给类的成员变量,便于类的成员方法访问(共享或复用)function __construct(Model $model, View $view){$this->model=$model;$this->view=$view;}function index(){return $this->view->fetch($this->model->getData());}}$model=new Model();$view=new View();echo (new Controller($model,$view))->index();
补充: 以前看框架时,说优势之一就是使用依赖注入,后来经老师演示,原来就是参数传递,不得不说,好多文档说明故意提高了理解的门槛,看来理解基础再来学框架是最正确的学习路径。
如果当前类依赖的对象较多,或者项目中有许多对象和类需要在项目中反复调用,可以将当前依赖的外部对象放到一个”服务容器”中进行统一管理。如TP6框架的Container类,服务容器最基本要有三个成员:对象容器(对象数组)、绑定对象方法和取对象方法。
// 如果当前类依赖的对象较多,可以将当前依赖的外部对象放到一个"服务容器"中进行统一管理// 服务容器: 一个自动产生类/对象的工厂class Container{// 1、对象容器(对象数组)protected $instance = [];/*** 2、绑定对象:对象容器中添加对象* $abstract :类标识, 接口, 是外部对象在当前容器数组中的键名/别名 alias ** $concrete : 要绑定的类, 闭包或者实例, 传入一个对象或者一个闭包,前者需要我们先实例化对象才能往对象容器中添加, 闭包优势:我们使用这个对象的时候,才实例化对象*/function bind($abstract, Closure $concrete){$this->instance[$abstract] = $concrete;}//3.从对象容器中取出对象, 调用function make($abstract, $args = []){return call_user_func_array($this->instance[$abstract], $args);}}
此时依赖注入就不是一个个类了,而是包含这些类的服务容器类Container,其实质仍然是引用类的实例对象,只不过这些实例对象通过bind方法保存到容器类中对象数组中,使用时使用make方法取出即可。
// Controller2.phprequire_once 'Model.php';require_once 'View.php';require_once '../Container.php';// 类的共享或复用class Controller{private $model=null;private $view=null;// 参数注入构造函数中,赋值给类的成员变量,便于类的成员方法访问(共享或复用)function __construct(Container $container){$this->model=$container->make('model');$this->view=$container->make('view');}function index(){return $this->view->fetch($this->model->getData());}}$container=new Container();$container->bind('model',function(){return new Model();});$container->bind('view',function(){return new View();});echo (new Controller($container))->index();
本来要讲Facade(门面)技术的,但它静态实现依赖static的后期静态绑定,自PHP 5.3.0起,PHP增加了一个叫做后期静态绑定的功能,用于在继承范围内引用静态调用的类。在群里也咨询老师了,老师的解释是后期静态绑定,是在继承上下文中,self只能和声明类进行绑定,并不能和调用类进行绑定。,同时也查阅了PHP官方的解释,最终梳理成以下几个理解点:
首先三者都是相当于魔术变量,在实际编译时会指向具体的类,都不能在类外调用,只能在类中使用。至于老师说的声明类和调用类,如何区分声明类和调用类就不好界定。通过代码测试,我的发现它们区别就是 查找类中成员范围起点不同 。
self::who();就是先从B类中查找成员,刚好有就输出Bparent::who();虽然B类已经定义who方法,但它直接从父类A开始查找,找到who方法,输出为A。parent::foo();self::foo();按parent和self规则其实最终找到就是A类中foo方法,但它是static访问成员,所以它要用明确指明类来替换,而下面代码是C::test()导致这种查找,因为C类中没有tes方法,它从父类B中找到,此时明确调用类是C不是B,这点最容易误导。下面是基于PHP官方区分self、parent和static的代码进行修改版,可以认清三者的查找成员时起点:
class A {public static function foo() {static::who();}public static function who() {echo __CLASS__."<br>";}}class B extends A {public static function test() {// 指明具体类A::foo();// 调用static后期绑定静态成员parent::foo();self::foo();// parent和self区别parent::who();self::who();}public static function who() {echo __CLASS__."<br>";}}class C extends B {public static function who() {echo __CLASS__."<br>";}}C::test();

补充: 正如图片中所说,static也同self和parent,只是指明
查找成员起点不同而已。若上面代码中C类没有重写who方法,即注释掉C类中who方法,此时由于C类没有who方法,C类调用父类B中who方法,此时输出是:A B B A B。
self::,parent::,static::以及 forward_static_call()。Foo::foo();上面是某网文对两种调用的解释,其中第一个是官方的解释,但我认为它们只是表面认识。经测试 两种调用都是针对是否重写继承父类的成员 来说的。若是重写了父类的成员,则明确指定类名或self::访问都直接访问当前类中成员,终止了向上查找,是非转发调用。而没有重写父类的成员则无论是指定类名,还是上面几种都是转发调用,上面代码中C::test()虽然指定类名了,但它转发调用B类中test方法。
官方解释说:存储了在上一个“非转发调用”(non-forwarding call)的类名。其实我觉得关键词是两个”非转发调用”和”类名”,上面代码中C::test()输出static::who();是从C类开始查找who成员方法,结果找到了,所以此时”非转发调用的类名”是C,若是C类中没有重写who方法(即注释掉C类的who方法),在B类中找到who成员方法,此时”非转发调用的类名”是B。所以 “非转发调用的类名”是由指定类名开始的,由继承关系的类组成的队列。下面是官方代码的解析过程,可以具体理解下三者作用。
* C::test(); //非转发调用 ,进入test()调用后,“上一次非转发调用”存储的类名为C** //当前的“上一次非转发调用”存储的类名为C* public static function test() {* A::foo(); //非转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为A,然后实际执行代码A::foo(), 转 0-0* parent::foo(); //转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为C, 此处的parent解析为A ,转1-0* self::foo(); //转发调用, 进入foo()调用后,“上一次非转发调用”存储的类名为C, 此处self解析为B, 转2-0* }** 0-0* //当前的“上一次非转发调用”存储的类名为A* public static function foo() {* static::who(); //转发调用, 因为当前的“上一次非转发调用”存储的类名为A, 故实际执行代码A::who(),即static代表A,进入who()调用后,“上一次非转发调用”存储的类名依然为A,因此打印 “A”* }** 1-0* //当前的“上一次非转发调用”存储的类名为C* public static function foo() {* static::who(); //转发调用, 因为当前的“上一次非转发调用”存储的类名为C, 故实际执行代码C::who(),即static代表C,进入who()调用后,“上一次非转发调用”存储的类名依然为C,因此打印 “C”* }** 2-0* //当前的“上一次非转发调用”存储的类名为C* public static function foo() {* static::who(); //转发调用, 因为当前的“上一次非转发调用”存储的类名为C, 故实际执行代码C::who(),即static代表C,进入who()调用后,“上一次非转发调用”存储的类名依然为C,因此打印 “C”* }
class A{protected static $_instance = null;static public function getInstance(){if (self::$_instance === null) {self::$_instance = new self();}return self::$_instance;}}class B extends A{}class C extends A{}$a = A::getInstance();$b = B::getInstance();$c = C::getInstance();var_dump($a);var_dump($b);var_dump($c);

上面单例继承问题可通过static::解决,即getInstance()方法中self都替换成static就可以。在TP6框架的服务容器Container类中也是通过static::解决单例继承问题的。
//TP6的Container.php中实例方法public static function getInstance(){if (is_null(static::$instance)) {static::$instance = new static;}if (static::$instance instanceof Closure) {return (static::$instance)();}return static::$instance;}
门面类Facade其实相当于中间层,先对想静态调用的类定义继承于门面类的子类,然后在该子类中定义相应的静态方法,实质是再调用对应类的非静态方法。为了相同类名,我参考了TP6框架的做法,将门面类统一命名空间为Facade。下面演示是基于服务容器类的门面类,单独类需要门面类似定义即可。
// Facade.phpnamespace Facade;require_once 'Container.php';// 门面类class Facade{protected static $container = null;static function instance(\Container $container){static::$container = $container;}}// 需要静态调用的类继承门面类,为了统一,和原类名相同class Model extends Facade{static function getData(){return static::$container->make('model')->getData();}}class View extends Facade{static function fetch($data){return static::$container->make('view')->fetch($data);}}
上面已经定义了基于服务容器类Container的门面类,将服务容器的对象数组成员赋值给Facade的静态成员,从而被所有继承Facade类的子类共享。
// Controller3.phprequire_once 'Facade.php';require_once 'Model.php';require_once 'View.php';use Facade\Facade;use Facade\Model;use Facade\View;// 类的共享或复用class Controller{function __construct(Container $container){Facade::instance($container);}function index(){return View::fetch(Model::getData());}}$container=new Container();$container->bind('model',function(){return new \Model();});$container->bind('view',function(){return new \View();});echo (new Controller($container))->index();
补充: 在我的Githubhttps://github.com/woxiaoyao81/phpcn13或Giteehttps://gitee.com/freegroup81/phpcn13可以下载到参考TP6运行模式的《PHP从零开始写MVC》压缩包源码,我是在从TP官网论坛下载到的,可以作为本博文的进阶练习。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号