Symfony是一款优秀的基于MVC架构的PHP框架。今天我在这里给你们分享一下在Symfony中如何建立数据模型和基于RESTful api的搭建。重点是如何建立数据模型哦!php
本教程使用的当前Symfony的LTS版本(Symfony 2.8)html
本教程开始学作日期为2016年11月前端
同窗们本身去官网看啦 -- Symfony官方网站。不管是使用Symfony installer仍是Composer,在中国大陆地区下载速度会比较慢,须要使用科学的上网方法。程序员
MVC这一点我得重点说一下。去任何一家企业面试PHP,90%的同窗会被问到什么是MVC。我相信你们已经背得倒背如流,能够自信满满的回答道:MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制器)。Ok,面试官满意地点点头,进入下一个问题。可是,可是!在实际工做中,我发现不少同窗在MVC框架下依然喜欢使用手写sql语句进行数据库操做。从这点来看,我我的认为这样作的同窗实际上是没有理解到MVC的,下面我就讲一讲我对MVC的理解。web
请你们想象下图所描绘的场景:Peter和John相隔千里,但他们能够经过互联网进行沟通。抽象一点理解,Peter和John是在电脑的世界里面进行沟通。在电脑的世界里面,再也不有现实中距离的限制,千里以外瞬息即到!模型就是现实对象在计算机世界里面的抽象,咱们将Peter和John进行了建模,让他们可以进入计算机的世界。经过controller咱们能够操做计算机世界里的数据模型,经过view咱们能够接收计算机世界里数据模型传达的信息。重点:模型是计算机世界里实实在在的一个对象(Object),模型是现实世界里一个对象在计算机世界里的抽象。面试
一般在电脑的世界里建立的数据模型会转换为数据库里的一条条记录。例如,咱们建立了Peter和John这两个数据模型,那么在数据库的User表里面就会多出下面这两条记录:sql
| id | name | gender | age | | 1 | Peter | male | 30 | | 2 | John | male | 25 |
那么在MVC构架的框架里,这两个数据模型写入数据库的过程一般是这样的:数据库
首先,定义个一个User类(如下代码为伪代码)编程
Class User { public $name; public $gender; public $age; # 下面会有一堆getter和setter }
而后实例化两个User类后端
$peter = new User(); $peter->name = 'Peter'; $peter->gender = 'male'; $peter->age = 30; $peter->save(); // 将Peter存入数据库 $john = new User(); $john->name = 'John'; $john->gender = 'male'; $john->age = 25; $john->save(); // 将John存入数据库
重点来了,有些同窗会说:你这样太麻烦了,两句sql不就搞定了吗?!具体sql语句以下:
INSERT INTO User VALUES ('Peter', 'male', 30) INSERT INTO User VALUES ('John', 'male', 25)
然而,在实际项目当中,数据模型不可能像我刚才说的例子那么简单,若使用手写sql的话,就必需要再写代码实现记录到逻辑的转换,逻辑复杂了,工做量就会很大!可是,若是让你手动建立数据模型,每每你又会以为也是一件麻烦的事情。在有了Symfony以后,数据模型的建立就变得无比简单,接下来我会用实际的例子告诉你怎么在Symfony里面快捷高效的建立数据模型!
好了,假设咱们如今要为一所学校作一个系统,具体需求以下图所示:
老师A有两门课 - 课程A和课程B,学生A和学生B选修了课程A,学生B和学生C选修了课程B。拿到这个需求的时候,千万不要急急忙忙的开始写代码,首先要作的是理清楚老师,课程还有学生之间的逻辑关系,建好数据模型。由于在逻辑都尚未理清的状况下写出来的代码你以为会没有问题吗?!到了那时,你会不停的进行结构大改,把本身搞得筋疲力尽并且代码写的乱七八糟!
好,在了解需求以后,咱们总结出如下两点:
一个老师能够教授多门课程,但一门课程只能有一个授课老师,即老师和课程是一对多的关系,老师须要保存的字段为:姓名,性别,年龄,院系,职位,发表论文数量,课程须要保存的字段为:课程代码,课程名称,课程开始时间,课程结束时间。
一门课程能够被多个学生选修,一个学生也能够选修多门课程,即学生和课程是多对多的关系。学生须要保存的字段为:姓名,性别,年龄,学院,年级。
逻辑关系理清楚了,就能够开始数据建模了。我所谓的数据模型其实很简单,就是类和类与类之间的关系的图表,根据以上需求,咱们能够建立以下对应的数据模型:
上面设计的数据模型是可用的,可是咱们发现一个问题 - 老师和学生有相同的字段,例如姓名,性别等,由于这些都是一我的基本的属性。那么运用OOP的思想,咱们眼前豁然开朗 - 老师和学生其实都是继承了人这个对象。基于这点,咱们能够优化以前的数据模型,让逻辑更加清晰,数据库表里的字段再也不有冗余!优化后的数据模型以下图所示:
到这里,数据模型已经搭建完成,业务逻辑已经整理完毕。能够说已经完成了大半了工做。有的同窗会说:你搞了那么半天,一点代码都没写,没有一点产量。那我只能回答:呵呵。下面我就实际演示一下什么是基于框架下的快速应用开发(RAD),但愿同窗们能够明白我们程序员必定要取巧,想比作更加剧要,否则你一生都是码农,干十年等于积累一年经验!
进入symfony2的根目录,执行以下命令:
php app/console
而后就会列出Symfony2全部的可用命令,咱们重点关注如下两条命令:
generate:bundle
: 建立一个bunlde,你能够把Bundle理解成一个模块 - 处理同一个问题的程序和资源的一个集合。Bundle和Bundle之间应该是低耦合(最好是零耦合),这一点应该很容易理解吧。例如一个是前端Bundle一个是后端Bundle,你要是在前端Bunlde里写了一堆处理后端逻辑的程序,在后端Bundle里加了一些处理前端逻辑的程序。Ok,我建议你应该先去从新理解一下编程的思想。为何说能够作到零耦合呢?由于在Symfony2里面一些多个Bundle都会调用的方法能够注册成Service,而不须要直接到方法所在的Bundle里去调用。
doctrine:generate:entity
: 生成一个模型,entity就是实体模型的意思。
首先使用 php app/console generate:bundle
生成一个名为Backend的Bundle,生成步骤以下图:
接着,使用 php app/console doctrine:generate:entity
生成数据模型,根据提示来一步一步来,很简单的!例以下图演示了如何生成学生数据模型:
进行如上操做以后,能够发如今项目目录结构下已经多了一个叫BackendBundle的文件夹,而且4个数据模型已经建好了,以下图所示:
接着,咱们须要手动调整一下Symfony2帮咱们自动生成的这4个数据模型。
在Symfony2 entity类中,属性名若由多个单词组成,通常使用大写字母分隔,例如:finishAt
。而在数据表中,属性名若由多个单词组成,通常使用下划线分隔,例如:finish_at
。查看自动生成的数据模型的属性,具体格式以下:
/** * @var \DateTime * * @ORM\Column(name="startFrom", type="datetime") */ private $startFrom;
能够发现,每个属性上面都有一堆注释,但这一堆注释不一样于普通的注释,由于它其实就是一段程序,由于咱们在生成BackendBundle的时候Configuration format选择的是annotation。在Symfony2中,路由注册,变量类型定义,数据库属性定义等均可以经过annotation的方式。特别是在路由注册的时候,我特别偏好于annotation的方式,由于它是离散型的,就像一个标签同样贴在了所做用属性或控制器的上面。有其余框架开发经验的同窗们应该知道在其余框架中,注册的路由一般会被写在一个文件里面,好比在Yii2 basic中,全部注册路由都在config/web.php的urlManager中。我曾经接手了一个基于Yii2 basic的项目,一打开路由配置文件,发现里面有一千多行注册路由的代码,并且以前注册这些路由的程序员根本就没正确理解路由的注册,搞得配置文件乱七八糟,每次打开这个文件内心就会有一种很不爽的感受,程序员的洁癖呀!还有一点就是,在团队合做中,这种中心配置文件确定会被不一样的程序员编辑,合并时发生冲突的可能大大增长,若是是Git还好一点,但若是你还用SVN,你就等着哭吧。好了,不吐槽了,我简单讲解一下上面的那一段代码,@var
定义了$startFrom
为DateTime类型,@ORM\\Column
定义了数据表中,属性名为startFrom,类型为datetime。咱们只须要修改一下数据表中的属性名就能够统一数据库表属性命名规范:
/** * @var \DateTime * * @ORM\Column(name="start_from", type="datetime") */ private $startFrom;
验证就是指检查传递过来的数据是否正确,例如以前的startFrom是一个DateTime类型,若是传递一个string类型过来,确定是不正确的。在Symfony2中,在每一个属性的annotation就能够定义验证条件,详情可见 Symfony Validation。下面举两个例子说明一下:
/** * @var string * * 不能为空 * @Assert\NotBlank() * 值必须为Male或Female * @Assert\Choice({"Male", "Female"}) * * @ORM\Column(name="gender", type="string", length=255) */ private $gender;
/** * @var string * * 不能为空 * @Assert\NotBlank() * 值类型需为string型 * @Assert\Type("string") * * @ORM\Column(name="name", type="string", length=255) */ private $name;
在数据模型中,通常咱们会默认加入两个时间戳属性 - createdAt(建立于)和updatedAt(更新于),这两个属性显然不该该是由咱们传值的,而是在记录生成和更新的时候自动将当时的时间存下来。对于这个问题,StofDoctrineExtensionsBundle这个第三方Bundle已经给咱们提供了完美的解决方案。
提示:为何咱们喜欢用流行的第三方框架,应为流行的第三方框架有本身的社区,有一大帮程序员不停的在为社区的壮大献出本身的一份力。不少时候,不少问题其实都是别人已经解决过了的。因此,咱们在遇到一个问题的时候,要多去社区里面找找成熟的解决方案,而不是关上门重复的造轮子。
Symfony官方帮咱们总结出了最火的30个第三方Bundle,有兴趣的能够看一下,说不定会有惊喜哦!
首先,用Composer下载这个第三方Bundle:
composer require stof/doctrine-extensions-bundle
而后,启用和配置Bundle:
// app/AppKernel.php class AppKernel extends Kernel { public function registerBundles() { $bundles = array( // ... new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(), ); // ... } // ... }
// app/config/config.yml stof_doctrine_extensions: default_locale: en_US orm: default: timestampable: true
最后,在createdAt和updatedAt属性上面用Annotation的方法进行配置便可:
use Gedmo\Mapping\Annotation as Gedmo; // ... /** * @var \DateTime * * @Gedmo\Timestampable(on="create") * @ORM\Column(name="created_at", type="datetime") */ private $createdAt; /** * @var \DateTime * * @Gedmo\Timestampable(on="update") * @ORM\Column(name="updated_at", type="datetime") */ private $updatedAt;
以前咱们已经总结出了数据模型之间的关系,如今让咱们再回顾一下:
Teacher与Course的关系为一对多
Student与Course的关系为多对多
Teacher和Student都继承Person类
具体实现以下:
// src/BackendBundle/Entity/Course.php use Doctrine\Common\Collections\ArrayCollection; // ... class Course { // ... /** * @var Teacher * * @ORM\ManyToOne(targetEntity="Teacher", inversedBy="courses") * @ORM\JoinColumn(name="teacher_id", referencedColumnName="id") */ private $teacher; /** * @ORM\ManyToMany(targetEntity="Student", inversedBy="courses") * @ORM\JoinTable(name="course_student") */ private $students; public function __construct() { $this->students = new ArrayCollection(); } // ... }
// src/BackendBundle/Entity/Teacher.php use Doctrine\Common\Collections\ArrayCollection; // ... class Teacher extends Person { // ... /** * @ORM\OneToMany(targetEntity="Course", mappedBy="teacher") */ private $courses; public function __construct() { $this->courses = new ArrayCollection(); } // ... }
// src/BackendBundle/Entity/Student.php use Doctrine\Common\Collections\ArrayCollection; // ... class Student extends Person { // ... /** * @ORM\ManyToMany(targetEntity="Course", mappedBy="students") */ private $courses; public function __construct() { $this->courses = new ArrayCollection(); } // ... }
// src/BackendBundle/Entity/Person.php /** * Person * * @ORM\Table(name="person") * @ORM\Entity(repositoryClass="BackendBundle\Repository\PersonRepository") * @ORM\InheritanceType("JOINED") */ abstract class Person { // ... }
而后运行php app/console doctrine:generate:entities BackendBundle
生成刚才咱们加入的自定义属性的getter和setter。以后,运行php app/console doctrine:schema:update --force
,数据表就已经成功生成了,赶快去数据库里面看看吧。