私は最終的に、私のプロジェクトに適したこの問題の解決策を準備することができました. はじめに、私のアーキテクチャのバンドルは「星のように」配置されていると言わざるを得ません。つまり、基本依存モジュールとして機能し、すべてのプロジェクトに存在するコアまたは基本バンドルが 1 つあるということです。他のすべてのバンドルは、それのみに依存できます。他のバンドル間に直接的な依存関係はありません。アーキテクチャが単純であるため、この提案されたソリューションがこの場合に機能することは間違いありません。また、この方法にはデバッグの問題が含まれる可能性があるのではないかと心配していますが、たとえば、構成設定に応じて、簡単にオンまたはオフに切り替えることができるようにすることができます。
基本的な考え方は、関連するエンティティが見つからない場合にエンティティの関連付けをスキップする、独自の ResolveTargetEntityListener を作成することです。これにより、インターフェイスにバインドされたクラスが欠落している場合でも、実行プロセスを続行できます。おそらく、構成のタイプミスの影響を強調する必要はありません。クラスが見つからず、デバッグが困難なエラーが発生する可能性があります。そのため、開発段階でオフにしてから、本番環境でオンに戻すことをお勧めします。このようにして、可能性のあるすべてのエラーが Doctrine によって指摘されます。
実装
実装は、ResolveTargetEntityListener のコードを再利用し、remapAssociation
メソッド内にいくつかの追加コードを配置することで構成されます。これが私の最終的な実装です:
<?php
namespace Name\MyBundle\Core;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Mapping\ClassMetadata;
class ResolveTargetEntityListener
{
/**
* @var array
*/
private $resolveTargetEntities = array();
/**
* Add a target-entity class name to resolve to a new class name.
*
* @param string $originalEntity
* @param string $newEntity
* @param array $mapping
* @return void
*/
public function addResolveTargetEntity($originalEntity, $newEntity, array $mapping)
{
$mapping['targetEntity'] = ltrim($newEntity, "\\");
$this->resolveTargetEntities[ltrim($originalEntity, "\\")] = $mapping;
}
/**
* Process event and resolve new target entity names.
*
* @param LoadClassMetadataEventArgs $args
* @return void
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$cm = $args->getClassMetadata();
foreach ($cm->associationMappings as $mapping) {
if (isset($this->resolveTargetEntities[$mapping['targetEntity']])) {
$this->remapAssociation($cm, $mapping);
}
}
}
private function remapAssociation($classMetadata, $mapping)
{
$newMapping = $this->resolveTargetEntities[$mapping['targetEntity']];
$newMapping = array_replace_recursive($mapping, $newMapping);
$newMapping['fieldName'] = $mapping['fieldName'];
unset($classMetadata->associationMappings[$mapping['fieldName']]);
// Silently skip mapping the association if the related entity is missing
if (class_exists($newMapping['targetEntity']) === false)
{
return;
}
switch ($mapping['type'])
{
case ClassMetadata::MANY_TO_MANY:
$classMetadata->mapManyToMany($newMapping);
break;
case ClassMetadata::MANY_TO_ONE:
$classMetadata->mapManyToOne($newMapping);
break;
case ClassMetadata::ONE_TO_MANY:
$classMetadata->mapOneToMany($newMapping);
break;
case ClassMetadata::ONE_TO_ONE:
$classMetadata->mapOneToOne($newMapping);
break;
}
}
}
switch
エンティティ関係をマップするために使用されるステートメントの前のサイレント リターンに注意してください。関連するエンティティのクラスが存在しない場合、メソッドはエラーのあるマッピングを実行してエラーを生成するのではなく、単に戻ります。これには、フィールドが欠落しているという意味もあります (多対多の関係でない場合)。その場合、外部キーはデータベース内で欠落するだけですが、エンティティ クラスに存在するため、すべてのコードは引き続き有効です (誤って外部キーのゲッターまたはセッターを呼び出しても、メソッドが見つからないというエラーは発生しません)。
活用する
このコードを使用できるようにするには、パラメーターを 1 つ変更するだけです。この更新されたパラメーターを、常にロードされるサービス ファイルまたは他の同様の場所に配置する必要があります。目標は、使用するバンドルに関係なく、常に使用される場所に置くことです。ベースバンドルサービスファイルに入れました:
doctrine.orm.listeners.resolve_target_entity.class: Name\MyBundle\Core\ResolveTargetEntityListener
これにより、元の ResolveTargetEntityListener があなたのバージョンにリダイレクトされます。念のため、キャッシュを配置した後は、キャッシュをクリアしてウォームアップする必要もあります。
テスト
このアプローチが期待どおりに機能することを証明したいくつかの簡単なテストのみを行いました。今後数週間、この方法を頻繁に使用するつもりであり、必要に応じてフォローアップします. また、試してみようと決心した他の人から有益なフィードバックを得たいと思っています。