glorpen/propel-bundle

为 Symfony2 提供的 Propel 事件和模型扩展。

安装次数: 79,087

依赖项: 1

建议者: 0

安全性: 0

星标: 10

关注者: 4

分支: 5

开放问题: 1

类型:symfony-bundle

v1.5.4 2019-05-30 10:15 UTC

README

https://travis-ci.org/glorpen/GlorpenPropelBundle.png?branch=master

为 Symfony2 提供的附加 Propel 集成。

官方仓库

用于分支和其他有趣的事情

GitHub: https://github.com/glorpen/GlorpenPropelBundle - 主仓库

BitBucket: https://bitbucket.org/glorpen/glorpenpropelbundle

支持的 Symfony2 版本

此捆绑包目前从版本 2.3 开始支持 Symfony2。

对于 Symfony2 3.x 项目,您必须指定 propel/propel-bundle 的 dev 依赖项,例如

{
    "require": {
        "symfony/symfony": "^3.3",
        "propel/propel-bundle": "1.6.x-dev",
        "glorpen/propel-bundle": "^1.5"
    }
}

如何安装

  • 将需求添加到 composer.json
{
    "require": {
        "glorpen/propel-bundle": "@dev"
    }
}
  • 在您的 AppKernel 类中启用插件

app/AppKernel.php

<?php

class AppKernel extends AppKernel
{
   public function registerBundles()
   {
       $bundles = array(
           ...
           new Glorpen\Propel\PropelBundle\GlorpenPropelBundle(),
           ...
       );
   }
}
  • 将行为配置添加到 propel 配置中

要一次性启用所有行为,您可以将 @GlorpenPropelBundle/Resources/config/config.ymlconfig_dev.yml 分别导入到您的配置中。

例如 config.yml

imports:
    - { resource: @GlorpenPropelBundle/Resources/config/config.yml }

Propel 事件

如果您没有导入此捆绑包提供的 config.yml,则必须将 event 行为添加到您的 propel 配置中,并更改 PropelPDO 类。

propel:
  build_properties:
    propel.behavior.event.class: 'vendor.glorpen.propel-bundle.src.Behaviors.EventBehavior'
    propel.behavior.default: "event"
  dbal:
    classname: Glorpen\Propel\PropelBundle\Connection\EventPropelPDO

config_dev.yml

propel:
  dbal:
    classname: Glorpen\Propel\PropelBundle\Connection\EventDebugPDO

监听 propel 钩子

使用订阅者

<service class="SomeBundle\Listeners\HistoryBehaviorListener">
        <argument type="service" id="security.context" />
        <tag name="propel.event" />
</service>

使用监听器

<service id="my.listener" class="SomeBundle\Listeners\HistoryBehaviorListener">
        <tag name="propel.event" method="onPropelEventSave" event="model.save.post" priority="0" />
</service>

priority 属性是可选的。

在两种情况下,您都可以使用 class 属性缩小接收事件的范围到给定的类

<service id="my.listener" class="SomeBundle\Listeners\HistoryBehaviorListener">
   <tag name="propel.event" method="onPropelEventSave" event="model.save.post" class="SomeBundle\Model\Example" />
</service>

可用事件

事件类:ConnectionEvent

  • connection.create
  • connection.begin.pre
  • connection.begin.post
  • connection.commit.pre
  • connection.commit.post
  • connection.rollback.post
  • connection.rollback.pre

事件类:ModelEvent

  • model.insert.post
  • model.update.post
  • model.delete.post
  • model.save.post
  • model.insert.pre
  • model.update.pre
  • model.delete.pre
  • model.save.pre
  • model.update.after
  • model.insert.after
  • model.save.after
  • model.construct
  • model.hydration.post (连接参数始终为 null)

名为 model.*.after 的事件在事务提交后触发,但在从 $model->save() 方法返回之前。

此外,它仅在更新/插入了某些内容时才会触发,它不会在空保存时触发,例如:$model->save()->save()。

事件类:QueryEvent

  • query.delete.pre
  • query.delete.post
  • query.select.pre
  • query.update.pre
  • query.update.post
  • query.construct

事件类:PeerEvent

  • peer.construct

在模型/查询/同伴构造/删除/更新等操作时调用

模型 ContainerAwareInterface

您可以在模型上实现 ContainerAwareInterface 以通过内置服务访问 Container。在 model.construct 事件中注入容器。

如果您遇到“Serialization of 'Closure' is not allowed”之类的错误,这可能是由于模型中注入了一些不可序列化的服务(因为 propel 有时会对数据进行序列化和反序列化)。

<?php

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class Something extends BaseSomething implements ContainerAwareInterface
{
   private $someService;

   public function setContainer(ContainerInterface $container = null){
      if($container) $this->someService = $this->container->get("some_service");
   }
}

事务事件

与 Doctrine @ORMHasLifecycleCallbacks 类似,您可以在数据库事务中处理模型中的非数据库逻辑。

提交钩子将在 PDO 事务提交之前运行,回滚将在回滚之前进行,并且仅对已保存的模型(如果预提交钩子中抛出了异常)执行。由 EventBehavior 提供的方法有

  • preCommit
  • preCommitSave
  • preCommitUpdate
  • preCommitInsert
  • preCommitDelete
  • preRollback
  • preRollbackSave
  • preRollbackUpdate
  • preRollbackInsert
  • preRollbackDelete

