如何使用容器将依赖项注入私有服务?

时间:2019-03-14 04:43:03

标签: php symfony symfony4

我正在为Symfony开发Form Request,但是我有一个问题。

我使用参数值解析将FormRequest实例注入到控制器动作中。但是我想向FormRequest实例注入服务,而不在services.yaml文件中将其公开。

这是我的解析器:

<?php

namespace App\Resolver;

use App\Exception\FormValidationException;
use App\Request\FormRequest;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

class ControllerRequestResolver implements ArgumentValueResolverInterface
{
    /**
     * @var SerializerInterface
     */
    private $serializer;

    /**
     * @var ValidatorInterface
     */
    private $validator;

    /**
     * @var DecoderInterface
     */
    private $decoder;

    /**
     * @var ContainerInterface
     */
    private $container;


    public function __construct(
        SerializerInterface $serializer,
        ValidatorInterface $validator,
        DecoderInterface $decoder,
        ContainerInterface $container
    ) {
        $this->serializer = $serializer;
        $this->validator = $validator;
        $this->decoder = $decoder;
        $this->container = $container;
    }

    /**
     * {@inheritdoc}
     * @throws FormValidationException
     */
    public function resolve(Request $request, ArgumentMetadata $argument)
    {
        $data = $this->decoder->decode($request->getContent(), 'json');

        $request->request->replace($data);

        $formRequestClass = $argument->getType();

       /** @var FormRequest $form */
        $form = $this->container->has($formRequestClass)
            ? $this->container->get($formRequestClass)
            : new $formRequestClass();

        $form->initialize(
            $request->query->all(), $request->request->all(), $request->attributes->all(),
            $request->cookies->all(), $request->files->all(), $request->server->all(), $request->getContent()
        );

        if (!$form->authorize()) {
            throw new AccessDeniedHttpException('Access denied.');
        }

        $violations = $this->validator->validate($data, $form->rules());

        if ($violations->count() > 0) {
            throw new FormValidationException($violations, 'Validation error.');
        }

        yield $form;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(Request $request, ArgumentMetadata $argument)
    {
        return (new \ReflectionClass($argument->getType()))->isSubclassOf(FormRequest::class);
    }
}

因此,要在FormRequest中接收具有所需依赖项的$this->container->get($formRequestClass),我需要将其公开。

这是FormRequest:

<?php

namespace App\Request;

use App\Entity\User;
use App\Rule\UniqueConfig;
use App\Service\Randomizer;
use App\Validator\Constraints\UniqueEntity;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Validator\Constraints as Assert;

class UserRequest extends FormRequest
{
    /**
     * @var null|Randomizer
     */
    private $randomizer;


    public function __construct(Randomizer $randomizer)
    {
        $this->randomizer = $randomizer;
    }

    public function authorize(): bool
    {
        return $this->randomizer->getNumber() > 0.5;
    }

    public function rules(): Assert\Collection
    {
        return new Assert\Collection([
            'email' => [
                new Assert\NotBlank(),
                new Assert\Email(),
                new UniqueEntity([
                    'config' => new UniqueConfig(User::class, 'u', 'email', function ($value, QueryBuilder $qb) {
                        // You can add here additional filtering
                    }),
                ]),
            ],
            'firstName' => [
                new Assert\Length(['max' => 255]),
            ],
            'lastName' => [
                new Assert\Length(['max' => 255]),
            ],
        ]);
    }
}

1 个答案:

答案 0 :(得分:0)

感谢@Cerad的帮助!这是总结果:

<?php

namespace App\Locator;

use Symfony\Component\DependencyInjection\ServiceLocator;

class FormRequestServiceLocator extends ServiceLocator
{
}

我将(FormRequestServiceLocator)注入ControllerRequestResolver

<?php

namespace App\DependencyInjection\Compiler;

use App\Locator\FormRequestServiceLocator;
use App\Request\FormRequest;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;

class FormRequestPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->has(FormRequest::class)) {
            return;
        }

        $taggedServices = $container->findTaggedServiceIds('app.form_request.tag');
        $serviceReferences = [];

        foreach ($taggedServices as $id => $tags) {
            if ($id === FormRequest::class) {
                continue;
            }

            $serviceReferences[$id] = new Reference($id);
        }

        $driverLocator = $container->getDefinition(FormRequestServiceLocator::class);
        $driverLocator->setArguments([$serviceReferences]);
    }
}
<?php

namespace App;

// ...

class Kernel extends BaseKernel
{
    // ...

    protected function build(ContainerBuilder $container)
    {
        parent::build($container);

        $container->registerForAutoconfiguration(FormRequest::class)
            ->addTag('app.form_request.tag');

        $container->addCompilerPass(new FormRequestPass());
    }
}