# 间接读取数据 直接读取数据虽然简单易懂,但往往在大型的项目中却并不适用。如果实现一个功能,我们把所有的代码都写在一个方法中,显然当功能复杂时,代码就会越来越长,越来越难以维护。这显然不是我们想看到的。 基于此,我们有了数据库M层,一个专门为我们处理数据表的文件。 ## M 数据模型层 没有M层以前我们取数的过程是这样的。 ![](https://box.kancloud.cn/2016-06-13_575e5bec70780.png) 有了M层以后,我们取数的过程变成了这样。 ![](https://box.kancloud.cn/2016-06-13_575e5bec83b8d.png) 是的,你想的没错,在小型的项目,或是我们这个教程中,这样做的确是画蛇添足了。但是我们的目标,并不仅仅满足于做小项目。后面你将会一点点的感受到分层的魅力。 ## 调用谁,你就要USE谁 我们再看前面写过的代码: ![](https://box.kancloud.cn/2016-06-13_575e5bec95ec4.png) 如果删除或注释掉第3行。 就会报如下错误: ![](https://box.kancloud.cn/2016-06-13_575e5becb7ceb.png) 报错信息中提示这个类 'app\index\controller\DB' 没有找到。 原因就是我们在用Db类以前,没有告诉程序这个正确的Db类在哪个位置,怎么告诉呢?利用的就是USE。 那我们说,你怎么就知道要这么use呢? 我们先看一下,Db这个类到底在哪。 ![](https://box.kancloud.cn/cfa27f6809b7dd160a110dddfc174837_254x650.png) 其实它在哪并不要紧,PHP中有一个叫命名空间的东西,至于怎么工作的,有兴趣的GOOGLE吧。我们只需要知道它叫命名空间就可以了。如下图所示: ![](https://box.kancloud.cn/2016-06-13_575e5bed00748.png) 命名空间,可以简单的理解为:我住哪儿。 我们看到Db这个类,它住在了`think`中,它的名字叫`Db`,所以别人想找它帮忙的时候,只需要`use think\Db;`就可以了。 当然了,我们自己写的类,由于也有命名空间,别人也可以通过我们的类住在哪儿,叫什么名字,来use我们的文件。 其实想搞清楚命名空间这个神奇的东西并不容易,所以搞不太清楚没有关系,我们现在只需要知道 **调用谁,就USE谁** ,就可以了。慢慢的,我们会一点点的明白什么是命名空间。 ### 调用M层,我们就要USE M层 #### 建立M层文件 上面我们是使用了Db这个类,这次我们放弃Db类。在实际的开发过程中,大多数的数据表都是固定的(有些项目需要动态生成数据表),所以我们也很少会去用这个Db类。基于此,我们对Db类的了解,到此为止。 这次我们引用think内置的一个类think\Model,这个类中封装好了所有的数据库操作方法。我们将采用继承的方法,如果现在不太清楚什么是继承,可以直接往下看,但我们有必要去学一学C++了。 代码如下: ~~~ <?php // 简单的原理重复记: namespace说明了该文件位于application\common\model 文件夹中 namespace app\common\model; use think\Model; // 导入think\Model类 /** * Teacher 教师表 */ // 我的类名叫做Teacher,对应的文件名为Teacher.php,该类继承了Model类,Model我们在文件头中,提前使用use进行了导入。 class Teacher extends Model { } ~~~ 我们再来巩固一下namespace,这个叫做命名空间的东西。 app\common\model; 是指这个文件位于application文件夹下的common文件夹下的model文件夹中。 所以当我们看到以app打头的命名空间时,也就相当于告诉了我们这个文件位于哪个位置。 类名必然与文件名相同。比如Teacher类,必须对应Teacher.php文件。 所以根据以上的代码,我们应该能够快速的定位到该代码应该处于: `application\common\model\Teacher.php`文件中。 查看**application**目录树 ~~~ ├── command.php ├── common │   └── model │   └── Teacher.php ├── common.php ├── config.php ├── database.php ├── index │   └── controller │   ├── Index.php │   └── Teacher.php ├── route.php └── tags.php ~~~ 最后我们是通过 extends 来继承的,因此,不仅仅我们在调用这个类之前需要进行use,我们在继承一个类之前也需要use。 #### 调用Teacher.php 模型文件 我们先回到C层,即controller文件夹下,对原Teahcer.php进行重新编辑,去除对Db类的调用。 > 我们需要记往一点,只有controller文件夹下的文件,才可以通过URL访问的到。其它的文件,比如我们刚刚新建的这个M层的Teacher.php,是直接访问不到的。 删除冗余的代码后如下: ~~~ <?php namespace app\index\controller; // 该文件的位于application\index\controller文件夹 /** * 教师管理 */ class Teacher { public function index() { echo 'hello teacher'; } } ~~~ 输入URL进行测试结果显示如下: ![](https://box.kancloud.cn/2016-06-13_575e5bed4c317.png) 下面,我们正式调用Teacher模型。 1、先use (导入) 2、再调用 示例代码如下: ~~~ <?php namespace app\index\controller; // 该文件的位于application\index\controller文件夹 use app\common\model\Teacher; // 教师模型 /** * 教师管理 */ class Teacher { public function index() { $Teacher = new Teacher; dump($Teacher); } } ~~~ 经测试,我们将得到如下错误提示: ![](https://box.kancloud.cn/2016-06-13_575e5bed5fa87.png) ~~~ git checkout -f step3.2.4.1 ~~~ 执行上述命令后,上述示例代码信息如下: ![](https://box.kancloud.cn/cd2587c989c11dcde5a2c738d5391514_653x307.png) 提示:不能重新声明`app\index\controller\Teacher`这个类,原因是由于这个类已经存在。 > 每个班级里面只能有一个小明,如果存在两个小明,那么他们肯定有一个是**大小明**,另一个是**小小明**。 在日常使用电脑时,也是如此。当我们尝试在同一个文件夹中建立两个同名文件(包含扩展名)时,就会报错。 有人说,老师我们并没有在同一个文件夹中建立`Teacher`呀,按上面的理论,应该不报错才对。这里,就要说下use这条语句了,当它执行`use app\common\model\Teacher;`时,我们可以简单理解为:将`application\common\model`文件夹下的`Teacher.php`文件中定义的`Teacher`类导入到当前文件。 此时,由于导入的类名叫做Teacher,然后我们自己的名字也叫做Teacher,就出现了名字发生冲突的错误。 所以简单来说:上述出现错误的原因,是由于这两个类起的名字是相同的。 ![](https://box.kancloud.cn/4eb08b4e6a8ba95f3543d6491935999f_436x352.png) #### 解决方法1 为导入的那个Teacher 改个名字 ~~~ <?php namespace app\index\controller; // 该文件的位于application\index\controller文件夹 // 导入app\common\model\Teacher模型,并给它重新起个名字:SmallTeacher use app\common\model\Teacher as SmallTeacher; // 教师模型 带有别名 /** * 教师管理 */ class Teacher { public function index() { $SmallTeacher = new SmallTeacher; dump($SmallTeacher); } } ~~~ ~~~ git checkout -f step3.2.4.2 ~~~ 执行上述命令后,上述示例代码信息如下: ![](https://box.kancloud.cn/2016-06-29_577343c45a825.png) #### 解决方法2,更改thinkphp的命名规则,不给他们发生冲突的机会。 1、修改config.php 在config.php 找到 controller_suffix 项,并配置为 true: ~~~ // 控制器类后缀 'controller_suffix' => true, ~~~ config.php 位置application根目录下。 > 细心的你可能早就发现了一个问题:为什么这个文件就没有命名空间呢? 是的,它没有,但是我们也没有看到里面有Class的字样呀,既然它不是一个Class(类),没有命名空间就是正常的。 2、修改原来C层的文件名 Teacher.php -> TeacherController.php 当然了,同为控制器的Index.php,也要修改成IndexController.php。由于类名与文件名相同,所以类名当然也要修改了。 修改后如图所示: ![](https://box.kancloud.cn/d0dd7825d4501386228b71a5176a97cf_1086x329.png) 这时候,C层的类名字叫做TeacherController,M层的叫做Teacher,两个名字不一样了,当然也就不需要使用别名了。 刷新URL进行测试: 如果你见到如下错误: ***控制器不存在:Teacher*** 产生的原因有以下两种: 1. 我们config.php中的配置信息没有起作用,请检查拼写是否有错误,文件是否进行正常保存等。 2. 文件名或类名命名不正确。 > 我们在以后的学习中,还会遇到上述错误提示,查找问题原因的方向为:1. 命名空间 2. 文件位置 3. 文件名 4. 类名 。 需要重点检查的项如下图所示: ![](https://box.kancloud.cn/e71d9f52244643929c70b269f7ab49dd_1090x329.png) 上图中的错误,你发现了吗? 由于此次我们并没有变动文件的位置,所以命名空间可以确定是没有问题的。那么,如果有问题就应该出现在文件名和类名上了。 #### 正确的测试结果如下图所示: ![](https://box.kancloud.cn/4fb88c6eef32448ab2e4acd73f147f38_666x395.png) 我们看到,第一行提示说在debug文件的169行。显然,我们更希望看到的是在TeacherController文件的第12行,这是由于我们使用了thinkphp内置的dump()方法造成的。在以后的教程中,如果我们在其它的历史项目中看到这个函数,知道它等于var_dump()就可以了。 > 在以后的项目或是教程中,我们将弃用dump()函数,一律改为var_dump()。 最后,我们去除原来的别名。 代码如下: ~~~ <?php namespace app\index\controller; // 该文件的位于application\index\controller文件夹 use app\common\model\Teacher; // 教师模型 /** * 教师管理 */ class TeacherController { public function index() { $Teacher = new Teacher; var_dump($Teacher); } } ~~~ 测试结果: ![](https://box.kancloud.cn/b96fdad49533afeedcf9e4d018e31695_684x389.png) 使用`var_dump()`提示我们,现在发生的位置位于`TeacherController`的第12行。 同时我们还看到`$Teacher`变量的类型是一个`object`,这个对象的原型是`app\common\model\Teacher`。没错,的确是我们引入的那个`app\common\model\Teacher`。 ~~~ git checkout -f step3.2.4.3 ~~~ 执行上述命令后,上述示例代码信息如下: ![](https://box.kancloud.cn/ec2dbb06785e65c966cefacd47df4d6b_1079x297.png) ## 主角登场 使用select()来获取数据库中的信息。 ~~~ <?php namespace app\index\controller; // 该文件位于application\index\controller文件夹 use app\common\model\Teacher; // 教师模型 /** * 教师管理 */ class TeacherController { public function index() { $Teacher = new Teacher; $teachers = $Teacher->select(); var_dump($teachers); } } ~~~ 测试结果: ![](https://box.kancloud.cn/9ef0204be7b536a78a71d0d245e2af9a_553x144.png) ~~~ git checkout -f step3.2.4.4 ~~~ 执行上述命令后,上述示例代码信息如下: ![](https://box.kancloud.cn/eba80b6b4b6d342918bb8e0a4480c731_1112x329.png) 我们看到返回值是个数组,这个数组的第0项是一个对象,第1项也是个对象,这两个对象都是基于`app\common\model\Teacher`这个类创建的。 我们并没在`app\common\model\Teacher`中定义任何属性,那么这些保护类型的私有属性是怎么来的呢?这是由于它继承了一个叫做`think\Model`的类。继承就是这样:你自己虽然没有,但是你父类有就和你有是一样的。在继承中是这样规定的,在父类中所有的protected和public的变量或**方法**,都是可以被子类直接使用。 变量我们看到了,那么我们还可以在这个对象上调用哪些方法呢? > `var_dump()` 函数可以查看对象中的属性,但却不显示对象中存在的方法。 我们现在就查看一下`think\Model`(think开头的类,位于thinkphp5/thinkphp/library/think文件夹下)中都有什么方法,然后调用一个试试。 ![](https://box.kancloud.cn/c8f967270b9c0029dc49612fc1f4882c_1203x669.png) 我们在Model.php文件中,同时按下 `Ctrl+P`,然后输入`@getData`,找到获取对象原始数据getData()方法,如下图所示。 ![](https://box.kancloud.cn/af8c5bc6a0a33b42910601c4fb135599_633x469.png) getData()方法代码如下: ~~~ /** * 获取对象原始数据 如果不存在指定字段返回false * @access public * @param string $name 字段名 留空获取全部 * @return mixed * @throws InvalidArgumentException */ public function getData($name = null) { if (is_null($name)) { return $this->data; } elseif (array_key_exists($name, $this->data)) { return $this->data[$name]; } else { throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); } } ~~~ 在此,不得不说:**越是技术牛气的人,代码越是规范!** 想成为技术大牛吗?从规范代码做起! 修改C层代码: ~~~ <?php namespace app\index\controller; use app\common\model\Teacher; // 教师模型 /** * 教师管理 */ class TeacherController { public function index() { $Teacher = new Teacher; $teachers = $Teacher->select(); // 获取第0个数据 $teacher = $teachers[0]; // 调用上述对象的getData()方法 var_dump($teacher->getData()); } } ~~~ 测试结果如下: ![](https://box.kancloud.cn/9a4a511f4e62844fcece2323e72c7ae5_573x240.png) 为getData()传参: ~~~ // 调用上述对象的getData()方法 var_dump($teacher->getData('name')); ~~~ 再测试,结果显示如下: ![](https://box.kancloud.cn/3206fcd698b5987da23ee63380151b38_758x75.png) 增加另外两个直接显示数据的方式: ~~~ // 调用上述对象的getData()方法 var_dump($teacher->getData('name')); echo $teacher->getData('name'); return $teacher->getData('name'); ~~~ 测试结果如下: ![](https://box.kancloud.cn/4304e1df387836bd7247a12c7575ff96_755x107.png) #### thinkphp 模型与数据表的自动关联 细心的你,发现我们并没有指定要去查哪个表,系统自动的就将Teacher表中的数据返回给了我们。 > 这是由于当我们新建的 app\common\model\Teacher,继承了think\Model, 在think\Model中,有个功能是自动关联数据表。也就是说,我们起的类名叫做Teacher,它就自动关联yunzhi_teacher这个表。为什么前面会自动加上yunzhi_ ? 再去找一下database.php吧。 是的,就这么神奇,数据表中的数据就这么过来了。 用Db类的select()方法和我们此处的 select() 方法,获取到的虽然都是一个数组,但一个是数组中的子项还是数组,而另一个是数组中的子项却是对象了。由于我们并不打算使用Db类,所以在此不对这两者的区别做过多的阐述。我们只需要知道,我们是面向对象的编程思路,即:**一切皆对象** 就可以了。 ### 重构代码 重新去写自己的代码就叫做重构,所以我们这里也叫重构,但我们重构的目的是越重构越清晰,而在这里重构的目的是希望大家对代码有了更加深入的理解。 重构后代码如下: ~~~ <?php namespace app\index\controller; use app\common\model\Teacher; // 教师模型 /** * 教师管理 */ class TeacherController { public function index() { $JiaoShiBiao = new Teacher; $SuoYouJiaoShi = $JiaoShiBiao->select(); // 获取第0个数据 $jiaoShiZhangSan = $SuoYouJiaoShi[0]; // 调用上述对象的getData()方法 echo '教师姓名' . $jiaoShiZhangSan->getData('name') . '<br />'; return '重复一遍:教师姓名' . $jiaoShiZhangSan->getData('name'); } } ~~~ 测试结果如下: ![](https://box.kancloud.cn/fe4be06cf9c9a7eac2ca5143c038e891_346x101.png) 也就是说$xxxx中的xxxx是变量名,这个名字你随便起,只要前后一致就可以。这些是由我们规定好的,它们在我们代码中第一次出现的位置是表达式的最左侧,所以我们想起什么名字,就起什么名字。 比如下图中这三项,第一次出现的位置是等号的左边,说明他们是我们定义的,我们当然有自主权了,起什么名字我们说了算。 ![](https://box.kancloud.cn/2016-06-13_575e5bef7f8d0.png) 但第一次出现的位置是等号的右边的话,那就不一样了。 这个是其他人规定好的,它怎么规定的,我们就需要怎么使用。 最简单的,比如: ~~~ $JiaoShiBiao = new Teacher; ~~~ Teacher这个名字,是我们在use的时候规定好的,在我们的代码中出现在了等号右边,那么我们就必须按规则执行。 比如: ![](https://box.kancloud.cn/2016-06-13_575e5befa4317.png) 为了提升代码的可读性,我们必须统一代码书写的规范。 我们规定大家必须这样写: ~~~ <?php namespace app\index\controller; // 该文件的位于application\index\controller文件夹 use app\common\model\Teacher; // 教师模型 /** * 教师管理 */ class TeacherController { public function index() { // $Teacher 首写字母大写,说明它是一个对象,更确切一些说明这是基于Teacher这个模型被我们手工实例化得到的,如果存在teacher数据表,它将对应teacher数据表。 $Teacher = new Teacher; // $teachers 以s结尾,表示它是一个数组,数据中的每一项都是一个对象,这个对象基于Teahcer这个模型。 $teachers = $Teacher->select(); // 获取第0个数据 $teacher = $teachers[0]; // 调用上述对象的getData()方法 var_dump($teacher->getData('name')); echo $teacher->getData('name'); return $teacher->getData('name'); } } ~~~ 测试结果如下: ![](https://box.kancloud.cn/bd0214b8fc84f6bb1a8252116a534808_755x107.png) ~~~ git checkout -f step3.2.4.5 ~~~ 执行上述命令后,上述示例代码信息如下: ![](https://box.kancloud.cn/0de925eba2a32d4fe7edba5035d6eca2_1240x556.png)