Symfony2でEntityRepositoryからContainerを参照できるようにする


EntityRepositoryからContainerを参照したかったので作ってみました。
標準ではEntityRepository内からContainerを参照したい場合

```
global $kernel;
if ($kernel instanceOf \AppCache) {
	$kernel = $kernel->getKernel();
}
$container = $kernel->getContainer();
```

と記述する必要がありますが、

```
$container = $this->getContainer();
```

と記述するだけでContainerを参照できるようになります。

## 環境
* Symfony 2.3.17
* Doctrine 2.4.4
* DoctrineBundle 1.2.0

## 実装する
実装手順は下記の通りです。

### 1. Containerを参照可能なEntityRepositoryクラス(ContainerAwareEntityRepository)を作成

```php:ContainerAwareEntityRepository.php
<?php

namespace Some\MyBundle\Doctrine\ORM;

use Doctrine\ORM\EntityRepository;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class ContainerAwareEntityRepository extends EntityRepository implements ContainerAwareInterface
{
    /**
     * Container
     * @var ContainerInterface
     */
    private $container;

    /**
     * {@inheritdoc}
     */
    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    /**
     * Get Container
     * @return ContainerInterface
     */
    public function getContainer()
    {
        return $this->container;
    }
}

```

### 2. ContainerAwareEntityRepositoryにContainerを設定するRepositoryFactoryクラス(MyRepositoryFactory)を作成

```php:MyRepositoryFactory.php
<?php

namespace Some\MyBundle\Doctrine\ORM\Repository;

use Some\MyBundle\Doctrine\ORM\ContainerAwareEntityRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;

class MyRepositoryFactory extends DefaultRepositoryFactory
{
    /**
     * Container
     * @var ContainerInterface
     */
    private $container;

    /**
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * {@inheritdoc}
     */
    public function getRepository(EntityManagerInterface $entityManager, $entityName)
    {
        $repository = parent::getRepository($entityManager, $entityName);

        if ($repository instanceof ContainerAwareEntityRepository) {
            $repository->setContainer($this->container);
        }

        return $repository;
    }
}
```

サービスへの登録も忘れずに行います。

```yaml:services.yml
services:
    my.doctrine.orm.repository_factory:
        class: Some\MyBundle\Doctrine\ORM\Repository\MyRepositoryFactory
        arguments: [ @service_container ]
```

### 3. MyRepositoryFactoryをEntityManagerに登録するCompilerPassクラス(MyCompilerPass)を作成

CompilerPassは他のBundleで定義されたサービスを操作する場合に利用します。
今回はDoctrineBundleで定義されたサービスに対して操作を行う必要があるので作成します。

```php:MyCompilerPass.php
<?php

namespace Some\MyBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class MyCompilerPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        $repositoryFactory = $container->getDefinition('my.doctrine.orm.repository_factory');
        $container->findDefinition('doctrine.orm.configuration')->addMethodCall('setRepositoryFactory', [$repositoryFactory]);
    }
}
```

### 4. MyCompilerPassをContainerBuilderに登録する

```php:SomeMyBundle.php
<?php

namespace Some\MyBundle;

use Some\MyBundle\DependencyInjection\Compiler\MyCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class SomeMyBundle extends Bundle
{
    /**
     * {@inheritdoc}
     */
    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        $container->addCompilerPass(new MyCompilerPass());
    }
}
```

## 使用する
ContainerAwareEntityRepositoryを継承したクラス内で`$this->getContainer()`するとContainerを参照できます。

```php:SomeTableRepository.php
<?php

namespace Some\MyBundle\Entity\SomeTableRepository;

use Some\MyBundle\Doctrine\ORM\ContainerAwareEntityRepository;

/**
 * SomeTableRepository
 *
 * This class was generated by the Doctrine ORM. Add your own custom
 * repository methods below.
 */
class SomeTableRepository extends ContainerAwareEntityRepository
{
    /**
     * Get Some Data
     * @param int $id
     * @return SomeTable
     */
    public function get($id)
    {
        $container = $this->getContainer();
        $someParam = $container->getParameter('some.parameter');

        $qb = $this
            ->createQueryBuilder('some')
            ->where('some.id = :id')
            ->andWhere('some.someColumn = :someParam')
            ->setParameters([
                'id' => $id,
                'someParam' => $someParam,
            ])
        ;

        $logger = $container->get('logger');
        $logger->debug("Repositoryからログ出力!");

        return $qb->getQuery()->getSingleResult();
    }
}
```