unifik / doctrine-behaviors-bundle
使用 traits 的 Symfony2 Doctrine Behaviors
Requires
- php: >=5.4.0
- symfony/symfony: ~2.3
This package is not auto-updated.
Last update: 2024-09-28 15:06:30 UTC
README
此包高度启发于 KnpLabs/DoctrineBehaviors。一些行为已被修改,因为它们没有完全达到我们的预期。
已添加新行为:Uploadable、Metadatable 和 Taggable。
原始行为已被封装在 Symfony2 包中。
由于我们使用 traits,需要 PHP 5.4.
目前,这些行为可用:
如何使用
可翻译
实体
您必须生成可翻译和翻译实体。例如,Text 和 TextTranslation
# Text.orm.yml Unifik\SystemBundle\Entity\Text: type: entity fields: id: type: integer id: true generator: strategy: AUTO createdAt: type: datetime gedmo: timestampable: on: create updatedAt: type: datetime gedmo: timestampable: on: update
# TextTranslation.orm.yml Unifik\SystemBundle\Entity\TextTranslation: type: entity fields: id: type: integer id: true generator: strategy: AUTO text: type: text name: type: string length: 255 nullable: true active: type: boolean nullable: true
在可翻译实体中,添加一个 use
语句以包含 Translatable
trait
<?php namespace Unifik\SystemBundle\Entity; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; /** * Text */ class Text { use UnifikORMBehaviors\Translatable\Translatable; /** * @var integer $id */ protected $id; [...] }
在翻译实体中,添加一个 use
语句以包含 Translation
trait
<?php namespace Unifik\SystemBundle\Entity; use Symfony\Component\Validator\ExecutionContextInterface; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; /** * TextTranslation */ class TextTranslation { use UnifikORMBehaviors\Translatable\Translation; /** * @var integer $id */ protected $id; [...] }
您完成了!locale
字段和双向关系将自动注册到实体类元数据中,通过以下名称的 Doctrine 事件监听器
// $translations property $textTranslations = $text->getTranslations(); // $translatable property $text = $textTranslation->getTranslatable(); // $locale property $locale = $text->getLocale()
在 postLoad Doctrine 事件中加载可翻译实体时,将使用当前区域设置。如果您想在一个特定区域设置中加载实体,可以使用 "setCurrentLocale" 方法
$text->setCurrentLocale('fr'); $name = $text->getName(); // Will return the 'fr' version of the $name property
表单
您需要为可翻译和翻译实体构建表单,并在 translation
属性上嵌入 TranslationForm
/** * Text Type */ class TextType extends AbstractType { /** * Build Form * * @param FormBuilderInterface $builder The builder * @param array $options Form options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('translation', new TextTranslationType()) ; } [...] }
/** * Text Translation Type */ class TextTranslationType extends AbstractType { /** * Build Form * * @param FormBuilderInterface $builder The builder * @param array $options Form options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('active', 'checkbox') ->add('name') ->add('text', 'textarea', array('label' => 'Text', 'attr' => array('class' => 'ckeditor'))) ; } [...] }
在 twig 中,您可以使用 form_rest
或逐字段渲染表单
<table class="fields" cellspacing="0"> {{ form_row(form.translation.active) }} {{ form_row(form.translation.name) }} {{ form_row(form.translation.text) }} {{ form_rest(form) }} </table>
可翻译实体仓库
此 trait 与可翻译行为相关。它处理自动 LEFT/INNER JOIN 在翻译表上,以避免在调用 find
、findBy
、findOneBy
和 findAll
方法时获取翻译行时的额外查询。
要使用此 trait,您需要扩展 Doctrine\ORM\EntityRepository
并实现 Symfony\Component\DependencyInjection\ContainerAwareInterface
,如下所示
<?php namespace Unifik\SystemBundle\Entity; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; use Doctrine\ORM\EntityRepository; use Symfony\Component\DependencyInjection\ContainerAwareInterface; /** * SectionRepository */ class SectionRepository extends EntityRepository implements ContainerAwareInterface { use UnifikORMBehaviors\Repository\TranslatableEntityRepository; }
可生成缩略名
此行为实现起来很简单,只需两步
特质
该特质将用于向实体元数据类添加 slug
字段并配置 slug 字段。
您需要添加一个 use
语句以包含 sluggable
特质并定义 getSluggableFields
方法(在特质中声明为抽象)以配置要缩略的字段(1个或多个)
<?php namespace Unifik\SystemBundle\Entity; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; /** * SectionTranslation */ class SectionTranslation { use UnifikORMBehaviors\Sluggable\Sluggable; /** * @var integer $id */ protected $id; [...] /** * Get Sluggable Fields * * @return array */ public function getSluggableFields() { return array('name'); } }
其他方法可以重载以配置行为
getIsSlugUnique
:确定 slug 是否唯一。默认为true
(它通过仅在当前区域设置中查找相似的 slug 支持可翻译行为)。getSlugDelimiter
:slug 分隔符。默认为-
。getRegenerateOnUpdate
:确定是否在可缩略字段被修改时重新生成 slug。默认为true
。如果设置为false
,则仅在 slug 字段设置为NULL
或空字符串时重新生成 slug。
服务
如果您希望使用默认行为,则无需创建服务。
当短链接配置为唯一(通过实体/特性中的 getIsSlugUnique
方法)时,会使用查询构建器在实体的表中进行查询,以找到与由该行为生成的短链接相似(如果是翻译实体,则为同一区域)的短链接。找到短链接后,短链接将追加 "-1"、" -2" 等等。可选地,您可以创建一个新的服务并覆盖默认的 getSelectQueryBuilder
方法来指定不同的查询构建器。查询构建器必须在其他实体上找到与实体相似的短链接。
只需像下面这样使用自己的类作为服务
services: unifik_system.section_translation.sluggable.listener: class: %unifik_system.section_translation.sluggable.listener.class% tags: - { name: doctrine.event_subscriber, type: sluggable, entity: Unifik\SystemBundle\Entity\SectionTranslation }
该类需要扩展 SluggableListener
抽象类。
以下是一个自定义服务的示例。我们尝试只在具有与可短链接的相同父级的实体上找到类似的短链接
// SectionTranslationSluggableListener.php <?php namespace Unifik\SystemBundle\Lib; use Unifik\DoctrineBehaviorsBundle\ORM\Sluggable\SluggableListener; use Doctrine\ORM\EntityManager; use Doctrine\ORM\QueryBuilder; /** * Class SectionTranslationSluggableListener */ class SectionTranslationSluggableListener extends SluggableListener { /** * Returns the Select QueryBuilder that will check for a similar slug in the table * The slug will be valid when the Query returns 0 rows. * * @param string $slug * @param mixed $entity * @param EntityManager $em * * @return QueryBuilder */ public function getSelectQueryBuilder($slug, $entity, EntityManager $em) { $translatable = $entity->getTranslatable(); $queryBuilder = $em->createQueryBuilder() ->select('DISTINCT(s.slug)') ->from('Unifik\SystemBundle\Entity\SectionTranslation', 's') ->innerJoin('s.translatable', 't') ->where('s.slug = :slug') ->andWhere('s.locale = :locale') ->setParameters([ 'slug' => $slug, 'locale' => $entity->getLocale() ]); // On update, look for other slug, not the current entity slug if ($em->getUnitOfWork()->isScheduledForUpdate($entity)) { $queryBuilder->andWhere('t.id <> :id') ->setParameter('id', $translatable->getId()); } // Only look for slug on the same level if ($translatable->getParent()) { $queryBuilder->andWhere('t.parent = :parent') ->setParameter('parent', $translatable->getParent()); } return $queryBuilder; } }
可上传
可上传行为简化了您在 Symfony2 中处理文件上传的方式。使用特性来配置可上传字段,您只需添加 2 个属性
- 一个将包含文件名并将其持久化的属性
- 一个非持久化属性,将包含作为
\Symfony\Component\HttpFoundation\File\UploadedFile
实体的提交的文件
配置
您可以可选地定义您的上传根(绝对)和 web(相对于 /web)文件夹,通过将这些行添加到 config.yml
文件中
unifik_doctrine_behaviors: uploadable: upload_root_dir: ../web/uploads upload_web_dir: /uploads
../web/uploads
和 /uploads
路径是默认值。如果您希望使用默认路径,则无需在 config.yml
中添加任何内容。
您将为实体的每个可上传字段指定不同的 upload_root_dir
子文件夹,我们稍后会看到。
实体
首先,您必须添加一个非持久化属性,并添加一个 use
语句以包含 Uploadable 特性。
该特性包含您需要在实体中定义的 getUploadableFields
抽象方法。该方法返回一个 key => value
数组,列出可上传字段(键)及其相应的上传目录(值)。
您可以根据需要添加任意数量的可上传字段。在此示例中,我们将添加两个可上传字段,如下所示
<?php namespace Unifik\SystemBundle\Entity; use Symfony\Component\HttpFoundation\File\UploadedFile; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; /** * Section */ class Section { use UnifikORMBehaviors\Uploadable\Uploadable; /** * @var integer */ private $id; /** * @var UploadedFile */ private $image; /** * @var UploadedFile */ private $otherImage; /** * Get the list of uploabable fields and their respective upload directory in a key => value array format. * * @return array */ public function getUploadableFields() { return [ 'image' => 'images', 'otherImage' => 'autres_images' ]; } /** * @param UploadedFile $image */ public function setImage($image) { $this->setUploadedFile($image, 'image'); } /** * @return UploadedFile */ public function getImage() { return $this->image; } /** * @param UploadedFile $otherImage */ public function setOtherImage($otherImage) { $this->setUploadedFile($otherImage, 'otherImage'); } /** * @return UploadedFile */ public function getOtherImage() { return $this->otherImage; } [...] }
注意:必须调用特性方法 setUploadedFile
(带有 2 个参数,即 UploadedFile
实例和字段的名称),以设置 UploadedFile
属性。此方法将处理文件命名和文件删除(在文件替换的情况下)。
接下来,您必须为每个可上传字段添加一个持久化字段到实体的模式中。每个持久化属性的名称将是非持久化字段的名称后缀为 "Path"。在此示例中,对于 $image
,我们将有 $imagePath
,对于 $otherImage
,我们将有 $otherImagePath
# Section.orm.yml Unifik\SystemBundle\Entity\Section: type: entity fields: id: type: integer id: true generator: strategy: AUTO imagePath: type: string length: 255 nullable: true otherImagePath: type: string length: 255 nullable: true
生成获取器和设置器
// Section.php [...] /** * @param string $imagePath */ public function setImagePath($imagePath) { $this->imagePath = $imagePath; } /** * @return string */ public function getImagePath() { return $this->imagePath; } /** * @param string $otherImagePath */ public function setOtherImagePath($otherImagePath) { $this->otherImagePath = $otherImagePath; } /** * @return string */ public function getOtherImagePath() { return $this->otherImagePath; } [...]
其他方法可以重载以配置行为
getNamingStrategy
:确定在使用字母数字命名策略重命名文件时使用的命名策略。可用的选项是alphanumeric
、random
和none
。请参阅 phpdoc 以获取详细说明。默认为alphanumeric
。getAlphanumericDelimiter
:在使用字母数字命名策略时的分隔符。默认为-
。getIsUnique
:确定文件名是否应该是唯一的。默认为true
。如果为true
,特性将通过追加 "-1"、" -2" 等等来生成唯一的文件名。如果设置为false
并且已在上传的磁盘上存在相同的文件名,则将覆盖该文件。
表单
只需向您的表单类型添加一个新的 file
字段即可完成
<?php namespace Unifik\SystemBundle\Form\Backend; /** * Section Type */ class SectionType extends AbstractType { /** * Build Form * * @param FormBuilderInterface $builder The Builder * @param array $options Array of options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('image', 'file') ->add('otherImage', 'file') ; } [...] }
控制器
上传过程由该监听器处理。
当实体被删除或文件被替换时,文件将自动从服务器上删除。
可记录时间戳
可记录时间戳的行为是最容易使用的,只需在实体中包含一个特性(Trait),操作就完成了。`updatedAt` 和 `createdAt` 属性将自动添加到实体中。
只将可记录时间戳的特性添加到您的实体中。
<?php namespace Unifik\SystemBundle\Entity; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; /** * Section */ class Section { use UnifikORMBehaviors\Timestampable\Timestampable; /** * @var integer */ private $id; [...] }
可归责
责任行为让您可以追踪哪个用户创建了、更新或删除了一个实体。您可以配置一个用户实体来与责任实体关联,这意味着这些实体将具有与用户实体的一对多关系。如果您未指定用户实体,则将使用当前登录用户的名字,并以字符串形式保存。
要激活责任行为,只需在您希望表现出责任行为的实体中使用特性。
<?php namespace Unifik\SystemBundle\Entity; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; /** * Section */ class Section extends BaseEntity { use UnifikORMBehaviors\Blameable\Blameable; /** * @var integer */ private $id; [...] }
如果您想在用户实体和责任实体之间创建一对多关系,可以配置监听器来自动管理关联,通过将 user_entity
参数设置为完全限定命名空间来实现。
# config.yml unifik_doctrine_behaviors: blameable: user_entity: Unifik\SystemBundle\Entity\User
可软删除
软删除(SoftDeletable)允许您软删除实体,这意味着实体不会被删除,而是在实体被删除时,将设置 `deletedAt` 属性为当前时间戳。
要使实体表现为软删除,只需像下面这样使用 SoftDeletable 特性。
<?php namespace Unifik\SystemBundle\Entity; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; /** * Section */ class Section extends BaseEntity { use UnifikORMBehaviors\SoftDeletable\SoftDeletable; /** * @var integer */ private $id; [...] }
以下是一些在控制器中使用的示例。
<?php $section = new Section(); $em->persist($section); $em->flush(); // Get id $id = $em->getId(); // Now remove it $em->remove($section); // Hey, i'm still here: $section = $em->getRepository('UnifikSystemBundle:Section')->findOneById($id); // But i'm "deleted" $section->isDeleted(); // === true
<?php $section = new Section(); $em->persist($section); $em->flush(); // I'll delete you tomorow $section->setDeletedAt((new \DateTime())->modify('+1 day')); // Ok, I'm here $section->isDeleted(); // === false /* * 24 hours later... */ // Ok I'm deleted $section->isDeleted(); // === true
可元数据化
可记录元数据的行为允许您在实体上指定元数据信息。目前支持三种元数据:标题、描述和关键词。
如果为空,则将使用实体的 `__toString` 方法自动生成 `metaTitle`。
要激活可记录元数据的行为,只需在您希望表现出可记录元数据行为的实体中使用特性。
<?php namespace Unifik\SystemBundle\Entity; use Unifik\DoctrineBehaviorsBundle\Model as UnifikORMBehaviors; /** * Section */ class Section extends BaseEntity { use UnifikORMBehaviors\Metadatable\Metadatable; /** * @var integer */ private $id; [...] }
要添加字段到表单中,只需扩展 `MetadatableType` 并在 `buildForm` 函数中调用父类的 `buildForm` 函数。
<?php namespace Unifik\SystemBundle\Form\Backend; use Symfony\Component\Form\FormBuilderInterface; use Unifik\DoctrineBehaviorsBundle\Form\MetadatableType; /** * Section Translation Type */ class SectionTranslationType extends MetadatableType { /** * Build Form * * @param FormBuilderInterface $builder The Builder * @param array $options Array of options */ public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); $builder ->add('someOtherFields'); } [...] }
可标签化
标签行为允许您为实体添加标签。您可以定义实体是使用全局标签还是特定实体标签。
要激活标签行为,只需在您希望表现出标签行为的实体中使用特性。
getResourceType()
方法定义了此标签与哪种类型的实体相关联。可选地,您可以覆盖此函数以返回您想要的 string
。
此特性有一个具有 getter/setter 的 $tags
属性。标签是延迟加载的,直到您调用 getTags()
getter 之前,不会执行数据库查询。
/** * @var ArrayCollection */ protected $tags; /** * @var \Closure */ protected $tagReference; /** * @var \DateTime */ protected $tagsUpdatedAt; /** * Get Tags * * @return ArrayCollection */ public function getTags() { // Lazy load the tags, only once if (null !== $this->tagReference && null === $this->tags) { $tagReference = $this->tagReference; $this->tagReference = null; // Avoir circular references $tagReference(); } if (null === $this->tags) { $this->tags = new ArrayCollection(); } return $this->tags; } /** * Set Tags * * @param ArrayCollection $tags * @return Taggable */ public function setTags($tags) { $this->tags = $tags; $this->setTagsUpdatedAt(new \DateTime()); } /** * Add Tag * * @param Tag $tag * @return Taggable */ public function addTag($tag) { $this->tags->add($tag); $this->setTagsUpdatedAt(new \DateTime()); return $this; }
已创建 TaggableType
表单类型,用于通过实体表单管理标签。
TaggableType
需要 2 个参数:
resource_type
:资源类型。最佳做法是使用可标签实体的 `getResourceType()` 方法。locale
:用于获取/创建标签的区域设置。
还有 3 个可选参数:
use_fcbkcomplete
:使用 Fcbkcomplete jQuery 插件。这允许您动态创建标签。(默认:true
)allow_add
:允许您直接在实体的表单中添加标签。必须设置use_fcbkcomplete
选项为true
。(默认:true
)use_global_tags
:定义此表单是否使用全局标签或特定实体标签。全局标签将在所有使用全局标签的其他实体之间共享。如果设置为 false,则标签将与使用相同 `resourceType`(在 `getResourceType()` 函数中设置)的其他实体共享。(默认:true
)
有几种方法可以将此字段添加到实体的表单中。最佳方法是使用绑定到表单的实体在 FormsEvents::POST_SET_DATA
事件上。以下是使用 TaggableType
的示例。
最佳方法
<?php namespace Unifik\SystemBundle\Form\Backend; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Unifik\DoctrineBehaviorsBundle\Form\MetadatableType; /** * Section Translation Type */ class SectionTranslationType extends MetadatableType { /** * Build Form * * @param FormBuilderInterface $builder The Builder * @param array $options Array of options */ public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); $builder ->add('active') ->add('name') ->add('slug') ; $builder->addEventListener(FormEvents::POST_SET_DATA, function ($event) { $form = $event->getForm(); $form->add('tags', 'taggable', [ 'resource_type' => $event->getData()->getResourceType(), 'locale' => $event->getData()->getTranslatable()->getCurrentLocale() // Translatable entity ]); }); } [...] }
其他方法
<?php namespace Unifik\SystemBundle\Form\Backend; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Unifik\DoctrineBehaviorsBundle\Form\MetadatableType; /** * Section Translation Type */ class SectionTranslationType extends MetadatableType { /** * Build Form * * @param FormBuilderInterface $builder The Builder * @param array $options Array of options */ public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); $builder ->add('active') ->add('name') ->add('slug') ; $form->add('tags', 'taggable', [ 'resource_type' => $options['data']->getResourceType(), 'locale' => $options['data']->getTranslatable()->getCurrentLocale() // Translatable entity ]); } [...] }
<?php namespace Unifik\SystemBundle\Form\Backend; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Unifik\DoctrineBehaviorsBundle\Form\MetadatableType; /** * Section Translation Type */ class SectionTranslationType extends MetadatableType { /** * Build Form * * @param FormBuilderInterface $builder The Builder * @param array $options Array of options */ public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); $builder ->add('active') ->add('name') ->add('slug') ; $form->add('tags', 'taggable', [ 'use_global_tags' => false, 'resource_type' => 'section', 'locale' => 'en' ]); } [...] }
不需要向控制器添加代码,所有操作都由 Doctrine 事件上的监听器处理。
如果您想使用Fcbkcomplete jQuery 插件,只需在您的页面中包含 JS 和 CSS 文件,并使用包含的 Twig 表单主题,操作如下
{# edit.html.twig #} {% form_theme form with ['form_div_layout.html.twig', 'UnifikDoctrineBehaviorsBundle:Taggable:form_theme.html.twig' %} <form> {{ form_row(form.tags) }} </form>
使用 TagManager
可选地,您可以使用 TagManager
服务在控制器中管理标签。
<?php // Get the TagManager service $this->tagManager = $this->get('unifik_doctrine_behaviors.tag_manager'); // Define a resource type (set to null if you want to use global tags) $resourceType = 'Unifik\BlogBundle\Entity\Article'; // Load or create a new tag $tag = $this->tagManager->loadOrCreateTag('Smallville', $resourceType); // Load or create a list of tags $tagNames = $this->tagManager->splitTagNames('Clark Kent, Loïs Lane, Superman')); $tags = $this->tagManager->loadOrCreateTags($tagNames, $resourceType); // Add a tag on your taggable resource.. $this->tagManager->addTag($tag, $article); // Add a list of tags on your taggable resource.. $this->tagManager->addTags($tags, $article); // Remove a tog on your taggable resource.. $this->tagManager->remove($tag, $article); // Save tagging.. // Note: $article must be saved in your database before (persist & flush) $this->tagManager->saveTagging($article); // Load tagging.. $this->tagManager->loadTagging($article); // Replace all current tags.. $tags = $this->tagManager->loadOrCreateTags(array('Smallville', 'Superman'), $resourceType); $this->tagManager->replaceTags($tags, $article);
与标签相关的查询
标签实体有一个存储库类,其中有两个特别有用的方法
<?php $tagRepo = $em->getRepository('UnifikDoctrineBehaviorsBundle:Tag'); // Define a resource type (set to null if you want to use global tags) $resourceType = 'Unifik\SystemBundle\Entity\Section'; // or $resourceType = $taggableEntity->getResourceType(); // Find all article ids matching a particular query $ids = $tagRepo->getResourceIdsForTag($resourceType, 'footag'); // Get the tags and count for all articles $tags = $tagRepo->getTagsWithCountArray($resourceType); foreach ($tags as $name => $count) { echo sprintf('The tag "%s" matches "%s" articles', $name, $count); } // Get the related blog Article having common tags with a Section entity // This method creates a QueryBuilder with a "resource" alias, which in this case is "Unifik\BlogBundle\Entity\Article" $queryBuilder = $tagRepo->getResourcesByTagsQueryBuilder($section->getTags(), 'Unifik\BlogBundle\Entity\Article'); $queryBuilder->orderBy('resource.updatedAt', 'DESC'); $articles = $queryBuilder->getQuery()->getResult();