みなさんこんにちは、山本です。
Symfonyの3系もリリースされた中、今更ながら弊社でもSymfonyの2系を使い始めました。
1系は慣れているのですが、2系は初めてなので不慣れな分、当然戸惑うこともありましたが、概ね好意的な印象を持ちました。
が、それでも幾つか強い不満を覚えるものがありました。
例えば、1系の sfContext::getInstance()です。
2系ですと、Containerなのですが、これがどこでも呼べるものではなくなっていたりします。
これがなければ、configのパラメータですら満足に呼び出せず、非常に難儀してしまいます。
他にもModelにはBaseクラスがあったはずなのですが、2系ではBaseクラスが存在しなくなっていました。
その為、Entityクラスに自動生成のメソッドと追加したものとが混在し、非常に可読性を低くしているように思えました。
何より、一度生成後にスキーマのデフォルト値を変更しても反映されなかったり色々と不便でした。
その不満を、ちょこちょこ調整してきたのでその辺を書き連ねていこうかと思います。
さて、今回はその中でもBaseモデルを作るように拡張をしようと思います。
コマンド
通常Entitiyの生成はコマンドから作成します。
ですので、まずは簡単にコマンドを作るところから始めましょう。
バンドル直下に、Commandディレクトリを生成し、その中に***Command.phpのファイルを作成します。
vi src/CommonBundle/Command/GenerateEntitiesDoctrineCommand.php
<?php
namespace Application\CommonBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
class GenerateEntitiesDoctrineCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('Foobar')
;
}
}
$ app/console
Available commands:
cc Clears the cache
Foobar
help Displays help for a command
list Lists commands
指定したnameのコマンドが一覧に表示されました。
このルールに従って作成すると簡単にコマンドを実装することが出来ます。
とても簡単ですね。
次に、作ったコマンドを拡張してBaseEntityを生成出来るようにしてみます。
vi src/CommonBundle/Command/GenerateEntitiesDoctrineCommand.php
namespace CommonBundle\Command;
use Doctrine\Bundle\DoctrineBundle\Command\GenerateEntitiesDoctrineCommand as BaseGenerateEntitiesDoctrineCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\Tools\EntityRepositoryGenerator;
use Doctrine\Bundle\DoctrineBundle\Mapping\DisconnectedMetadataFactory;
class GenerateEntitiesDoctrineCommand extends BaseGenerateEntitiesDoctrineCommand
{
protected function execute(InputInterface $input, OutputInterface $output)
{
$manager = new DisconnectedMetadataFactory($this->getContainer()->get('doctrine'));
try {
$bundle = $this->getApplication()->getKernel()->getBundle($input->getArgument('name'));
$output->writeln(sprintf('Generating entities for bundle "<info>%s</info>"', $bundle->getName()));
$metadata = $manager->getBundleMetadata($bundle);
} catch (\InvalidArgumentException $e) {
$name = strtr($input->getArgument('name'), '/', '\\');
if (false !== $pos = strpos($name, ':')) {
$name = $this->getContainer()->get('doctrine')->getAliasNamespace(substr($name, 0, $pos)).'\\'.substr($name, $pos + 1);
}
if (class_exists($name)) {
$output->writeln(sprintf('Generating entity "<info>%s</info>"', $name));
$metadata = $manager->getClassMetadata($name, $input->getOption('path'));
} else {
$output->writeln(sprintf('Generating entities for namespace "<info>%s</info>"', $name));
$metadata = $manager->getNamespaceMetadata($name, $input->getOption('path'));
}
}
$generator = $this->getEntityGenerator();
$backupExisting = !$input->getOption('no-backup');
$generator->setBackupExisting($backupExisting);
$repoGenerator = new EntityRepositoryGenerator();
foreach ($metadata->getMetadata() as $m) {
if ($backupExisting) {
$basename = substr($m->name, strrpos($m->name, '\\') + 1);
$output->writeln(sprintf(' > backing up <comment>%s.php</comment> to <comment>%s.php~</comment>', $basename, $basename));
}
// Getting the metadata for the entity class once more to get the correct path if the namespace has multiple occurrences
try {
$entityMetadata = $manager->getClassMetadata($m->getName(), $input->getOption('path'));
} catch (\RuntimeException $e) {
// fall back to the bundle metadata when no entity class could be found
$entityMetadata = $metadata;
}
$baseMeta = clone $m;
$className = str_replace($baseMeta->namespace.'\\', '', $baseMeta->name);
$baseMeta->name = sprintf('%s\\Base\\Base%s', $baseMeta->namespace, $className);
$generator->setRegenerateEntityIfExists(true);
$generator->setFieldVisibility($generator::FIELD_VISIBLE_PROTECTED);
$output->writeln(sprintf(' > generating <comment>%s</comment>', $baseMeta->name));
$generator->generate(array($baseMeta), $entityMetadata->getPath());
require_once $entityMetadata->getPath() . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $baseMeta->name) . '.php';
$m->fieldMappings = [];
$m->reflFields = [];
$m->associationMappings = [];
$generator->setRegenerateEntityIfExists(false);
$generator->setClassToExtend($baseMeta->name);
$output->writeln(sprintf(' > generating <comment>%s</comment>', $m->name));
$generator->generate(array($m), $entityMetadata->getPath());
if ($m->customRepositoryClassName && false !== strpos($m->customRepositoryClassName, $metadata->getNamespace())) {
$repoGenerator->writeEntityRepositoryClass($m->customRepositoryClassName, $metadata->getPath());
}
}
}
}
CoreのGenerateEntitiesDoctrineCommandを継承して、executeをoverrideします。
BaseEntity用にEntityのメタを複製して、BaseEntityのname(namespace)を変更します。
nameを元にEntityファイルを生成するので、上記のソースでは以下の構成でファイルが生成されます。
Entity/Test.php
Entity/Base/BaseTest.php
そして中身が、Baseクラスを継承するようになります。
cat Entity/Test.php
namespace CommonBundle\Entity\Base;
/**
* BaseTest
*/
class BaseTest
{
/*****/
}
cat Entity/Base/BaseTest.php
namespace CommonBundle\Entity\Base;
/**
* Test
*/
class Test extends \CommonBundle\Entity\Base\BaseTest
{
}
又、setRegenerateEntityIfExistsをtrueにしているので、Baseクラスは毎回新規に作り直しをするようになりました。
これで、スキーマを変更しても再生成しても反映されなかったり、自動生成のものと、自作のメソッドが混在することもなくなりました。
次回
・どこでも必要となるContainerの注入
・リスナー、フィルターの作り方
・Fixtureとダミーデータの作り方
・SessionをDBにする
等などと色々と候補があるのでその辺で記事を書こうかと思います。
コメントを残す