Symfony 2 && Doctrineのいくつかの「グッズ」

この記事では、Symfony 2およびDoctrineの基本的なコンポーネントを使用していくつかの問題を解決する方法について説明します。


Symfony 2で作業した多くの人が、サービスコンテナをモデル(エンティティ)または他のコンポーネント(サービス)に導入する問題に遭遇したと思います。 これを行う方法を知る前に、それが必要かどうかを考えてください。 特定のサービス(ユーティリティ)を作成し、そこに必要なすべてを実行できます。 さて、あなたがそれを必要とすると決めたら、それから:

モデルでのBun No. 1サービスの実装:


それを実装するにはいくつかの方法があり、多くの場合、誰もが最も簡単な方法を選択します。

オプション番号1

グローバルスコープからカーネルを取得し、「愚かに」呼び出します

class MyEntity { // ... properties // ... methods public function someAction() { global $kernel; return $kernel->getContainer() ->..... } } 

私に関しては、この方法は非常にシンプルですが、同じ変数の存在を検証することは不可能なので、非常に漏れやすいです...

オプション番号2

ここでは、100個の単純な関数に対して少し「汗をかく」必要がありますが、このソリューションは最初のものよりもはるかに優れており、完全なパフォーマンスを保証します。 モデルのロード/保存中に何でもできるようにするDoctrineイベントここで使用されます

まず、Doctrineのイベントまたはサブスクライバーを作成する必要があります。これにより、モデルの読み込み中に必要なコンポーネントが実装されます( postLoad )。 そして、すべてのサービスを移転できるように、このビジネスをすべてサービスとして作成する必要があります。

Doctrineのサブスクライバーを作成します。

 namespace Acme\DemoBundle\EventListener; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\LifecycleEventArgs; use Symfony\Component\DependencyInjection\ContainerInterface; use Acme\DemoBundle\Entity\MyEntity; class MyEntitySubscriber implements EventSubscriber { protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function postLoad(LifecycleEventArgs $event) { $entity = $event->getEntity(); //  ,   ,       , //     if ($entity instanceof MyEntity) { $entity-> setMyComponent($this->container->get('....')); } } /** * ,       * :    -    * ,    */ public function getSubscribedEvents() { return array( 'postLoad' ); } } 

新しいモデルは次のようになります。

 class MyEntity { // … properties protected $myComponent; // … methods public function setMyComponent($myComponent) { $this->myComponent = $myComponent; } public function someAction() { return $this->myComponent ->.... } } 

サービスとして登録する

 <service id="my_entity.doctrine.subscriber" class="Acme\DemoBundle\EventListener\MyEntitySubscriber"> <!--    ,     subscriber --> <tag name="doctrine.event_subscriber" /> <argument type="service" id="service_container" /> </service> 

それですべてです。モデルを読み込むときに、システムはこのモデルが必要かどうかをチェックし、必要であれば、セッターを使用して必要なコンポーネントをmyComponent変数に「注入」します。

結論:モデルは読み取り専用/データベースへの書き込みであるため、このような場合は避けてください。

お団子No. 2歴史の保存:


場合によっては、たとえば、将来のロールバックのためにレコード変更の履歴など、モデルの変更の履歴全体を保存する必要があります。 この状況では、教義の出来事自体が大いに役立ちます。つまり、 onFlush

従う必要がある「ニュース」モデル、つまりタイトルフィールドがあるとします。

 class News { protected $title; // ... properties // ... methods } 

さて、追跡するには、変更を正確に制御する別のモデルを作成する必要があります。

 class NewsHistory { protected $title; // ... properties // ... methods } 

イベント自体では、このフィールドが変更されたかどうかを確認する必要があります。 これは、特定のマネージャーのすべてのモデルを注意深く監視するUnitOfWorkに役立ちます。

