Symfony2学习笔记之数据库操作.doc_第1页
Symfony2学习笔记之数据库操作.doc_第2页
Symfony2学习笔记之数据库操作.doc_第3页
Symfony2学习笔记之数据库操作.doc_第4页
Symfony2学习笔记之数据库操作.doc_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Symfony2学习笔记之数据库操作数据库和Doctrine让我们来面对这个对于任何应用程序来说最为普遍最具挑战性的任务,从数据库中读取和持久化数据信息。幸运的是,Symfony和Doctrine进行了集成,Doctrine类库全部目标就是给你一个强大的工具,让你的工作更加容易。Doctrine是完全解耦与Symfony的,所以并不一定要使用它。一个简单例子:一个产品,我们首先来配置数据库,创建一个Product对象,持久化它到数据库并把它读回来。首先我们需要创建一个bundle:$php app/console generate:bundle -namespace=Acme/StoreBundle 配置数据库在开始之前,首先需要配置数据库连接信息。根据惯例,这些信息通常会配置在app/config/parameters.ini 文件中。复制代码;app/config/parameters.iniparameters database_driver = pdo_mysql database_host = localhost database_name = test_project database_user = root database_password = password复制代码将配置信息定义到parameters.ini文件中也是一个常用的做法。定义在该文件中的配置信息将会被主配置文件在安装Doctrine时引用。复制代码doctrine: dbal: driver: %database_driver% host: %database_host% dbname: %database_name% user: %database_user% password: %database_password%复制代码通过把数据库信息分离到一个特定的文件中,你可以很容易的为每个服务器保存不同的版本。现在Doctrine知道你的数据库配置了,你可以用它来创建一个数据库了。$php app/console doctrine:database:create 创建一个实体类:假设你创建一个应用程序,其中有些产品需要展示。即时不考虑Doctrine或者数据库,你也应该知道你需要一个Product对象来表现这些产品。在你的AcmeStoreBundle的Entity目录下创建一个类。复制代码/ src/Acme/StoreBundle/Entity/Product.phpnamespace AcmeStoreBundleEntity;class Product protected $name; protected $price; protected $description;复制代码这样的类经常被称为“Entity,意味着一个基础类保存数据。它们简单来满足你应用程序的业务需要。不过现在它还不能被保存到数据库中,因为现在它只不过还是个简单的PHP类。一旦你学习了Doctrine背后的概念,你可以让Doctrine来为你创建实体类。$php app/console doctrine:generate:entity -entity=AcmeStoreBundle:Product -fields=name:string(255) price:float description:text 添加映射信息Doctrine允许你使用一种更加有趣的方式对数据库进行操作,而不是只是获取基于列表的行到数组中。Doctrine允许你保存整个对象到数据库或者把对象从数据库中取出。这些都是通过映射PHP类到一个数据库表,PHP类的属性对应数据库表的列来实现的。因为Doctrine能够做这些,所以你仅仅只需要创建一个meatdata,或者配置告诉DoctrineProduct类和它的属性应该如何映射到数据库。这些metadata可以被定义成各种格式,包括YAML,XML或者通过声明直接定义到Product类中。一个bundle只可以接受一种metadata定义格式。比如,不能把YAML定义的metadata和声明PHP实体类一起混用。复制代码use DoctrineORMMapping as ORM;/* * ORMEntity * ORMTable(name=product) */class Product /* * ORMId * ORMColumn(type=integer) * ORMGeneratedValue(strategy=AUTO) */ protected $id; /* * ORMColumn(type=string, length=100) */ protected $name; /* * ORMColumn(type=decimal, scale=2) */ protected $price; /* * ORMColumn(type=text) */ protected $description;复制代码YAML格式metadata定义:复制代码# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.ymlAcmeStoreBundleEntityProduct: type: entity table: product id: id: type: integer generator: strategy: AUTO fields: name: type: string length: 100 price: type: decimal scale: 2 description: type: text复制代码XML格式metadata定义:复制代码 复制代码表名称是可选的,可以忽略;如果忽略将会自动的根据entity类名对应。如果使用在类中声明metadata需要首先使用use DoctrineORMMapping as ORM;导入ORM声明前缀。然后在每个声明前使用 ORM 比如:ORMColumn(.);注意:你的类名称和属性不能映射到SQL受保护的关键字(比如:group 或者 user)。如果你的实体类名是Group,默认情况下你的表面也将是group,这会引起SQL错误。当使用另外的类库或者程序,它们使用了声明,你应该把IgnoreAnnotation声明添加到该类上来告诉Symfony忽略它们。比如我们要阻止fn 声明抛出异常,可以这样:/* IgnoreAnnotation(fn)*/class Product 生成Getters和Setters尽管Doctrine现在知道了如何值就花Product对象到数据库,但是类本身还是无法使用。因为Product仅仅是一个标准的PHP类,你需要创建getter和setter方法(比如getName(),setName()来访问它的属性(因为它的属性是protected),幸运的是Doctrine可以为我们做这些:$php app/console doctrine:generate:entites Acme/StoreBundle/Entity/Product该命令可以确认Product类所有的getter和setter都被生成。这是一个安全的命令行,你可以多次运行它,它只会生成那些不存在的getters和setters,而不会替换已有的。关于doctrine:generate:entities命令 用它你可以生成getters和setters。 用它在配置ORMEntity(repositoryClass=.)声明的情况下,生成repository类。 用它可以为1:n或者n:m生成合适的构造器。该命令会保存一个原来Product.php文件的备份Product.php。 有些时候可也能够会造成“不能重新声明类”错误,你可以放心的删除它,来消除错误。当然你没有必要依赖于该命令行,Doctrine不依赖于代码生成,想标准的PHP类,你只需要保证它的protected/private属性拥有getter和setter方法即可。你也可以为一个bundle或者整个实体命名空间内的所有已知实体(任何包含Doctrine映射声明的PHP类)来生成getter和setter:$php app/console doctrine:generate:entities AcmeStoreBundle$php app/console doctrine:generate:entities AcmeDoctrine不关心你的属性是protected还是private,或者这些属性是否有getter或setter。只所以生成这些getter或者setter完全是因为你需要跟你的PHP对象进行交流需要它们。 创建数据库表和模式现在我们有了一个可用的Product类和它的映射信息,所以Doctrine知道如何持久化它。当然,现在Product还没有相应的product数据库表在数据库中。幸运的是,Doctrine可以自动创建所有的数据库表。$php app/console doctrine:schema:update -force说真的,这条命令是出奇的强大。它会基于你的entities的映射信息,来比较现在的数据库,并生成所需要等新数据库的更新SQl语句。换句话说,如果你想添加一个新的属性映射元数据到Product并运行该任务,它将生成一个alert table 语句来添加新的列到已经存在的product表中。一个更好的发挥这一优势的功能是通过migrations,它允许你生成这些SQL语句并存储到一个合并类,并能有组织的运行在你的生产环境中有效的跟踪和并安全的合并你的数据库。现在你的数据库中有了一个全功能的product表,它的每个列都会被映射到你指定的元数据。持久化对象到数据库现在我们有了一个Product实体和与之映射的product数据库表。你可以把数据持久化到数据库里。在Controller内,它非常简单。添加下面的方法到bundle的DefaultController中。复制代码/ src/Acme/StoreBundle/Controller/DefaultController.phpuse AcmeStoreBundleEntityProduct;use SymfonyComponentHttpFoundationResponse;/ .public function createAction() $product = new Product(); $product-setName(A Foo Bar); $product-setPrice(19.99); $product-setDescription(Lorem ipsum dolor); $em = $this-getDoctrine()-getEntityManager(); $em-persist($product); $em-flush(); return new Response(Created product id .$product-getId();复制代码事实上,Doctrine了解你所有的被管理的实体,当你调用flush()方法时,它会计算出所有的变化,并执行最有效的查询可能。 比如,你要持久化总是为100的产品对象,然后调用flush()方法。Doctrine会创建一个唯一的预备语句并重复使用它插入。 这种模式成为Unit of work。在创建和更新对象是,工作流是相同的。Doctrine提供了一个类库允许你通过编程加载测试数据到你的项目。该类库为DoctrineFixturesBundle(/doc/current/bundles/DoctrineFixturesBundle/index.html)从数据库中读取对象从数据库中获取对象更容易,举个例子,加入你配置了一个路由来基于它的ID显示特定的product。复制代码public function showAction($id) $product = $this-getDoctrine() -getRepository(AcmeStoreBundle:Product) -find($id); if(!$product) throw $this-createNotFoundException(No product found for id .$id); /do something,想把$product对象传递给一个template等。复制代码当你查询某个特定的产品是,你总是需要使用它的respository。你可以认为Respository是一个PHP类,它的唯一工作就是帮助你从某个特定类哪里获取实体。你可以为一个实体对象访问一个repository对象,如下:$repository = $this-getDoctrine() -getRepository(AcmeStoreBundle:Product);其中AcmeStoreBundle:Product是简洁写法,你可以在Doctrine中任意使用它来替代实体类的全限定名称。AcmeStoreBungEntityProduct你一旦有了Repository,你就可以访问其所有分类的帮助方法了。复制代码/通过主键查询(一般为id)$product=$repository-find($id);/动态方法名基于列值查找$product=$repository-findOneById($id);$product=$repository-findOneByName(foo);/查询所有产品$products=$repository-findAall();/基于任意列值查找一组产品$products = $repository-findByPrice(19.99);复制代码你也可以发挥findBy和findOneBy方法的优势很容易的基于多个条件来获取对象。复制代码/按照名字和价格来获取一个匹配的对象$product=$repository-findOneBy(array(name=foo,price=19.99);/查询匹配名字的所有产品并按照价格排序$products = $repository-findBy( array(name= foo), array(price=ASC);复制代码 更新对象一旦你从Doctrine中获取了一个对象,那么更新它就变得很容易了。假设你有一个路由映射一个产品id到一个controller的更新行为。复制代码public function updateAction($id) $em = $this-getDoctrine()-getEntityManager(); $product = $em-getRepository(AcmeStoreBundle:Product)-find($id); if (!$product) throw $this-createNotFoundException(No product found for id .$id); $product-setName(New product name!); $em-flush(); return $this-redirect($this-generateUrl(homepage);复制代码更新一个对象包括三步: 1.从Doctrine取出对象 2.修改对象 3.在实体管理者上调用flush()方法注意调用 $em-persist($product) 在这里没有必要。我们回想一下,调用该方法的目的主要是告诉Doctrine来管理或者“watch$product对象。在这里,因为你已经取到了$product对象了,说明已经被管理了。删除对象: 删除一个对象,需要从实体管理者那里调用remove()方法。$em-remove($product);$em-flush();正如你想的那样,remove()方法告诉Doctrine你想从数据库中移除指定的实体。真正的删除查询没有被真正的执行,直到flush()方法被调用。查询对象:你已经看到了repository对象允许你执行一些基本的查询而不需要你做任何的工作。$repository-find($id);$repository-findOneByName(Foo);当然,Doctrine 也允许你使用Doctrine Query Language(DQL)写一些复杂的查询,DQL类似于SQL,只是它用于查询一个或者多个实体类的对象,而SQL则是查询一个数据库表中的行。在Doctrinez中查询时,你有两种选择:写纯Doctrine查询 或者 使用Doctrine的查询创建器。用DQL查询对象:假设你想查询产品,需要返回价格高于19.99的产品,并且要求按价格从低到高排列。我们可以在相应的Controller里进行如下操作:$em = $this-getDoctrine()-getEntityManager();$query = $em-createQuery( SELECT p FROM AcmeStoreBundle:Product p WHERE p.price :price ORDER BY p.price ASC)-setParameter(price, 19.99);$products = $query-getResult();如果你习惯了写SQL,那么对于DQL也应该不会感到陌生。它们之间最大的不同就是你需要思考对象,而不是数据库表行。正因为如此,所以你从AcmeStoreBundle:Product选择并给它定义别名p。getResult()方法返回一个结果数组。如果你只需要一个对象,你可以使用getSingleResult()方法。$product = $query-getSingleResult();如果没有符合要求的结果,getSingleResult()方法会抛出一个 DoctrineORMNoResultException 异常和如果不只有一个结果返回那么就会抛出一个DoctrineORMNonUniqueResultException 异常。所以,如果你要使用该方法的话,需要把它包裹在一个try-catch块内,以确保只有一个结果被返回。复制代码$query = $em-createQuery(SELECT .) -setMaxResults(1);try $product = $query-getSingleResult(); catch (DoctrineOrmNoResultException $e) $product = null;/ .复制代码DQL的语法难以置信的强大,允许你很容易的俩和多个实体,分组等进行查询。 设置参数:注意setParameter()方法,在使用Doctrine时,把任何的外部值设置成占位符是一个非常好的做法。 比如上例中 .WHERE p.price:price .这样你可以通过调用setParameter()方法为price占位符设置具体值。-setParameter(price, 19.99)使用参数而不是直接把具体在插入到查询字符串中是为了放置SQL注入攻击,所以必须始终这么做。如果你使用了多个参数,你可以使用setParameters()方法一次性设置他们的值。-setParameters(array( price=19.99, name =foo,) 使用Doctrine的查询创建器除了直接编写查询以外,你可以使用Doctrine的QueryBuilder来做相同的工作。面前对象接口。如果你使用IDE,你还可以获取自动编译检查的好处。复制代码$repository = $this-getDoctrine() -getRepository(AcmeStoreBundle:Product);$query = $repository-createQueryBuilder(p) -where(p.price :price) -setParameter(price, 19.99) -orderBy(p.price, ASC) -getQuery();$products = $query-getResult();复制代码QueryBuilder对象包含了创建查询的所有必须的方法。通过调用getQuery()方法,查询创建器将返回一个标准的Query对象。它跟我们直接写查询对象效果相同。 自定义Repository类在上面你已经开始在controller中创建和使用负责的查询了。为了隔离,比阿育测试和重用这些查询,一个好的办法是为你的实体创建一个自定义的repository类并添加相关逻辑查询方法。要定义repository类,首先需要在你的映射定义中添加repository类的声明:在实体类中声明方式:复制代码/ src/Acme/StoreBundle/Entity/Product.phpnamespace AcmeStoreBundleEntity;use DoctrineORMMapping as ORM;/* * ORMEntity(repositoryClass=AcmeStoreBundleRepositoryProductRepository) */class Product /.复制代码YAML格式:# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.ymlAcmeStoreBundleEntityProduct: type: entity repositoryClass: AcmeStoreBundleRepositoryProductRepository # .XML格式:复制代码 复制代码然后通过运行跟之前生成丢失的getter和setter方法同样的命令行,Doctrine会为你自动生成repository类。$php app/console doctrine:generate:entities Acme接下来,添加一个新方法findAllOrderedByName() 到新生成的repository类。该方法将查询所有的Product实体,并按照字符顺序排列。复制代码/ src/Acme/StoreBundle/Repository/ProductRepository.phpnamespace AcmeStoreBundleRepository;use DoctrineORMEntityRepository;class ProductRepository extends EntityRepository public function findAllOrderedByName() return $this-getDoctrine EntityManager() -createQuery (SELECT p FROM AcmeStoreBundle:Product p ORDER BY ASC) -getResult(); 复制代码注意在Repository类中可以通过$this-getEntityManager()方法类获取实体管理者。如此一来你就可以像使用默认的方法一样使用这个新定义的方法了:$em = $this-getDoctrine()-getEntityManager();$products = $em-getRepository(AcmeStoreBundle:Product) -findAllOrderedByName();在使用自定义的repository类时,你依然可以访问原有的默认查找方法,比如find() 和findAll()等。实体关系/关联假设你应用程序中的产品属于一确定的分类。这时你需要一个分类对象和一种把Product和Category对象联系在一起的方式。首先我们创建Category实体,我们最终要通过Doctrine来对其进行持久化,所以我们这里让Doctrine来帮我们创建这个类。$php app/console doctrine:generate:entity -entity=AcmeStoreBundle:Category -fields=name:string(255)该命令行为你生成一个Category实体,包含id字段和name字段以及相关的getter和setter方法。关系映射元数据:联系Category和Product两个实体,首先在Category类中创建一个products属性:Category类中声明格式:复制代码/ src/Acme/StoreBundle/Entity/Category.php/ .use DoctrineCommonCollectionsArrayCollection;class Category / . /* * ORMOneToMany(targetEntity=Product, mappedBy=category) */ protected $products; public function _construct() $this-products = new ArrayCollection(); 复制代码YAML定义格式:复制代码# src/Acme/StoreBundle/Resources/config/doctrine/Category.orm.ymlAcmeStoreBundleEntityCategory: type: entity # . oneToMany: products: targetEntity: Product mappedBy: category # 不要忘记在实体的 _construct() 方法中初始化集合复制代码首先,因为一个Category对象将关系到多个Product对象,一个products数组属性被添加到Category类保存Product对象。其次,这里没有被做因为Doctrine需要它,但在应用程序中为每一个Category来保存一个Product数组非常有用。代码中_construct()方法非常重要,因为Doctrine需要$products熟悉成为一个ArrayCollection对象,它跟数组非常类似。targetEntity 的值可以使用合法的命名空间引用任何实体,而不仅仅是定义在同一个类中的实体。 如果要关系一个定义在不同的类或者bundle中的实体则需要输入完全的命名空间作为目标实体。接下来,因为每个Product类可以关联一个Category对象,所有添加一个$category属性到Product类:在Product类声明中定义:复制代码/ src/Acme/StoreBundle/Entity/Product.php/ .class Product / . /* * ORMManyToOne(targetEntity=Category, inversedBy=products) * ORMJoinColumn(name=category_id, referencedColumnName=id) */ protected $category;复制代码YAML定义格式:复制代码# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.ymlAcmeStoreBundleEntityProduct: type: entity # . manyToOne: category: targetEntity: Category inversedBy: products joinColumn: name: category_id referencedColumnName: id复制代码最后,到现在为止,我们添加了两个新属性到Category和Product类。现在告诉Doctrine来为它们生成getter和setter方法。$php app/console doctrine:generate:entities Acme我们先不看Doctrine的元数据,你现在有两个类Category和Product,并且拥有一个一对多的关系。Category包含一个数组Product对象,Product包含一个Category对象。换句话说,你已经创建了你所需要的类了。现在让我们来看看在Product类中为$category配置的元数据。它告诉Doctrine关系类是Category并且它需要保存category的id到product表的category_id字段。换句话说,相关的分类对象将会被保存到$category属性中,但是在底层,Doctrine会通过存储category的id值到product表的category_id列持久化它们的关系。Category类中$product属性的元数据配置不是特别重要,它仅仅是告诉Doctrine去查找Product.category属性来计算出关系映射是什么。在继续下去之前,首先确定告诉Doctrine添加一个新的category表和product.category_id列以及新的外键。$php app/console doctrine:schema:update -force保存关系实体:现在让我们来看看Controller内的代码如何处理:复制代码/ .use AcmeStoreBundleEntityCategory;use AcmeStoreBundleEntityProduct;use SymfonyComponentHttpFoundationResponse;/ .class DefaultController extends Controller public function createProductAction() $category = new Category(); $category-setName(Main Products); $product = new Product(); $product-setName(Foo); $product-setPrice(19.99); / relate this product to the category $product-setCategory($category); $em = $this-getDoctrine()-getEntityManager(); $em-persist($category); $em-persist($product); $em-flush(); return new Response( Created product id: .$product-getId(). and category id: .$category-getId() ); 复制代码现在,一个单独的行被添加到category和product表中。新产品的product.categroy_id列被设置为新category表中的id的值。Doctrine会为你管理这些持久化关系。获取相关的对象:当你需要获取关联的对象时,你的工作流畅跟以前一样。首先获取$product对象,然后访问它的关联Category。复制代码public function showAction($id) $product = $this-getDoctrine() -getRepository(AcmeStoreBundle:Product) -find($id); $categoryName = $product-getCategory()-getName(); / .复制代码在这个例子中,你首先基于产品id查询一个Product对象,接下来当你调用$product-getCategory()-getName() 时,Doctrine默默的为你执行了第二次查询,查找一个与该产品相关的category,它生成一个$category对象返回给你。重要的是你很容易的访问到了product的关联对象category。但是category的数据并不会被取出来而直到你请求category的时候。这就是延迟加载。你也可以从其它方向进行查询:复制代码public function showProductAction($id) $category = $this-getDoctrine() -getRepository(AcmeStoreBundle:Category) -find($id); $products = $category-getProducts(); / .复制代码在这种情况下,同样的事情发生了。你首先查查一个category对象,然后Doctrine制造了第二次查询来获取与之相关联的Product对象们。只有在你调用-getProducts()时才会执行一次。 $products变量是一个通过它的category_id的值跟给定的category对象相关联的所有Product对象的集合。关系和代理类:延迟加载成为可能是因为Doctrine返回一个代理对象来代替真正的对象:复制代码$product = $this-getDoctrine() -getRepository(AcmeStoreBundle:Product) -find($id);$category = $product-getCategory();/ 输出结果 ProxiesAcmeStoreBundleEntityCategoryProxyecho get_class($cate

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论