第八章-模型层内部
到目前为止,大部分讨论都集中在构建页面以及处理请求和响应上。但是web应用程序的业务逻辑主要依赖于它的数据模型。ob娱乐下载Symfony的默认模型组件是基于一个被称为Propel项目的对象/关系映射层(http://propel.phpdb.org/).在symfob娱乐下载ony应用程序中,您可以访问存储在数据库中的数据并通过对象对其进行修改;您从未显式地寻址数据库。这保持了高度的抽象和可移植性。
本章介绍如何创建对象数据模型,以及如何在Propel中访问和修改数据。同时还演示了在Symfony中如何集成Propel。ob娱乐下载
为什么使用ORM和抽象层?
数据库是关系型的。PHP 5和symfob娱乐下载ony是面向对象的。为了在面向对象上下文中最有效地访问数据库,需要一个将对象逻辑转换为关系逻辑的接口。正如在第1章中所解释的,该接口称为对象-关系映射(ORM),它由提供数据访问权并将业务规则保留在自身中的对象组成。
ORM的主要好处是可重用性,允许从应用程序的不同部分,甚至从不同的应用程序调用数据对象的方法。ORM层还封装了数据逻辑——例如,根据贡献的数量和这些贡献的受欢迎程度计算论坛用户评级。当一个页面需要显示这样的用户评分时,它只需调用数据模型的一个方法,而不需要考虑计算的细节。如果之后计算发生变化,您只需要修改模型中的评级方法,而应用程序的其余部分保持不变。
使用对象而不是记录,使用类而不是表,还有另一个好处:它们允许您向对象添加不一定与表中的列匹配的新访问器。例如,如果您有一个名为客户端
其中有两个字段first_name
而且last_name
,你可能只需要一个的名字
.在面向对象的世界中,只需向对象中添加新的访问器方法即可客户端
如清单8-1所示。从应用的角度来看,两者并无区别FirstName
,姓
,的名字
的属性客户端
类。只有类本身可以确定哪些属性对应于数据库列。
清单8-1 -访问器屏蔽了模型类中的实际表结构
公共函数getName(){返回这个美元->getFirstName().' '.这个美元->getLastName();}
所有重复的数据访问函数和数据本身的业务逻辑都可以保存在这样的对象中。假设你有一个ShoppingCart
你所在的班级项目
(都是对象)。要获得购物车中用于结帐的全部金额,可以编写一个自定义方法来封装实际计算,如清单8-2所示。
清单8-2 - Accessors屏蔽数据逻辑
公共函数getTotal(){总美元=0;foreach(这个美元->getItems()作为美元的项目){总美元+ =美元的项目->getPrice()*美元的项目->getQuantity();}返回总美元;}
在构建数据访问过程时,还需要考虑另一个重要问题:数据库供应商使用不同的SQL语法变体。切换到另一个数据库管理系统(DBMS)将迫使您重写为前一个数据库管理系统设计的部分SQL查询。如果您使用独立于数据库的语法构建查询,并将实际的SQL转换留给第三方组件,那么您可以轻松地切换数据库系统。这是数据库抽象层的目标。它迫使您使用特定的语法进行查询,并执行符合DBMS细节和优化SQL代码的肮脏工作。
抽象层的主要好处是可移植性,因为它使得切换到另一个数据库成为可能,即使是在项目中间。假设您需要为一个应用程序编写一个快速原型,但是客户还没有决定哪个数据库系统最适合他的需求。例如,您可以开始使用SQLite构建应用程序,然后在客户端准备决定时切换到MySQL、PostgreSQL或Oracle。只需更改配置文件中的一行,它就可以工作。
ob娱乐下载Symfony使用Propel作为ORM,而Propel使用Creole进行数据库抽象。这两个第三方组件都是由Propel团队开发的,可以无缝集成到symfony中,您可以将它们视为框架的一部分。ob娱乐下载本章所述的它们的句法和惯例都经过了调整,使它们尽可能少地区别于symfony。ob娱乐下载
请注意
在symfob娱乐下载ony项目中,所有应用程序共享相同的模型。这就是项目级别的全部意义:重新组合依赖公共业务规则的应用程序。这就是模型独立于应用程序的原因,并且模型文件存储在lib /模型/
目录在项目的根目录。
ob娱乐下载Symfony的数据库模式
为了创建symfony将使用的数据对象模型,您需要将数据库拥有的任何关系模型转换为对象数据模ob娱乐下载型。ORM需要关系模型的描述来进行映射,这称为模式。在模式中,定义表、表之间的关系以及表列的特征。
ob娱乐下载Symfony的模式语法使用YAML格式。的schema.yml
文件必须位于myproject / config /
目录中。
请注意
ob娱乐下载Symfony还理解Propel原生XML模式格式,如“超越模式”中所述。yml:本章后面的schema.xml部分。
模式的例子
如何将数据库结构转换为模式?举个例子是理解它的最好方式。假设你有一个博客数据库,有两个表:blog_article
而且blog_comment
,结构如图8-1所示。
图8-1博客数据库表结构
相关的schema.yml
文件应该类似于清单8-3。
清单8-3 -示例schema.yml
propel: blog_article: _attributes: {phpName: Article} id: title: varchar(255) content: longvarchar created_at: blog_comment: _attributes: {phpName: Comment} id: article_id: author: varchar(255) content: longvarchar created_at:
注意,数据库本身的名称(博客
)没有出现在schema.yml
文件。相反,数据库是在连接名(推动
在这个例子中)。这是因为实际的连接设置可能取决于应用程序运行的环境。例如,当您在开发环境中运行应用程序时,您将访问开发数据库(可能会访问)blog_dev
),但是使用与生产数据库相同的模式。连接设置将在databases.yml
在本章后面的“数据库连接”一节中描述。该模式不包含任何到设置的详细连接,仅包含一个连接名称,以维护数据库抽象。
基本模式语法
在一个schema.yml
文件中,第一个键表示连接名称。它可以包含几个表,每个表有一组列。根据YAML语法,键以冒号结束,结构通过缩进(一个或多个空格,但没有表)显示。
表可以具有特殊的属性,包括phpName
(将要生成的类的名称)。如果你不提phpName
对于一个表,symfonyob娱乐下载根据camelCase版本的表名创建它。
提示
camelCase约定去掉单词中的下划线,并将内部单词的第一个字母大写。的默认camelCase版本blog_article
而且blog_comment
是BlogArticle
而且BlogComment
.这个大会的名字来自于一个长单词中出现的大写字母,让人联想到骆驼的驼峰。
表包含列。列值可以用三种不同的方式定义:
- 如果您什么都没有定义,symfony将根据列名ob娱乐下载和一些约定猜测最佳属性,这些约定将在本章后面的“空列”部分中描述。例如,
id
清单8-3中的列不需要定义。ob娱乐下载Symfony将使它成为一个自动递增的整数,表的主键。中的article_idblog_comment
表的外键将被理解为blog_article
表(以。结尾的列_id
被认为是外键,根据列名的第一部分自动确定相关的表)。列被称为created_at
自动设置为时间戳
类型。对于所有这些列,不需要指定任何类型。这是其中一个原因schema.yml
很容易写。 - 如果只定义了一个属性,那么它就是列类型。ob娱乐下载Symfony理解常见的列类型:
布尔
,整数
,浮动
,日期
,varchar(大小)
,用longvarchar
(例如,转换为文本
在MySQL中),等等。对于超过256个字符的文本内容,需要使用用longvarchar
类型,没有大小(但在MySQL中不能超过65KB)。注意日期
而且时间戳
类型有Unix日期的通常限制,不能设置为1970-01-01之前的日期。由于您可能需要设置更早的日期(例如,出生日期),可以使用“Unix之前”的日期格式bu_date
而且bu_timestamp
. - 如果需要定义其他列属性(如默认值、required等),则应该将列属性编写为一组
键:值
.这个扩展的模式语法将在本章后面描述。
列也可以有phpName
属性,它是名称的大写版本(Id
,标题
,内容
,等等)并且在大多数情况下不需要重写。
表还可以包含显式的外键和索引,以及一些特定于数据库的结构定义。请参阅本章后面的“扩展模式语法”一节了解更多信息。
模型类
该模式用于构建ORM层的模型类。为了节省执行时间,生成这些类的命令行任务为propel-build-model
.
> ob娱乐下载symfony推进构建模型
提示
在构建模型之后,必须记得清除symfony的内部缓存ob娱乐下载ob娱乐下载symfony cc
因此sob娱乐下载ymfony可以找到您新创建的模型。
类型中的基本数据模型类的生成将启动模式分析lib /模型/ om /
项目目录:
BaseArticle.php
BaseArticlePeer.php
BaseComment.php
BaseCommentPeer.php
此外,将在中创建实际数据模型类lib /模型/
:
Article.php
ArticlePeer.php
Comment.php
CommentPeer.php
您只定义了两个表,但最终得到了8个文件。没有什么不对,但它值得一些解释。
基类和自定义类
为什么将数据对象模型的两个版本保存在两个不同的目录中?
您可能需要向模型对象添加自定义方法和属性getName ()
方法)。但是随着项目的发展,您还将添加表或列。每当你改变schema.yml
文件中,您需要通过对push -build-model进行新的调用来重新生成对象模型类。如果您的自定义方法是在实际生成的类中编写的,那么它们将在每次生成后被删除。
的基地
保存在lib /模型/ om /
目录是直接从模式生成的。您不应该修改它们,因为模型的每次新构建都会完全删除这些文件。
另一方面,自定义对象类,保存在lib /模型/
目录,实际上继承自基地
的人。当propel-build-model
任务在现有模型上调用时,这些类不会被修改。这就是你可以添加自定义方法的地方。
类的第一次调用所创建的自定义模型类的示例,如清单8-4所示propel-build-model
的任务。
清单8-4 -样本模型类文件,在lib /模型/ Article.php
类文章扩展BaseArticle{}
类的所有方法BaseArticle
类,但模式中的修改不会影响它。
自定义类扩展基类的机制允许您开始编码,即使不知道数据库的最终关系模型。相关的文件结构使得模型既可定制又可进化。
对象和对等类
文章
而且评论
是表示数据库中记录的对象类。它们提供对记录的列和相关记录的访问权。这意味着您可以通过调用article对象的方法来知道文章的标题,如清单8-5所示的示例所示。
清单8-5 -对象类中有用于记录列的getter
美元的文章=新文章();...美元的标题=美元的文章->getTitle();
ArticlePeer
而且CommentPeer
是同辈类;也就是说,包含对表进行操作的静态方法的类。它们提供了从表中检索记录的方法。它们的方法通常返回一个对象或相关对象类的对象集合,如清单8-6所示。
清单8-6 -检索记录的静态方法在对等类中可用
美元的文章= ArticlePeer::retrieveByPks(数组(123,124,125));// $articles是Article类对象的数组
请注意
从数据模型的角度来看,不可能有任何对等对象。这就是为什么对等类的方法使用::
(用于静态方法调用),而不是通常的方法->
(用于实例方法调用)。
因此,在基类和自定义版本中组合对象类和对等类会导致在模式中描述的每个表生成四个类。事实上,还有第五个类是在lib /模型/地图/
目录,其中包含运行时环境所需的关于表的元数据信息。但是由于您可能永远不会更改这个类,所以您可以忘记它。
访问数据
在syob娱乐下载mfony中,数据是通过对象访问的。如果您习惯于关系模型并使用SQL来检索和修改数据,那么对象模型方法可能看起来很复杂。但是,一旦您体验过面向对象的数据访问功能,您可能会非常喜欢它。
但首先,让我们确保我们拥有相同的词汇。关系数据模型和对象数据模型使用类似的概念,但它们各自有自己的命名法:
关系 | 面向对象的 |
---|---|
表格 | 类 |
行,记录 | 对象 |
场,列 | 财产 |
检索列值
类中定义的ob娱乐下载每个表创建一个基对象类schema.yml
.这些类中的每一个都带有基于列定义的默认构造函数、访问器和突变器新
,getXXX ()
,setXXX ()
方法可以帮助创建对象并访问对象属性,如清单8-7所示。
清单8-7 -生成的对象类方法
美元的文章=新文章();美元的文章->setTitle(“我的第一篇文章”);美元的文章->setContent(“这是我的第一篇文章。\ n希望你喜欢!”);美元的标题=美元的文章->getTitle();美元的内容=美元的文章->getContent();
请注意
调用生成的对象类文章
,即phpName
赠予blog_article
表格如果phpName
如果没有在模式中定义,类会被调用吗BlogArticle
.访问器和突变器使用列名的驼峰式变体,因此getTitle ()
方法的值标题
列。
要同时设置多个字段,可以使用fromArray ()
方法,也为每个对象类生成,如清单8-8所示。
清单8-8 - ThefromArray ()
方法是一个多重Setter
美元的文章->fromArray(数组(“标题”= >“我的第一篇文章”,“内容”= >“这是我的第一篇文章。\ n希望你喜欢!”,));
检索相关记录
的article_id
中的列。blog_comment
属性的外键blog_article
表格每条评论都与一篇文章相关,一篇文章可以有很多评论。生成的类包含五个以面向对象的方式转换这种关系的方法,如下所示:
评论- > getArticle ()
:获取相关信息文章
对象评论- > getArticleId ()
:获取相关的ID文章
对象评论- > setArticle ($)
:定义相关的文章
对象评论- > setArticleId ($ id)
:定义相关的文章
从ID中获取对象文章- > getComments ()
:获取相关信息评论
对象
的getArticleId ()
而且setArticleId ()
方法表明,您可以将article_id列视为常规列并手动设置关系,但它们不是很有趣。面向对象方法的好处在其他三种方法中更为明显。清单8-9显示了如何使用生成的setter。
清单8-9 -外键被翻译成一个特殊Setter
美元的评论=新评论();美元的评论->setAuthor(“史蒂夫。”);美元的评论->setContent(“天哪,伙计,你太棒了:有史以来最好的文章!”);//将此注释附加到前面的$article对象美元的评论->setArticle(美元的文章);//其他语法//只有当对象已经保存在数据库中时才有意义美元的评论->setArticleId(美元的文章->getId());
清单8-10显示了如何使用生成的getter。它还演示了如何链接模型对象上的方法调用。
清单8-10 -外键被转换为特殊getter
//多对一关系回声美元的评论->getArticle()->getTitle();我的第一篇文章回声美元的评论->getArticle()->getContent();这是我的第一篇文章。希望你喜欢!//一对多关系美元的评论=美元的文章->getComments();
的getArticle ()
方法返回类的对象文章
,这得益于getTitle ()
访问器。这比自己进行连接要好得多,后者可能需要几行代码(从评论- > getArticleId ()
调用)。
的美元的评论
清单8-10中的variable包含class对象的数组评论
.你可以用评论[0]美元
或对集合进行迭代Foreach ($comments为$comment)
.
请注意
根据约定,模型中的对象使用单一名称定义,现在您可以理解其中的原因了。类中定义的外键blog_comment
表将导致创建getComments ()
方法,通过添加对象来命名年代
到评论
对象名称。如果给模型对象一个复数名称,生成的方法将被命名为getCommentss ()
,这没有道理。
保存和删除数据
通过调用新
构造函数中创建了一个新对象,但没有创建实际记录blog_article
表格修改对象对数据库也没有影响。为了将数据保存到数据库中,需要调用save ()
对象的方法。
美元的文章->保存();
ORM足够智能,可以检测对象之间的关系,因此可以保存美元的文章
对象还保存相关的美元的评论
对象。它还知道保存的对象在数据库中是否有一个现有的对应对象,因此调用save ()
在SQL中有时由插入
,有时由一个更新
.方法自动设置主键save ()
方法,以便保存后可以使用文章- > getId ()
.
提示
你可以通过调用isNew()来检查一个对象是否是新的。如果您想知道一个对象是否已被修改并值得保存,请调用它isModified ()
方法。
如果你读到你文章的评论,你可能会改变在互联网上发表文章的兴趣。如果您不喜欢文章评论者的讽刺意味,可以使用delete ()
方法,如清单8-11所示。
清单8-11 -使用delete ()
方法
foreach(美元的文章->getComments()作为美元的评论){美元的评论->删除();}
提示
即使打了电话delete ()
方法时,对象在请求结束前都保持可用。方法确定是否删除了数据库中的对象isDeleted ()
方法。
按主键检索记录
如果知道特定记录的主键,请使用retrieveByPk ()
对等类的类方法来获取相关对象。
美元的文章= ArticlePeer::retrieveByPk(7);
的schema.yml
文件定义id
属性的主键blog_article
表,所以这个语句实际上会返回条目id
7.由于使用了主键,您知道将只返回一条记录;的美元的文章
变量包含class的对象文章
.
在某些情况下,一个主键可能包含多个列。在这些情况下,retrieveByPK ()
方法接受多个参数,每个主键列对应一个参数。
您还可以根据它们的主键选择多个对象,通过调用生成的retrieveByPKs ()
方法,该方法需要一个主键数组作为参数。
使用条件检索记录
方法检索多条记录时,需要调用doSelect ()
与要检索的对象对应的对等类的方法。例如,检索类的对象文章
,叫ArticlePeer: doSelect ()
.
的第一个参数doSelect ()
方法是类的对象标准
,这是一个简单的查询定义类,为了数据库抽象,没有使用SQL定义。
一个空标准
返回类的所有对象。例如,清单8-12所示的代码检索所有文章。
清单8-12 -通过Criteria检索记录doSelect ()
——空标准
$ c=新标准();美元的文章= ArticlePeer::doSelect($ c);//将导致以下SQL查询选择blog_article。ID, blog_article。标题、blog_article。内容,blog_article.FROM blog_article;
侧边栏
保湿
呼唤:: doSelect ()
实际上比简单的SQL查询强大得多。首先,SQL针对您选择的DBMS进行了优化。对象传递的任何值标准
在集成到SQL代码之前进行转义,以防止SQL注入风险。第三,该方法返回一个对象数组,而不是结果集。ORM根据数据库结果集自动创建和填充对象。这个过程被称为水化。
对于更复杂的对象选择,需要等价于WHERE、ORDER BY、GROUP BY和其他SQL语句。的标准
对象具有用于所有这些条件的方法和参数。例如,要获取Steve编写的所有注释(按日期排序),可以构建一个标准
如清单8-13所示。
清单8-13 -通过Criteria检索记录doSelect ()
——有条件的准则
$ c=新标准();$ c->添加(CommentPeer::作者,“史蒂夫。”);$ c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);美元的评论= CommentPeer::doSelect($ c);//将导致以下SQL查询选择blog_comment。ARTICLE_ID,blog_comment。作者,blog_comment。内容,blog_comment。CREATED_ATFROM blog_comment WHERE blog_comment.author =“史蒂夫。”由blog_comment订购。CREATED_AT ASC;
作为参数传递给add()方法的类常量引用属性名。它们以列名的大写版本命名。例如,处理内容
的列blog_article
表,使用ArticlePeer:内容
类常量。
请注意
为什么要使用CommentPeer:作者
而不是blog_comment。作者
,这是它将在SQL查询中输出的方式?假设您需要将作者字段的名称更改为贡献者
在数据库中。如果你用过blog_comment。作者
,您将不得不在对模型的每次调用中更改它。另一方面,通过使用CommentPeer:作者
中的列名,只需更改schema.yml
文件,保持phpName
作为作者
,并重新构建模型。
的SQL语法比较如表8-1所示标准
对象的语法。
表8-1 - SQL和标准对象语法
SQL | 标准 |
---|---|
WHERE列=值 |
- >添加(列值); |
WHERE列<>值 |
->add(column, value, Criteria::NOT_EQUAL); |
其他比较操作符 | |
>, < |
标准::GREATER_THAN,标准::LESS_THAN |
> =, < = |
标准::GREATER_EQUAL,标准::LESS_EQUAL |
是空的,不是空的 |
标准::ISNULL,标准::ISNOTNULL |
喜欢,我喜欢 |
标准:标准::我喜欢 |
In,而不是In |
标准:标准:::NOT_IN |
其他SQL关键字 | |
按列ASC订购 |
- > addAscendingOrderByColumn(列); |
按列描述顺序 |
- > addDescendingOrderByColumn(列); |
限制限制 |
- > setLimit(限制) |
抵消抵消 |
- > setOffset(抵消) |
FROM table1, table2 WHERE table1。Col1 = table2.col2 |
- > addJoin (col1, col2) |
从table1左连接table2到table1。Col1 = table2.col2 |
->addJoin(col1, col2, Criteria::LEFT_JOIN) |
从table1右连接table2到table1。Col1 = table2.col2 |
->addJoin(col1, col2, Criteria::RIGHT_JOIN) |
提示
发现和理解生成类中哪些方法可用的最好方法是查看基地
文件lib /模型/ om /
生成后的文件夹。方法名非常显式,但如果需要对其进行更多注释,请设置propel.builder.addComments
参数真正的
在配置/ propel.ini
存档并重新构建模型。
清单8-14显示了另一个示例标准
有多种条件。它检索Steve对包含单词“enjoy”的文章的所有评论,按日期排序。
清单8-14 -通过Criteria检索记录的另一个示例doSelect ()
——有条件的准则
$ c=新标准();$ c->添加(CommentPeer::作者,“史蒂夫。”);$ c->addJoin(CommentPeer::ARTICLE_ID, ArticlePeer::ID);$ c->添加(ArticlePeer::内容,“%享受%”标准::就像);$ c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);美元的评论= CommentPeer::doSelect($ c);//将导致以下SQL查询选择blog_comment。ID, blog_comment。ARTICLE_ID,blog_comment。作者,blog_comment。内容,blog_comment。CREATED_ATFROM blog_comment, blog_article WHERE blog_comment.AUTHOR =“史蒂夫。”和blog_article。内容如“%享受%”和blog_comment。ARTICLE_ID=blog_article.ID ORDER BY blog_comment。CREATED_AT ASC
就像SQL是一种允许您构建非常复杂查询的简单语言一样,Criteria对象可以处理任何复杂级别的条件。但是,由于许多开发人员在将条件转换为面向对象逻辑之前首先考虑SQL,因此标准
物体最初可能很难理解。理解它的最好方法是从示例和示例应用程序中学习。例如,sob娱乐下载ymfony项目的网站上充满了标准
建立例子,在很多方面给你启发。
除了doSelect ()
方法,每个对等类都有一个doCount ()
方法,该方法只计算满足作为参数传递的条件的记录数量,并以整数形式返回计数。由于没有对象返回,在这种情况下,水化过程不会发生doCount ()
方法的速度比doSelect ()
.
对等类也提供doDelete ()
,doInsert ()
,doUpdate ()
方法,这些方法都需要一个标准
作为一个参数。这些方法允许您发布删除
,插入
,更新
对数据库的查询。查看模型中生成的对等类,了解有关这些Propel方法的更多细节。
最后,如果您只想返回第一个对象,则替换doSelect ()
与一个doSelectOne ()
调用。当你知道a标准
将只返回一个结果,优点是该方法返回一个对象,而不是一个对象数组。
提示
当一个doSelect ()
查询返回大量结果,您可能希望在响应中只显示其中的一个子集。ob娱乐下载Symfony提供了一个名为sfPropelPager的寻呼机类,它可以自动对结果进行分页。查看寻呼机文档欧宝官网下载app/食谱/ 1 _0 / en /寻呼机获取更多信息和使用示例。
使用原始SQL查询
有时,您不想检索对象,而只想获得由数据库计算的综合结果。例如,要获得所有文章的最新创建日期,检索所有文章并在数组上循环是没有意义的。您更倾向于要求数据库只返回结果,因为它将跳过对象水合过程。
另一方面,您不希望直接调用PHP命令进行数据库管理,因为这样会失去数据库抽象的好处。这意味着您需要绕过ORM (Propel),而不是数据库抽象(Creole)。
使用Creole查询数据库需要执行以下操作:
- 获取数据库连接。
- 构建查询字符串。
- 用它创建一个语句。
- 迭代语句执行产生的结果集。
如果这看起来像胡言乱语,那么清单8-15中的代码可能更明确。
清单8-15 - Creole自定义SQL查询
美元的连接=推动:getConnection();美元的查询=“选择马克斯(?)马克斯来自哪里?”;美元的声明=美元的连接->prepareStatement(美元的查询);美元的声明->setString(1, ArticlePeer::CREATED_AT);美元的声明->setString(2, ArticlePeer::TABLE_NAME);$ resultset=美元的声明->executeQuery();$ resultset->下一个();美元最大=$ resultset->getInt(“马克斯”);
就像Propel选择一样,当你第一次开始使用Creole查询时,它们也很棘手。同样,来自现有应用程序和教程的示例将向您展示正确的方法。
谨慎
如果您想绕过这个过程,直接访问数据库,就有可能失去Creole提供的安全性和抽象性。用Creole的方式来做会比较长,但它迫使您使用良好的实践来保证应用程序的性能、可移植性和安全性。对于包含来自不可信源(例如Internet用户)的参数的查询尤其如此。Creole执行所有必要的转义并保护数据库。直接访问数据库会使您面临sql注入攻击的风险。
使用特殊的日期列
通常,当一个表中有一个名为created_at
,它用于存储记录创建日期的时间戳。这同样适用于updated_at列,每次更新记录本身时,都要将这些列更新为当前时间的值。
好消息是,symf欧宝平台是合法的吗ony将识别这些列的名称并ob娱乐下载为您处理它们的更新。您不需要手动设置created_at
而且updated_at
列;它们将自动更新,如清单8-16所示。同样的方法也适用于列命名created_on
而且updated_on
.
清单8-16 -created_at
而且updated_at
自动处理列
美元的评论=新评论();美元的评论->setAuthor(“史蒂夫。”);美元的评论->保存();//显示创建日期回声美元的评论->getCreatedAt();= >[日期数据库的INSERT操作]
此外,日期列的getter接受日期格式作为参数:
回声美元的评论->getCreatedAt(“Y-m-d”);
侧边栏
重构到数据层
在开发symfony项目时,通常从在ob娱乐下载操作中编写域逻辑代码开始。但是数据库查询和模型操作不应该存储在控制器层中。因此,所有与数据相关的逻辑都应该移动到模型层。每当您需要在操作中的多个地方执行相同的请求时,请考虑将相关代码传输到模型。它有助于保持动作的简短和可读性。
例如,假设在一个博客中检索给定标记(作为请求参数传递)的10篇最受欢迎的文章所需的代码。这些代码不应该在操作中,而应该在模型中。事实上,如果你需要在模板中显示这个列表,动作应该简单地像这样:
公共函数executeShowPopularArticlesForTag(){美元的标记= TagPeer::retrieveByName(这个美元->getRequestParameter(“标签”));这个美元->forward404Unless(美元的标记);这个美元->文章=美元的标记->getPopularArticles(10);}
该操作创建了一个class对象标签
从请求参数。然后查询数据库所需的所有代码都位于getPopularArticles ()
类的方法。它使操作更具可读性,并且模型代码可以很容易地在另一个操作中重用。
将代码移动到更合适的位置是重构技术之一。如果您经常这样做,您的代码将易于维护,并易于其他开发人员理解。关于何时对数据层进行重构的一条经验法则是,动作的代码应该很少包含超过10行PHP代码。
数据库连接
数据模型独立于所使用的数据库,但您肯定会使用数据库。symfony向项目数据库发送请求所需的最少信息是名称、访问代码和数据库ob娱乐下载类型。这些连接设置应在databases.yml
文件位于配置/
目录中。清单8-17显示了这样一个文件的示例。
清单8-17 -数据库连接设置示例,在myproject / config / databases.yml
prod: propel: param: hostspec: mydataserver username: myusername password: xxxxxxxxxx all: propel: class: sfPropelDatabase param: phptype: mysql #数据库供应商hostspec: localhost数据库:博客用户名:登录密码:passwd port: 80 encoding: utf8 #创建表的默认字符集persistent: true #使用持久连接
连接设置与环境有关。属性的不同设置刺激
,dev
,测验
环境,或应用程序中的任何其他环境。通过在特定于应用程序的文件中设置不同的值,也可以在每个应用程序中覆盖此配置应用程序/ myapp / config / databases.yml
.例如,您可以使用这种方法为前端应用程序和后端应用程序设置不同的安全策略,并在数据库中定义几个具有不同权限的数据库用户来处理这个问题。
对于每个环境,您都可以定义许多连接。每个连接都引用一个用相同名称标记的模式。在清单8-17的示例中,propel连接指的是推动
清单8-3中的schema。
的允许值phptype
参数为Creole支持的数据库系统:
mysql
该软件
pgsql
sqlite
甲骨文
hostspec
,数据库
,用户名
,密码
通常的数据库连接设置。它们也可以以更短的方式写成数据源名称(DSN)。清单8-18等价于全部:
部分。
清单8-18 -简写数据库连接设置
all: propel: class: sfPropelDatabase param: dsn: mysql://login:passwd@localhost/blog
如果使用SQLite数据库,则hostspec
参数必须设置为数据库文件的路径。例如,如果你保持你的博客数据库数据/ blog.db
,databases.yml
文件将如清单8-19所示。
清单8-19 - SQLite的数据库连接设置使用文件路径作为主机
all: propel: class: sfPropelDatabase param: phptype: sqlite database: %SF_DATA_DIR%/blog.db
扩展模型
生成的模型方法很棒,但通常还不够。一旦实现了自己的业务逻辑,就需要通过添加新方法或覆盖现有方法来扩展它。
添加新方法
方法中生成的空模型类可以添加新方法lib /模型/
目录中。使用这个美元
调用当前对象的方法,并使用自我::
调用当前类的静态方法。类的方法继承基地
类位于lib /模型/ om /
目录中。
例如,对于文章
对象生成基于清单8-3,您可以添加一个魔术__toString ()
方法,以便回显类的对象文章
显示标题,如清单8-20所示。
清单8-20 -定制模型,在lib /模型/ Article.php
类文章扩展BaseArticle{公共函数__toString(){返回这个美元->getTitle();// getTitle()继承自BaseArticle}}
您还可以扩展对等类——例如,添加一个方法来检索按创建日期排序的所有文章,如清单8-21所示。
清单8-21 -自定义模型,在lib /模型/ ArticlePeer.php
类ArticlePeer扩展BaseArticlePeer{公共静态函数getAllOrderedByDate(){$ c=新标准();$ c->addAscendingOrderByColumn(自我::CREATED_AT);返回自我::doSelect($ c);}}
新方法可用的方式与生成的方法相同,如清单8-22所示。
清单8-22 -使用自定义模型方法就像使用生成的方法
foreach(ArticlePeer::getAllOrderedByDate()作为美元的文章){回声美元的文章;//调用神奇的__toString()方法}
重写现有方法
中的一些生成的方法基地
类不符合您的要求,您仍然可以在自定义类中覆盖它们。只需确保使用相同的方法签名(即相同数量的参数)。
例如,文章- > getComments ()
方法返回的数组评论
对象,没有特别的顺序。如果希望结果按创建日期排序,最新的注释放在前面,则重写getComments ()
方法,如清单8-23所示。要知道,原来getComments ()
方法(在lib /模型/ om / BaseArticle.php
)需要一个标准值和一个连接值作为参数,所以你的函数也必须这样做。
清单8-23 -覆盖现有模型方法,在lib /模型/ Article.php
公共函数getComments(美元标准=零,反对美元=零){如果(is_null(美元标准)){美元标准=新标准();}其他的{//在PHP5中对象是通过引用传递的,所以为了避免修改原始对象,必须克隆它美元标准=克隆美元标准;}美元标准->addDescendingOrderByColumn(CommentPeer::CREATED_AT);返回父::getComments(美元标准,反对美元);}
自定义方法最终调用父基类中的一个,这是很好的实践。但是,您可以完全绕过它并返回您想要的结果。
使用模型行为
一些模型修改是通用的,可以重用。例如,使模型对象可排序的方法和防止并发对象保存之间冲突的乐观锁都是可以添加到许多类中的通用扩展。
ob娱乐下载Symfony将这些扩展打包成行为。行为是外部类,为类建模提供了额外的方法。模型类已经包含钩子,symfony知道如何通过的方式扩展它们ob娱乐下载sfMixer
(详见第十七章)。
属性中的一个设置,才能在模型类中启用行为配置/ propel.ini
文件:
push .builder. addbehaviors = true //默认值为false
symfony中没有默认绑定的行为,但是可以通过插件安装它们。ob娱乐下载一旦安装了行为插件,您就可以用一行将行为分配给一个类。例如,如果在应用程序中安装了sfPropelParanoidBehaviorPlugin,则可以扩展文章
类的末尾添加以下内容,从而使用此行为初始化Article.class.php
:
sfPropelBehavior::添加(“文章”,数组(“偏执”= >数组(“列”= >“deleted_at”)));
重建模型后,删除文章
对象将保留在数据库中,对使用ORM的查询不可见,除非您临时禁用的行为sfPropelParanoidBehavior:禁用()
.
检查wiki中的symfony插件列ob娱乐下载表以查找行为(http://trac.ob娱乐下载symfony-project.org/wiki/SymfonyPlugins#Behaviors).每种软件都有自己的文档和安装指南。欧宝官网下载app
扩展模式语法
一个schema.yml
文件可以很简单,如清单8-3所示。但是关系模型通常很复杂。这就是为什么模式具有广泛的语法,能够处理几乎所有的情况。
属性
连接和表可以具有特定的属性,如清单8-24所示。它们被放置在_attributes
关键。
清单8-24 -连接和表的属性
propel: _attributes: {noXsd: false, defaultIdMethod: none, package: lib。model} blog_article: _attributes: {phpName:文章}
您可能希望在代码生成之前对模式进行验证。要做到这一点,请禁用noXSD
属性。连接还支持defaultIdMethod
属性。如果没有提供,则将使用数据库生成id的本机方法——例如,自动增量
MySQL,或序列
PostgreSQL。另一个可能的值是没有一个
.
的包
属性类似于名称空间;它决定了生成的类存储的路径。默认为lib /模型/
,但是您可以更改它以在子包中组织您的模型。例如,如果不希望将核心业务类和定义数据库存储统计引擎的类混合在同一个目录中,那么可以使用lib.model.business
而且lib.model.stats
包。
你们已经看到了phpName
属性,用于设置映射到表的生成类的名称。
包含本地化内容的表(也就是说,在一个相关表中有几个版本的内容,用于国际化)还具有两个额外的属性(详情请参阅第13章),如清单8-25所示。
清单8-25 - i18n表的属性
propel: blog_article: _attributes: {isI18N: true, i18nTable: db_group_i18n}
侧边栏
处理多个模式
每个应用程序可以有多个模式。ob娱乐下载Symfony将考虑以。结尾的每个文件schema.yml
或schema.xml
在配置/
文件夹中。如果您的应用程序有很多表,或者有些表不共享同一个连接,您会发现这种方法非常有用。
考虑以下两种模式:
//在config/business-schema中。yml propel: blog_article: _attributes: {phpName: Article} id: title: varchar(50) //在config/stats-schema中。yml propel: stats_hit: _attributes: {phpName: Hit} id: resource: varchar(100) created_at:
两个模式共享相同的连接(推动
),以及文章
而且打击
类将在相同的目录下生成lib /模型/
目录中。就好像只编写了一个模式一样。
您还可以使用不同的连接使用不同的模式(例如,推动
而且propel_bis
的定义databases.yml
),并在子目录中组织生成的类:
//在config/business-schema中。yml propel: blog_article: _attributes: {phpName: Article, package: lib.model.business} id: title: varchar(50) //在config/stats-schema中。yml propel_bis: stats_hit: _attributes: {phpName: Hit, package: lib.model.stat} id: resource: varchar(100) created_at:
许多应用程序使用多个模式。特别是,一些插件有自己的模式和包,以避免混淆你自己的类(详情请参阅第17章)。
列的细节
基本语法为您提供了两种选择:让symfony从其名称推断列的特征(通过给出一个空值),或ob娱乐下载者使用类型关键字之一定义类型。清单8-26演示了这些选择。
清单8-26 -基本列属性
propel: blog_article: id: #让symfob娱乐下载ony完成工作标题:varchar(50) #自己指定类型
但是您可以为一列定义更多内容。如果这样做,您将需要将列设置定义为一个关联数组,如清单8-27所示。
清单8-27 -复杂列属性
propel: blog_article: id: {type: integer, required: true, primaryKey: true, autoIncrement: true} name: {type: varchar(50), default: foobar, index: true} group_id: {type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade}
列参数如下:
类型
:列类型。选项是布尔
,非常小的整数
,短整型
,整数
,长整型数字
,双
,浮动
,真正的
,小数
,字符
,varchar(大小)
,用longvarchar
,日期
,时间
,时间戳
,bu_date
,bu_timestamp
,团
,clob
.要求
:布尔。设置为真正的
如果您希望该列是必需的。大小
:支持该类型的字段的大小或长度规模
:用于十进制数据类型的小数位数(还必须指定大小)默认的
:默认值。primaryKey
:布尔。设置为真正的
对于主键。自动增量
:布尔。设置为真正的
对于类型的列整数
需要取一个自动递增的值。序列
:使用序列的数据库的序列名自动增量
列(例如PostgreSQL和Oracle)。指数
:布尔。设置为真正的
如果你想要一个简单的索引或独特的
如果您希望在列上创建唯一的索引。foreignTable
:表名,用于创建到另一个表的外键。foreignReference
:如果外键是通过定义的,则相关列的名称foreignTable
.onDelete
:确定删除相关表中的记录时触发的操作。当设置为setnull
,将外键列设置为零
.当设置为级联
,该记录被删除。如果数据库引擎不支持设置的行为,ORM将模拟它。这只与带有a的列相关foreignTable
和一个foreignReference
.isCulture
:布尔。设置为真正的
本地化内容表中的区域性列(参见第13章)。
外键
作为一种替代foreignTable
而且foreignReference
列属性下的外键_foreignKeys:
输入一个表。清单8-28中的模式将在user_id
列,与id
中的列。blog_user
表格
清单8-28 -外键替代语法
propel: blog_article: id: title: varchar(50) user_id: {type: integer} _foreignKeys: - foreignTable: blog_user onDelete: cascade references: - {local: user_id, foreign: id}
替代语法对于多引用外键和给外键命名很有用,如清单8-29所示。
清单8-29 -应用于多个引用外键的外键替代语法
_foreignKeys: my_foreign_key: foreignTable: db_user onDelete:级联引用:- {local: user_id, foreign: id} - {local: post_id, foreign: id}
索引
作为一种替代指数
属性下添加索引_indexes:
输入一个表。方法定义惟一索引,必须使用_uniques:
头。对于需要大小的列,因为它们是文本列,所以索引的大小与使用括号指定列的长度相同。清单8-30显示了索引的替代语法。
清单8-30 -索引和唯一索引替代语法
Propel: blog_article: id: title: varchar(50) created_at: _indexes: my_index: [title(10), user_id] _uniques: my_other_index: [created_at]
替代语法仅对构建在多个列上的索引有用。
空的列
当遇到没有值的列时,symfony将使用一些魔法并添加自己的值。ob娱乐下载添加到空列的详细信息请参见清单8-31。
清单8-31 -从列名推导出的列详细信息
//命名为id的空列被认为是主键id: {type: integer, required: true, primaryKey: true, autoIncrement: true} //命名为XXX_id的空列被认为是外键foobar_id: {type: integer, foreignTable: db_foobar, foreignReference: id} //命名为created_at, updated at, created_on和updated_on的空列被认为是日期,并自动采用时间戳类型created_at: {type: timestamp} updated_at: {type: timestamp}
对于外键,symfony将寻找具有相ob娱乐下载同外键的表phpName
作为列名的开头,如果找到一个,它将以这个表名作为foreignTable
.
I18n表
ob娱乐下载Symfony支持相关表中的内容国际化。这意味着,当您拥有国际化的内容时,它将存储在两个单独的表中:一个表具有不变列,另一个表具有国际化列。
在一个schema.yml
文件,当你命名一个表时,所有这些都是隐含的foobar_i18n
.例如,清单8-32中所示的模式将自动使用列和表属性完成,以使国际化内容机制工作。在内部,symfony会ob娱乐下载像清单8-33那样理解它。第13章将告诉你更多关于i18n的知识。
清单8-32 -隐含的i18n机制
db_group: id: created_at: db_group_i18n: name: varchar(50)
清单8-33 -显式i18n机制
propel: db_group: _attributes: {isI18N: true, i18nTable: db_group_i18n} id: created_at: db_group_i18n: id: {type: integer, required: true,primaryKey: true,foreignTable: db_group, foreignReference: id, onDelete: cascade} culture: {isCulture: true, type: varchar(7), required: true,primaryKey: true} name: varchar(50)
超出模式。yml: schema.xml
事实上,图式。Yml格式是symfony内部的。ob娱乐下载当您调用一个propel-命令时,symfony实际上将这个文件转ob娱乐下载换为一个generated-schema.xml
文件,这是驱动程序期望在模型上实际执行任务的文件类型。
的schema.xml
文件包含与它的YAML对等文件相同的信息。例如,清单8-3被转换为清单8-34所示的XML文件。
清单8-34 -示例schema.xml
,对应清单8-3
<?xml版本=“1.0”编码=“utf - 8”? ><数据库的名字=“推动”defaultIdMethod=“本地”noXsd=“真正的”包=“lib.model”><表的名字=“blog_article”phpName=“文章”><列的名字=“id”类型=“整数”要求=“真正的”primaryKey=“真正的”自动增量=“真正的”/><列的名字=“标题”类型=“varchar”大小=“255”/><列的名字=“内容”类型=“用longvarchar”/><列的名字=“created_at”类型=“时间戳”/>< /表><表的名字=“blog_comment”phpName=“评论”><列的名字=“id”类型=“整数”要求=“真正的”primaryKey=“真正的”自动增量=“真正的”/><列的名字=“article_id”类型=“整数”/><外键foreignTable=“blog_article”><参考当地的=“article_id”外国=“id”/>< /外键><列的名字=“作者”类型=“varchar”大小=“255”/><列的名字=“内容”类型=“用longvarchar”/><列的名字=“created_at”类型=“时间戳”/>< /表>< /数据库>
对schema.xml
格式可以在文档和Propel项目网站的“入门”部分找到欧宝官网下载app(http://propel.phpdb.org/docs/user_guide/chapters/appendices/AppendixB-SchemaReference.html).
YAML格式的设计目的是使模式易于读写,但代价是最复杂的模式不能用schema.yml
文件。另一方面,不管模式有多复杂,XML格式都允许进行完整的模式描述,并包括特定于数据库供应商的设置、表继承等等。
ob娱乐下载Symfony实际上理解以XML格式编写的模式。因此,如果您的模式对于YAML语法来说太复杂,如果您有一个现有的XML模式,或者如果您已经熟悉了Propel XML语法,那么您不必切换到symfony YAML语法。ob娱乐下载把你的schema.xml
在项目中配置/
目录,建立模型,就这样。
侧边栏
在交响乐中推进ob娱乐下载
本章给出的所有细节都不是针对symfony的,而是针对Propel的。ob娱乐下载Propel是symfony的首选对象/关系抽象层,但您可以选择另一个。ob娱乐下载然而,symfonob娱乐下载y与Propel的配合更加无缝,原因如下:
所有对象数据模型类和标准
类都是自动加载类。只要您使用它们,symfony就会包含正确的文件,ob娱乐下载并且您不需要手动添加文件包含语句。在syob娱乐下载mfony中,Propel不需要启动也不需要初始化。当一个对象使用Propel时,库会自行启动。一些symob娱乐下载fony助手使用Propel对象作为参数来实现高级任务(如分页或过滤)。Propel对象允许快速原型化和生成应用程序的后端(第14章提供更多细节)。模式的写入速度更快schema.yml
文件。
而且,由于Propel独立于所使用的数据库,symfony也是如此。ob娱乐下载
不要创建两次模型
使用ORM的代价是必须定义两次数据结构:一次用于数据库,一次用于对象模型。幸运的是,symfony提ob娱乐下载供了命令行工具来基于一个生成另一个,因此可以避免重复的工作。
基于现有模式构建SQL数据库结构
方法启动应用程序schema.yml
文件,symob娱乐下载fony可以生成一个SQL查询,直接从YAML数据模型创建表。要使用查询,请转到您的根项目目录并键入以下内容:
> ob娱乐下载symfony推进-build-sql
一个lib.model.schema.sql
文件将在myproject /数据/ sql /
.方法中定义的数据库系统将优化生成的SQL代码phptype
参数。propel.ini
文件。
您可以使用模式。年代ql file directly to build the tables. For instance, in MySQL, type this:
> mysqladmin -u root -p create blog > mysql -u root -p blog < data/sql/lib.model.schema.sql
生成的SQL还有助于在另一个环境中重新构建数据库,或更改到另一个DBMS。如果连接设置在您的propel.ini
,你甚至可以使用ob娱乐下载symfony propel-insert-sql
命令自动执行此操作。
提示
命令行还提供了一个任务,用基于文本文件的数据填充数据库。有关的更多信息,请参阅第16章propel-load-data
任务和YAML fixture文件。
从现有数据库生成YAML数据模型
ob娱乐下载Symfony可以使用Creole数据库访问层来生成一个schema.yml
来自现有数据库的文件,这要归功于自省(数据库确定其操作的表的结构的能力)。当您进行逆向工程时,或者如果您喜欢在处理对象模型之前先处理数据库,这可能特别有用。
为了做到这一点,您需要确保项目propel.ini
文件指向正确的数据库并包含所有连接设置,然后调用propel-build-schema
命令:
> ob娱乐下载symfony推进-构建-schema
一个全新的schema.yml
从数据库结构构建的文件将在配置/
目录中。您可以基于这个模式构建您的模型。
模式生成命令非常强大,可以向模式中添加大量依赖于数据库的信息。由于YAML格式不处理这类供应商信息,因此需要生成一个XML模式来利用它。你可以简单地添加一个xml
的参数建立模式
任务:
> ob娱乐下载symfony推进-构建-schema XML
而不是生成schema.yml
文件,这将创建一个schema.xml
与Propel完全兼容的文件,包含所有供应商信息。但是请注意,生成的XML模式往往相当冗长且难以阅读。
侧边栏
ini配置
的propel-build-sql
而且propel-build-schema
属性中定义的连接设置databases.yml
文件。相反,这些任务使用另一个名为propel.ini
并存储在项目中配置/
目录:
propel.database.url = mysql://login:passwd@localhost propel.database.url = mysql://login:passwd@localhost/blog
此文件包含用于配置Propel生成器以使生成的模型类与symfony兼容的其他设置。ob娱乐下载大多数设置是内部的,用户不感兴趣,除了一些:
/ /基类自动装载在symfony / /设置为true使用inclob娱乐下载ude_once语句而不是/ /(小对性能产生负面影响)propel.builder.addIncludes = false / /默认生成的类不评论/ /设置为true将评论添加到基类/ /(小对性能产生负面影响)propel.builder.addComments = false / /默认行为不处理/ /设置为true能够propel.builder.AddBehaviors = false / /标识符处理它们quote by default //设置为true禁用标识符引用(例如oracle)disableidentifierquotation = false
对象进行修改后propel.ini
设置时,不要忘记重新构建模型,以便更改生效。
总结
ob娱乐下载Symfony使用Propel作为ORM,使用Creole作为数据库抽象层。这意味着在生成对象模型类之前,必须首先在YAML中描述数据库的关系模式。然后,在运行时,使用对象和对等类的方法检索关于一条记录或一组记录的信息。您可以通过向自定义类添加方法来覆盖它们并轻松地扩展模型。连接设置定义在databases.yml
文件,它可以支持多个连接。命令行包含特殊任务,以避免重复的结构定义。
模型层是symfony框架中最复杂的。ob娱乐下载造成这种复杂性的一个原因是数据操作是一件复杂的事情。相关的安全问题对于一个网站来说是至关重要的,不应该被忽视。另一个原因是symfony更适合企业上下文中的ob娱乐下载中大型应用程序。在这样的应用程序中,symfony模型提供的自动化实际上意味着时间的增加,值得投资学习其内部知识。ob娱乐下载
因此,不要犹豫,花一些时间测试模型对象和方法,以完全理解它们。应用程序的稳定性和可伸缩性将是一个巨大的回报。
本作品在GFDL许可下获得许可。