glorpen / propel-bundle
为 Symfony2 提供的 Propel 事件和模型扩展。
Requires
- propel/propel-bundle: 1.*
- symfony/symfony: >=2.3,<5.0
Requires (Dev)
- phpunit/phpunit: >=4.8,<7.0
- squizlabs/php_codesniffer: *
README
为 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.yml 和 config_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
使用上述配置,您可以使用 AdminGenerator 为 FOSUser 生成后端,用于编辑/创建等。目前,您必须创建空的 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');