请注意,当使用大量模型对象的事务,并且有按需格式化时,它们仍然将缓存在服务中,因此您可能会耗尽可用的 PHP 内存。

以下是一个示例,展示如何使用可用的钩子(代码大部分借鉴自Symfony2菜谱)

<?php
class SomeModel extends BaseSomeModel {
   public function preCommitSave(\PropelPDO $con = null){
      $this->upload();
   }
   public function preCommitDelete(\PropelPDO $con = null){
      $this->removeUpload();
   }

   public function preSave(\PropelPDO $con = null){
      $this->preUpload();
      return parent::preSave($con);
   }

   // code below is copied from https://symfony.com.cn/doc/2.1/cookbook/doctrine/file_uploads.html

   public $file;

   public function preUpload(){
      if (null !== $this->file){
         // do whatever you want to generate a unique name
         $filename = sha1(uniqid(mt_rand(), true));
         $this->path = $filename.'.'.$this->file->guessExtension();
      }
   }

   public function upload(){
      if (null === $this->path) return;

      // if there is an error when moving the file, an exception will
      // be automatically thrown by move(). This will properly prevent
      // the entity from being persisted to the database on error
      $this->file->move($this->getUploadRootDir(), $this->path);
      throw new \RuntimeException("file cannot be saved");

      unset($this->path);
   }

   public function removeUpload(){
      if ($file = $this->getAbsolutePath()){
         unlink($file);
      }
   }
}

自定义事件

您可以使用通用或自定义的事件类来触发事件,以下示例中使用的是 ValidationEvent

  • 创建 ValidationEvent 事件
<?php

namespace YourBundle\Events;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\EventDispatcher\Event;

class ValidationEvent extends Event {
   private $metadata;

   public function __construct(ClassMetadata $metadata){
      $this->metadata = $metadata;
   }

   /**
    * @return \Symfony\Component\Validator\Mapping\ClassMetadata
    */
   public function getMetadata(){
      return $this->metadata;
   }
}
  • services.xml 中注册监听器
<service id="your.service" class="%your.service.class%">
   <argument>%your.service.argument%</argument>
   <tag name="propel.event" method="onProductLoadValidatorMetadata" event="product.validation" />
</service>
  • 然后在模型类中使用它
<?php

namespace YourBundle\Model;
use YourBundle\Events\ValidationEvent;
use Glorpen\Propel\PropelBundle\\Dispatcher\EventDispatcherProxy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use YourBundle\Model\om\BaseProduct;

class Product extends BaseProduct {
   public static function loadValidatorMetadata(ClassMetadata $metadata)
   {
      EventDispatcherProxy::trigger('product.validation', new ValidationEvent($metadata));
   }
}

模型扩展

如果您没有导入此包提供的 config.yml 文件,您必须在您的 propel 配置中添加 extend 行为。

propel:
  build_properties:
    propel.behavior.extend.class: 'vendor.glorpen.propel-bundle.Glorpen.Propel.PropelBundle.Behaviors.ExtendBehavior'
    propel.behavior.default: "extend"

启用行为后,您可以为 Propel 定义自定义的模型类。

您只能以这种方式扩展 Model 类(通常不需要扩展 Peers/Queries)。

调用 Query::find()、Peer::populateObject() 等方法现在将返回您的扩展类对象。

简而言之,它解决了以下问题:

  • 扩展其他包(例如 FOSUserBundle)使用的 Model 类
  • 返回正确实例的查询/peer
  • 调用 SomeQuery::create() 时创建正确的 Query 实例

映射用法

config.yml

glorpen_propel:
  extended_models:
    FOS\UserBundle\Propel\User: MyApp\MyBundle\Propel\User

动态/服务用法

您可以使用服务创建动态扩展。

您的服务应该实现 GlorpenPropelPropelBundleProviderOMClassProvider 接口。

services.xml

<service id="your.service" class="%your.service.class%">
   <argument>%your.service.argument%</argument>
   <tag name="propel.om" />
</service>

FOSUserBundle 和 AdminGenerator

使用上述配置,您可以使用 AdminGeneratorFOSUser 生成后端,用于编辑/创建等。目前,您必须创建空的 UserQuery 和 UserPeer 类,然后整个用户模型的后端都应该工作:)

其他优点

PlainModelJoin

允许将数据注入 ON 子句,例如比较字段与日期或来自其他连接表的字段。

请记住,提供的值将被原样添加,而不进行别名解析和转义。

用法

<?php
$relationAlias = 'WithoutCurrentSubscription';

$join = PlainModelJoin::create($this, 'Subscription', $relationAlias, \Criteria::LEFT_JOIN);

//active items...
$join->addCondition($relationAlias.'.starts_at', '"'.$now->format('Y-m-d H:i:s').'"', \Criteria::LESS_EQUAL);
$join->addCondition($relationAlias.'.ends_at', '"'.$now->format('Y-m-d H:i:s').'"', \Criteria::GREATER_EQUAL);

//...and inversion
$this->where('WithoutCurrentSubscription.Id is null');