 namespace Acme\DemoBundle\EventListener; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\OnFlushEventArgs; use Symfony\Component\DependencyInjection\ContainerInterface; use Acme\DemoBundle\Entity\News; use Acme\DemoBundle\Entity\NewsHistory; class NewsSubscriber implements EventSubscriber { public function onFlush(OnFlushEventArgs $event) { $em = $event->getEntityManager(); $uow = $em->getUnitOfWork(); //    ,    foreach ($uow->getScheduledEntityUpdates() as $entity) { //    if ($entity instanceof News) { //    ,   $changeSet = $uow->getEntityChangeSet($entity); //     title? if (isset($changeSet['title'])) { //      list ($oldTitle, $newTitle) = $changeSet['title']; //  entity    $newsHistory = new NewsHistory; $newsHistory->setTitle($oldTitle); //      $em->persist($newsHistory); // !!! //        ,  //  ,   ,  flush  //  ,     ,   // ,   . //     UnitOfWork,    , //       //    ClassMetadata   //    ""     $classMetadata = $em->getClassMetadata(get_class($newsHistory)); //    ""  UnitOfWork,      $uow->computeChangeSet($classMetadata, $newsHistory); } } } } public function getSubscribedEvents() { return array( 'onFlush' ); } } 

さて、いつものように、あなたはサービスとして登録する必要があり、それが教義の加入者であることを示します:

 <service id="news.doctrine.subscriber" class="Acme\DemoBundle\EventListener\NewsSubscriber"> <tag name="doctrine.event_subscriber" /> </service> 

UnitOfWorkは、いわゆるApiであり、これを使用してモデルで不正行為を行うことができます。
同様に、新しいモデルの作成と古いモデルの削除を制御できます。

Bun#3 SQLロガーを無効にし、Doctrineのキャッシュをクリアします:


データベースに対して大量のクエリを実行するコマンドを(コンソールから)実行する必要がある場合があります。 良い例は、データベースに100,000を超えるレコードがある場合に、ある種のCMSまたはフレーマーからSymfony 2にサイトを転送することです。 コンソールからSymphonyでコマンドを実行すると、デフォルトでシステムはdevモードで起動し、デバッグが有効になります。このモードでは、Doctrineは後続のデバッグのすべてのリクエストを保存します。 その結果、スクリプトに割り当てられたメモリは絶えず増加し、その後速度に影響を及ぼし、重大なメモリ制限エラーをスローする可能性があります。 このような状況では、Doctrine(マネージャー)が使用するすべてのロガーを無効にするか、必要なものだけを制御するために完全に再定義できます。 ロガー自体は、ドクトリンの構成に依存しています。

完全なシャットダウンの例:
Php
 $container->get('doctrine') ->getManager() ->getConnection() ->getConfiguration() ->setSQLLogger(null); //      


構成
doctrine:
dbal:
connections:
default:
logging: false


また、 unsetが呼び出されるか、「静的」キャッシュがリセットされるまで、データベースからダウンロードされたすべてのモデルがマネージャーに保存されることを忘れないでください。 これは、同じモデルの選択を常に照会しないように設計されました。 その結果、このような大きなスクリプト(モデルで多くの操作を行う)では、キャッシュを定期的に消去する必要があります。

 $container->get('doctrine')->getManager()->clear(); 

Bun No. 4コンソールの環境の分離:


Symphonyの新しいアセンブリを作成するとき、いくつかのフォルダー( Permission denied )の問題が常に発生します: app / cacheおよびapp / logs 。 この問題は、ブラウザからサイトを既に起動していて、コンソールで何かを実行しようとしているときに発生します。 問題は、Webサーバー自体が別のユーザーで実行されていることです。
サーバー(ルート)に完全にアクセスできる人は、これを行う必要はありません。見栄えが良い: http : //symfony.com/doc/current/book/installation.html#configuration-and-setup
ただし、システムへの完全なアクセス権(ホスティングなど)がなく、すでにこの問題にうんざりしている場合は、スタートアップシステムをdevとコンソールに分割できます。 これは次のように行われます。

  1. config_dev.yml configをインポートするファイルapp / config / config_console.ymlを作成します
  2. app / consoleファイルでは、デフォルトではコンソールではなくdevを設定します
  3. 追加のバンドルが接続されている場所でapp / AppKernel.phpファイルを修正し(環境を確認中)、別のコンソールを追加します

これで、アプリ/コンソールを起動すると、システムはまったく異なる環境で動作します。これは、devに干渉しません
注意 :これは、システムが開発モードの場合のみです!

PSコードは例として作成されたものであり、prodシステムでの作業用ではありません。 Symfony 2.2を使用しました。
PSSあまりキックしないでください...私の最初の投稿。 ご清聴ありがとうございました!

Source: https://habr.com/ru/post/J272367/


All Articles