数据库和ORM原则
编辑本页警告:您正在浏览的文档欧宝官网下载appob娱乐下载Symfony 6.0,现已不再维护。
读本页的更新版本用于Syob娱乐下载mfony 6.2(当前稳定版本)。
数据库和ORM原则
截屏视频
你更喜欢视频教程吗?请查看教义短片系列.
ob娱乐下载Symfony提供了在应用程序中使用数据库所需的所有工具学说,是使用数据库的最佳PHP库集。这些工具支持关系数据库,如MySQL和PostgreSQL,也支持NoSQL数据库,如MongoDB。
数据库是一个广泛的主题,因此文档分为三篇文章:欧宝官网下载app
- 本文解释了推荐的工作方式关系数据库在Syob娱乐下载mfony应用中;
- 读这是另一篇文章如果你需要低级访问对关系数据库执行原始SQL查询(类似于PHP)PDO);
- 读DoctrineMongoDBBundle文档如果你在和MongoDB数据库.
安装原则
首先,通过orm
ob娱乐下载Symfony包,以及MakerBundle,它将帮助生成一些代码:
1 2
$作曲家需要symfony/orm-ob娱乐下载pack$Composer require——开发symob娱乐下载fony/maker-bundle
配置数据库
数据库连接信息存储为名为DATABASE_URL
.对于开发,您可以在其中找到并定制它.env
:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# .env(或在.env中覆盖DATABASE_URL)。本地以避免提交您的更改)#自定义这一行!DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" # to use mariadb: DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=mariadb-10.5.8" # to use sqlite: # DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" # to use postgresql: # DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8" #使用oracle: # DATABASE_URL=" oc8://db_user:db_password@127.0.0.1:1521/db_name"
谨慎
如果用户名、密码、主机或数据库名在URI中包含任何被认为特殊的字符(例如+
,@
,$
,#
,/
,:
,*
,!
,%
),你必须编码它们。看到RFC 3986保留字符的完整列表或使用urlencode函数对它们进行编码。在这种情况下,您需要删除解决:
前缀的配置/包/ doctrine.yaml
为了避免错误:url: ' % env (DATABASE_URL) % '
连接参数设置完成后,Doctrine可以创建db_name
数据库为您:
1
$PHP bin/控制台原则:数据库:创建
还有更多的选择配置/包/ doctrine.yaml
你可以配置,包括你的server_version
(例如,如果你使用的是MySQL 5.7),这可能会影响Doctrine的功能。
提示
还有许多其他的教义命令。运行PHP bin/控制台列表原则
查看完整列表。
创建实体类
假设您正在构建一个需要显示产品的应用程序。甚至不考虑Doctrine或数据库,您已经知道您需要一个产品
对象来表示这些产品。
您可以使用:实体
命令来创建该类和所需的任何字段。这个命令会问你一些问题-像下面这样回答他们:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
$ php bin/console make:entity要创建或更新的实体的类名称:> Product新属性名称(按<返回>停止添加字段):> name字段类型(输入?[string]: > string字段长度[255]:> 255该字段可以为空吗在the database (nullable) (yes/no) [no]: > no返回>停止添加字段):> price字段类型(输入?查看所有类型)[string]: >整数这个字段可以为空吗在the database (nullable) (yes/no) [no]: > no返回>停止添加字段):>(再次按enter键完成)
哇!你现在有了一个新的src /实体/ Product.php
文件:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/ / src /实体/ Product.php名称空间应用程序\实体;使用应用程序\存储库\ProductRepository;使用学说\ORM\映射作为ORM;# (ORM \实体(repositoryClass: ProductRepository::类))类产品{# (ORM \ Id)# (ORM \ GeneratedValue)# (ORM \列)私人int$id;# [ORM列(长度:255)\]私人字符串$的名字;# (ORM \列)私人int$价格;公共函数getId(): ?int{返回$这->id;}/ /……Getter和setter方法}
请注意
从v1.44.0开始- MakerBundle只支持使用PHP属性的实体。
请注意
为什么价格是整数?别担心:这只是一个例子。但是,将价格存储为整数(例如100 = $1 USD)可以避免舍入问题。
请注意
如果你使用的是SQLite数据库,你会看到以下错误:PDOException: SQLSTATE[HY000]:一般错误:1不能添加默认值为NULL的NOT NULL列.添加一个nullable = true
选项。描述
属性来修复问题。
谨慎
有一个索引键前缀的限制为767字节在MySQL 5.6及更早版本中使用InnoDB表时。字符串列,长度为255个字符utf8mb4
编码超过了这个限制。这意味着任何类型的列字符串
而且独特= true
必须设置其最大值长度
来190
.否则,你会看到这个错误:"[PDOException] SQLSTATE[42000]:语法错误或访问违规:1071指定的键太长;最大密钥长度为767字节.
这个类称为“实体”。很快,您将能够将Product对象保存和查询到产品
表。中的每个属性产品
实体可以映射到该表中的列。这通常通过属性来完成# (ORM \列(…))
你在每个属性上面看到的注释:
的:实体
命令是使生活更容易的工具。但是这是你的代码:添加/删除字段,添加/删除方法或更新配置。
Doctrine支持各种各样的字段类型,每种类型都有自己的选项。要查看完整的列表,请查看Doctrine的映射类型文档欧宝官网下载app.如果希望使用XML而不是注释,请添加类型:xml
而且dir:“% kernel.project_dir % / config /学说”
中的实体映射配置/包/ doctrine.yaml
文件。
谨慎
注意不要使用保留的SQL关键字作为表或列的名称(例如。集团
或用户
).看到学说的保留SQL关键字文档欧宝官网下载app有关如何逃脱这些的详细信息。或者,用更改表名# (ORM \表(名字:“集团”))
属性配置列名名称:“group_name”
选择。
迁移:创建数据库表/模式
的产品
类已完全配置,并准备保存到产品
表格如果你只定义了这个类,你的数据库实际上没有产品
表。要添加它,可以利用DoctrineMigrationsBundle,已安装:
1
$PHP bin/控制台make:迁移
如果一切正常,你会看到如下内容:
1 2 3 4
成功!接下来:查看新的迁移“migrations/Version20211116204726.php”,然后:使用php bin/console原则运行迁移:migrations:migrate
如果您打开此文件,它将包含更新数据库所需的SQL !要运行SQL,请执行迁移:
1
$PHP bin/控制台原则:迁移:迁移
此命令执行尚未在数据库上运行的所有迁移文件。在进行部署时,应该在生产环境中运行此命令,以保持生产数据库的最新状态。
迁移&添加更多字段
但是如果你需要添加一个新的字段属性产品
,就像描述
?您可以编辑类以添加新属性。但是,你也可以用:实体
再次:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
$ php bin/console make:entity创建或更新实体的类名> Product新属性名(按<返回>停止添加字段):> description字段类型(输入?to see all types) [string]: > text该字段可以为空吗在the database (nullable) (yes/no) [no]: > no返回>停止添加字段):>(再次按enter键完成)
这将添加新的描述
财产和getDescription ()
而且setDescription ()
方法:
12 3 4 5 6 7 8 9 10 11 12
// src/Entity/Product.php…class Product{//…+ #[ORM\Column(type: 'text')]+ private $description;// getDescription() & setDescription()也被添加}
中的新属性已映射,但它还不存在产品
表格没问题!生成一个新的迁移:
1
$PHP bin/控制台make:迁移
这一次,生成的文件中的SQL将是这样的:
1
改变表格产品添加描述量变不零
迁移系统是聪明的.它将您的所有实体与数据库的当前状态进行比较,并生成同步它们所需的SQL !像以前一样,执行你的迁移:
1
$PHP bin/控制台原则:迁移:迁移
这将只执行一个新的迁移文件,因为DoctrineMigrationsBundle知道第一次迁移已经在前面执行了。在幕后,它管理着一个migration_versions
表来跟踪这个。
每次对模式进行更改时,运行这两个命令生成迁移,然后执行它。确保提交迁移文件并在部署时执行它们。
提示
如果您喜欢手动添加新属性,可以使用:实体
命令可以为你生成getter和setter方法:
1
$PHP bin/控制台生成实体
如果你做了一些改变,想要重生所有Getter /setter方法也通过——覆盖
.
持久化对象到数据库
是时候节省了产品
对象的数据库!让我们创建一个新的控制器来进行实验:
1
$ProductController . php bin/控制台
在控制器内部,您可以创建一个新的产品
对象,在其上设置数据并保存:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
/ / src /控制器/ ProductController.php名称空间应用程序\控制器;/ /……使用应用程序\实体\产品;使用学说\持久性\ManagerRegistry;使用ob娱乐下载\组件\HttpFoundation\响应;使用ob娱乐下载\组件\路由\注释\路线;类ProductController扩展AbstractController{#[Route('/product', name: 'create_product')]公共函数createProduct(ManagerRegistry$学说):响应{$entityManager=$学说->getManager ();$产品=新产品();$产品->setName (“键盘”);$产品->setPrice (1999);$产品->setDescription (“符合人体工学,时尚!”);//告诉Doctrine你想(最终)保存产品(还没有查询)$entityManager->persist ($产品);//实际执行查询(即INSERT查询)$entityManager->冲洗();返回新响应(“保存了带有id的新产品”.$产品->getId ());}}
试试吧!
恭喜你!你刚刚创建了第一行产品
表格为了证明,可以直接查询数据库:
1 2 3 4
$PHP bin/console dbal:run-sqlSELECT * FROM product在没有使用Powershell的Windows系统上,执行以下命令:# php bin/console dbal:run-sql "SELECT * FROM product"
更详细地看看前面的例子:
- 13号线的
美元ManagerRegistry学说
论点告诉Symfonyob娱乐下载注入Doctrine服务进入控制器方法。 - 第15行的
原则- > getManager ()
方法得到Doctrine的实体管理器客体,是《主义》中最重要的客体。它负责将对象保存到数据库,并从数据库中获取对象。 - 行日方法的实例化和使用
美元的产品
对象,就像任何其他普通PHP对象一样。 - 第23行的
坚持(产品)
调用告诉Doctrine“管理”美元的产品
对象。这并不导致对数据库进行查询。 - 第26行当
冲洗()
方法时,Doctrine会查看它所管理的所有对象,以确定它们是否需要持久化到数据库中。在本例中,美元的产品
对象的数据在数据库中不存在,因此实体管理器执行插入
对象中创建新行产品
表格
请注意
如果冲洗()
呼叫失败,a学说\ ORM \ ORMException
异常。看到事务和并发.
无论您是创建还是更新对象,工作流程总是相同的:Doctrine足够智能,可以知道它是应该INSERT还是UPDATE您的实体。
验证对象
Symfob娱乐下载ony验证器重用Doctrine元数据来执行一些基本的验证任务:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
/ / src /控制器/ ProductController.php名称空间应用程序\控制器;使用应用程序\实体\产品;使用ob娱乐下载\组件\HttpFoundation\响应;使用ob娱乐下载\组件\路由\注释\路线;使用ob娱乐下载\组件\验证器\验证器\ValidatorInterface;/ /……类ProductController扩展AbstractController{#[Route('/product', name: 'create_product')]公共函数createProduct(ValidatorInterface$验证器):响应{$产品=新产品();//这将触发一个错误:该列在数据库中不是空的$产品->setName (零);//这将触发类型不匹配错误:需要一个整数$产品->setPrice (“1999”);/ /……$错误=$验证器->validate ($产品);如果(数($错误) >0) {返回新响应((字符串)$错误,400);}/ /……}}
虽然产品
实体没有定义任何显式的验证配置, ob娱乐下载Symfony内省Doctrine映射配置来推断一些验证规则。例如,假设的名字
财产不能是零
在数据库中,aNotNull约束自动添加到属性(如果它尚未包含该约束)。
下表总结了Doctrine元数据和Symfony自动添加的相应验证约束之间的映射:ob娱乐下载
义属性 | 验证约束 | 笔记 |
---|---|---|
nullable = false |
NotNull | 需要安装PropertyInfo组件 |
类型 |
类型 | 需要安装PropertyInfo组件 |
独特= true |
UniqueEntity | |
长度 |
长度 |
因为表单组件还有API的平台在内部使用Validator组件,所有的表单和web api也将自动受益于这些自动验证约束。
这种自动验证是一个很好的特性,可以提高您的工作效率,但它不能完全取代验证配置。你还需要添加一些验证约束确保用户提供的数据是正确的。
从数据库中获取对象
从数据库中取回对象更加简单。假设你想要能够/产品/ 1
要查看您的新产品:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/ / src /控制器/ ProductController.php名称空间应用程序\控制器;使用应用程序\实体\产品;使用ob娱乐下载\组件\HttpFoundation\响应;使用ob娱乐下载\组件\路由\注释\路线;/ /……类ProductController扩展AbstractController{#[Route('/product/{id}', name: 'product_show')]公共函数显示(ManagerRegistry$学说, int$id):响应{$产品=$学说->getRepository(产品::类)->找到($id);如果(!$产品) {扔$这->createNotFoundException (“没有为id找到产品”.$id);}返回新响应(“看看这个很棒的产品吧:”.$产品->getName ());//或呈现一个模板//在模板中,使用{{product.name}}打印内容//返回$this->渲染('product/show.html. 'Twig ', ['product' => $product]);}}
另一种可能是使用ProductRepository
使用Symfob娱乐下载ony的自动装配和依赖注入容器注入:
12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20
/ / src /控制器/ ProductController.php名称空间应用程序\控制器;使用应用程序\实体\产品;使用应用程序\存储库\ProductRepository;使用ob娱乐下载\组件\HttpFoundation\响应;使用ob娱乐下载\组件\路由\注释\路线;/ /……类ProductController扩展AbstractController{#[Route('/product/{id}', name: 'product_show')]公共函数显示(int$id, ProductRepository$productRepository):响应{$产品=$productRepository->找到($id);/ /……}}
试试吧!
当你查询一个特定类型的对象时,你总是使用所谓的“存储库”。您可以将存储库视为一个PHP类,它的唯一任务是帮助您获取某个类的实体。
一旦你有了一个存储库对象,你就有了很多帮助方法:
12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20 21
$存储库=$学说->getRepository(产品::类);//通过主键(通常是"id")查找单个Product$产品=$存储库->找到($id);//通过名称查找单个产品$产品=$存储库->findOneBy ([“名字”= >“键盘”]);//或按名称和价格查找$产品=$存储库->findOneBy ([“名字”= >“键盘”,“价格”= >1999]);//查找多个与名称匹配的Product对象,按价格排序$产品=$存储库->findBy ([“名字”= >“键盘”]、[“价格”= >“ASC”]);//查找*所有* Product对象$产品=$存储库->findAll ();
您还可以添加自定义用于更复杂查询的方法!稍后会详细介绍数据库和ORM原则部分。
提示
当呈现一个HTML页面时,页面底部的web调试工具栏将显示查询的数量和执行查询所需的时间:
如果数据库查询的数量太高,图标将变为黄色,表示可能有错误。单击图标打开Symfony Profiler并查看执行的确切ob娱乐下载查询。如果看不到web调试工具栏,请安装分析器
ob娱乐下载Symfony包运行此命令:开发symfony/profiler-packob娱乐下载
.
自动抓取对象(ParamConverter)
在许多情况下,可以使用SensioFrameworkExtraBundle自动为您做查询!首先,安装bundle以防你没有它:
1
$Composer需要sensio/framework-extra-bundle
现在,简化你的控制器:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/ / src /控制器/ ProductController.php名称空间应用程序\控制器;使用应用程序\实体\产品;使用应用程序\存储库\ProductRepository;使用ob娱乐下载\组件\HttpFoundation\响应;使用ob娱乐下载\组件\路由\注释\路线;/ /……类ProductController扩展AbstractController{#[Route('/product/{id}', name: 'product_show')]公共函数显示(产品$产品):响应{//使用该产品!/ /……}}
就是这样!bundle使用{id}
从路由中查询产品
由id
列。如果没有找到,则生成404页面。
您可以使用的选项还有很多。阅读更多有关ParamConverter.
更新对象
一旦你从Doctrine中获取了一个对象,你就可以像与任何PHP模型一样与它交互:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
/ / src /控制器/ ProductController.php名称空间应用程序\控制器;使用应用程序\实体\产品;使用应用程序\存储库\ProductRepository;使用ob娱乐下载\组件\HttpFoundation\响应;使用ob娱乐下载\组件\路由\注释\路线;/ /……类ProductController扩展AbstractController{#(路线(' /产品/编辑/ {id}’,名字:“product_edit”))公共函数更新(ManagerRegistry$学说, int$id):响应{$entityManager=$学说->getManager ();$产品=$entityManager->getRepository(产品::类)->找到($id);如果(!$产品) {扔$这->createNotFoundException (“没有为id找到产品”.$id);}$产品->setName (“新产品名称!”);$entityManager->冲洗();返回$这->redirectToRoute (“product_show”, (“id”= >$产品->getId ()));}}
使用Doctrine编辑现有产品包括三个步骤:
- 从Doctrine中获取对象;
- 修改对象;
- 调用
冲洗()
在实体管理器上。
你可以调用entityManager - >保存(产品)
,但这不是必要的:Doctrine已经在“观察”你的对象的变化。
删除对象
对象的删除非常类似,但需要调用remove ()
实体管理器方法:
1 2
$entityManager->remove ($产品);$entityManager->冲洗();
如你所料,remove ()
方法通知Doctrine您希望从数据库中删除给定对象。的删除
查询直到冲洗()
方法。
查询对象:存储库
你已经看到了repository对象是如何让你不做任何工作就可以运行基本查询的:
1 2 3
//从控制器内部$存储库=$学说->getRepository(产品::类);$产品=$存储库->找到($id);
但是如果需要更复杂的查询呢?当你用:实体
,命令也生成一个ProductRepository
类:
12 3 4 5 6 7 8 9 10 11 12 13 14
/ / src /仓库/ ProductRepository.php名称空间应用程序\存储库;使用应用程序\实体\产品;使用学说\包\DoctrineBundle\存储库\ServiceEntityRepository;使用学说\持久性\ManagerRegistry;类ProductRepository扩展ServiceEntityRepository{公共函数__construct(ManagerRegistry$注册表){父::__construct ($注册表、产品::类);}}
当你获取你的存储库(即。- > getRepository(产品::类)
),是实际上的一个实例这对象!这是因为repositoryClass
的顶部生成的配置产品
实体类。
假设您希望查询大于某个价格的所有Product对象。为你的存储库添加一个新方法:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/ / src /仓库/ ProductRepository.php/ /……类ProductRepository扩展ServiceEntityRepository{公共函数__construct(ManagerRegistry$注册表){父::__construct ($注册表、产品::类);}/ * * *@return产品[]* /公共函数findAllGreaterThanPrice(int$价格):数组{$entityManager=$这->getEntityManager ();$查询=$entityManager->createQuery (SELECT p FROM App\Entity\Product WHERE p.price >:price ORDER BY p.price ASC)->setParameter (“价格”,$价格);//返回Product对象数组返回$查询->getResult ();}}
传递给的字符串createQuery ()
可能看起来像SQL,但它确实是Doctrine查询语言.这允许您使用常见的查询语言输入查询,但引用PHP对象代替(即在从
声明)。
现在,你可以在存储库中调用这个方法:
1 2 3 4 5 6
//从控制器内部$minPrice=1000;$产品=$学说->getRepository(产品::类)->findAllGreaterThanPrice ($minPrice);/ /……
看到服务容器关于如何将存储库注入任何服务。
使用查询生成器进行查询
原则还提供了查询构建器,一种面向对象的方式来编写查询。建议在动态构建查询(即基于PHP条件)时使用:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/ / src /仓库/ ProductRepository.php/ /……类ProductRepository扩展ServiceEntityRepository{公共函数findAllGreaterThanPrice(int$价格bool,$includeUnavailableProducts= false):数组{//自动知道选择产品//“p”是你将在查询的其余部分使用的别名$qb=$这->createQueryBuilder (“p”)->(在哪里'p.price >:价格')->setParameter (“价格”,$价格)->orderBy (“p.price”,“ASC”);如果(!$includeUnavailableProducts) {$qb->引入('p.available = TRUE');}$查询=$qb->getQuery ();返回$查询->execute ();//只得到一个结果:// $product = $query->setMaxResults(1)->getOneOrNullResult();}}
使用SQL查询
此外,如果您需要,您可以直接使用SQL查询:
12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20 21
/ / src /仓库/ ProductRepository.php/ /……类ProductRepository扩展ServiceEntityRepository{公共函数findAllGreaterThanPrice(int$价格):数组{$康涅狄格州=$这->getEntityManager ()->getConnection ();$sql=' SELECT * FROM product p WHERE p.price >:price ORDER BY p.price ASC ';$支撑=$康涅狄格州->准备($sql);$结果集=$支撑->executeQuery ([“价格”= >$价格]);//返回数组的数组(即原始数据集)返回$结果集->fetchAllAssociative ();}}
使用SQL,您将返回原始数据,而不是对象(除非您使用NativeQuery功能)。
数据库测试
阅读这篇关于测试与数据库交互的代码.
原则扩展(可时间戳、可翻译等)
Doctrine社欧宝体育平台怎么样区创建了一些扩展来实现共同的需求,例如在创建实体时自动设置createdAt属性的值.阅读更多有关可用的理论扩展并使用StofDoctrineExtensionsBundle将它们集成到应用程序中。