In a Symfony 5.0.2 project a test of the new Mailer fails with
Error: Call to a member function getSubject() on null
The email service and test are based on symfonycast tutorials.
Adding var_dump($email); in the service immediately after $email = ...; shows object(Symfony\Bridge\Twig\Mime\TemplatedEmail)#24 (11) {..., which says there is a real object created in the service.
services.yaml:
App\Services\EmailerService:
$mailer: '#mailer'
$senderAddress: '%app.sender_address%'
Service:
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
class EmailerService
{
private $mailer;
private $sender;
public function __construct($mailer, $senderAddress)
{
$this->mailer = $mailer;
$this->sender = $senderAddress;
}
public function appMailer($mailParams)
{
$email = (new TemplatedEmail())
->from($this->sender)
->to($mailParams['recipient'])
->subject($mailParams['subject'])
->htmlTemplate($mailParams['view'])
->context($mailParams['context']);
$this->mailer->send($email);
}
}
Test:
use App\Services\EmailerService;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\MailerInterface;
class MailerTest extends TestCase
{
public function testSimpleMessage()
{
$symfonyMailer = $this->createMock(MailerInterface::class);
$symfonyMailer->expects($this->once())
->method('send');
$mailer = new EmailerService($symfonyMailer, 'admin#bogus.info', 'admin#bogus.info');
$mailParams = [
'view' => 'Email/non_user_forgotten_password.html.twig',
'context' => ['supportEmail' => 'admin#bogus.info'],
'recipient' => 'bborko#bogus.info',
'subject' => 'Test message',
];
$email = $mailer->appMailer($mailParams);
$this->assertSame('Test message', $email->getSubject());
}
}
appMailer() must return a TemplatedEmail object so you can call getSubject() on it. Currently it is returning nothing. Change it to:
public function appMailer($mailParams)
{
$email = (new TemplatedEmail())
->from($this->sender)
->to($mailParams['recipient'])
->subject($mailParams['subject'])
->htmlTemplate($mailParams['view'])
->context($mailParams['context']);
$this->mailer->send($email);
return $email; // I added this line.
}
Related
I have a CrudController for my entity, Participant. I want to add a custom action, sendAcknowledgementEmail. The EasyAdmin docs doesn't mention anything about the custom function parameters or return values.
I have the following code
public function configureActions(Actions $actions): Actions
{
$send_acknowledgement_email = Action::new('sendAcknowledgementEmail', 'Send Acknowledgement Email', 'fa fa-send')
->linkToCrudAction('sendAcknowledgementEmail');
return $actions
->add(Crud::PAGE_INDEX, $send_acknowledgement_email)
->add(Crud::PAGE_EDIT, $send_acknowledgement_email)
;
}
public function sendAcknowledgementEmail() //Do I need parameters?
{
//How do I get the Entity?
//What should I return?
}
So far, EasyAdmin detects the custom function but I get an error "The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned null. Did you forget to add a return statement somewhere in your controller?"
How do I continue from here?
The v3.x of the bundle is quite new and the documentation is not perfect yet.
Based on Ceochronos answer, here is my implementation for a clone action.
public function configureActions(Actions $actions): Actions
{
$cloneAction = Action::new('Clone', '')
->setIcon('fas fa-clone')
->linkToCrudAction('cloneAction');
return $actions
->add(Crud::PAGE_INDEX, $cloneAction);
}
public function cloneAction(AdminContext $context)
{
$id = $context->getRequest()->query->get('entityId');
$entity = $this->getDoctrine()->getRepository(Product::class)->find($id);
$clone = clone $entity;
// custom logic
$clone->setEnabled(false);
// ...
$now = new DateTime();
$clone->setCreatedAt($now);
$clone->setUpdatedAt($now);
$this->persistEntity($this->get('doctrine')->getManagerForClass($context->getEntity()->getFqcn()), $clone);
$this->addFlash('success', 'Product duplicated');
return $this->redirect($this->get(CrudUrlGenerator::class)->build()->setAction(Action::INDEX)->generateUrl());
}
After browsing through the EasyAdmin AbstractCrudController I came up with the following working code.
In order to get the current object you need the parameter AdminContext
For my use case I want to return to the CrudController index action, so for that I can do a redirect.
Note: you need to inject the CrudUrlGenerator service in your constructor controller.
public function sendAcknowledgementEmail(AdminContext $context)
{
$participant = $context->getEntity()->getInstance();
// Your logic
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
My current function looks like this:
public function sendAcknowledgementEmail(AdminContext $context)
{
$participant = $context->getEntity()->getInstance();
$participant->sendAcknowledgementEmail();
$this->addFlash('notice','<span style="color: green"><i class="fa fa-check"></i> Email sent</span>');
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
return $this->redirect($url);
}
My current working code
<?php
namespace App\Controller\Admin;
use App\Service\WebinarService;
use EasyCorp\Bundle\EasyAdminBundle\Router\CrudUrlGenerator;
use Symfony\Contracts\Translation\TranslatorInterface;
// ...
class ParticipantCrudController extends AbstractCrudController
{
private CrudUrlGenerator $crudUrlGenerator;
private WebinarService $webinar_service;
private TranslatorInterface $translator;
public function __construct(CrudUrlGenerator $crudUrlGenerator, WebinarService $webinar_service, TranslatorInterface $translator)
{
$this->crudUrlGenerator = $crudUrlGenerator;
$this->webinar_service = $webinar_service;
$this->translator = $translator;
}
// ...
public function sendAcknowledgementEmail(AdminContext $context): Response
{
$participant = $context->getEntity()->getInstance();
try {
$this->webinar_service->sendAcknowledgementEmail($participant);
$this->addFlash('notice', 'flash.email.sent');
} catch (Exception $e) {
$this->addFlash('error', $this->translator->trans('flash.error', ['message' => $e->getMessage()]));
}
$url = $this->crudUrlGenerator->build()
->setController(ParticipantCrudController::class)
->setAction(Action::INDEX)
->generateUrl()
;
return $this->redirect($url);
}
}
Trying to mock a doctrine repository inside a test, the returnValueMap() is always returning NULL when used with the findOneBy method.
I have mocked two entities then tried to mock their repository with a given return value map. The test fails and debugging shows that the returnValueMap() is returning NULL.
Here is the class to be tested (the denormalizer)
<?php
declare(strict_types=1);
namespace App\Serializer;
use App\Entity\AdditionalService;
use App\Repository\AdditionalServiceRepository;
use Dto\AdditionalServiceCollection;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
class AdditionalServiceCollectionDenormalizer implements DenormalizerInterface
{
/** #var AdditionalServiceRepository */
private $additionalServiceRepository;
public function __construct(AdditionalServiceRepository $additionalServiceRepository)
{
$this->additionalServiceRepository = $additionalServiceRepository;
}
public function denormalize($mappedCsvRow, $class, $format = null, array $context = [])
{
$addtionalServicesCollection = new AdditionalServiceCollection();
foreach ($mappedCsvRow as $fieldName => $fieldValue) {
/** #var AdditionalService $additionalService */
$additionalService = $this->additionalServiceRepository->findOneBy(['name'=>$fieldName]);
if ($additionalService) {
$addtionalServicesCollection->add($additionalService->getId(), $fieldValue);
}
}
return $addtionalServicesCollection;
}
public function supportsDenormalization($data, $type, $format = null)
{
return $type instanceof AdditionalServiceCollection;
}
}
Here is my test class:
<?php
namespace App\Tests\Import\Config;
use App\Entity\AdditionalService;
use App\Repository\AdditionalServiceRepository;
use App\Serializer\AdditionalServiceCollectionDenormalizer;
use PHPUnit\Framework\TestCase;
use Dto\AdditionalServiceCollection;
class AddionalServiceCollectionDenormalizerTest extends TestCase
{
public function provider()
{
$expected = new AdditionalServiceCollection();
$expected->add(1, 22.1)->add(2, 3.1);
return [
[['man_1' => 22.1], $expected],
[['recycling' => 3.1], $expected],
];
}
/**
* #dataProvider provider
* #covers \App\Serializer\AdditionalServiceCollectionDenormalizer::denormalize
*/
public function testDenormalize(array $row, AdditionalServiceCollection $exptected)
{
$manOneService = $this->createMock(AdditionalService::class);
$manOneService->expects($this->any())->method('getId')->willReturn(1);
$recycling = $this->createMock(AdditionalService::class);
$recycling->expects($this->any())->method('getId')->willReturn(2);
$additionalServicesRepoMock = $this
->getMockBuilder(AdditionalServiceRepository::class)
->setMethods(['findOneBy'])
->disableOriginalConstructor()
->getMock();
$additionalServicesRepoMock
->expects($this->any())
->method('findOneBy')
->will($this->returnValueMap(
[
['name'=>['man_1'], $manOneService],
['name'=>['recycling'], $recycling],
]
));
$denormalizer = new AdditionalServiceCollectionDenormalizer($additionalServicesRepoMock);
self::assertEquals($exptected, $denormalizer->denormalize($row, AdditionalServiceCollection::class));
}
}
I had a hard time debugging the PHPUnit library, to figure out finally that it is the findOneBy() method that expects two arguments, among which the second one is optional (set to null)
The willReturnMap() method is as follows:
/**
* Stubs a method by returning a value from a map.
*/
class ReturnValueMap implements Stub
{
/**
* #var array
*/
private $valueMap;
public function __construct(array $valueMap)
{
$this->valueMap = $valueMap;
}
public function invoke(Invocation $invocation)
{
$parameterCount = \count($invocation->getParameters());
foreach ($this->valueMap as $map) {
if (!\is_array($map) || $parameterCount !== (\count($map) - 1)) {
continue;
}
$return = \array_pop($map);
if ($invocation->getParameters() === $map) {
return $return;
}
}
return;
}
I suspected the method was always returning with null because of the unmet condition $parameterCount !== (\count($map) - 1).
A breakpoint confirmed my doubts, and also revealed that $invocation->getParameters() dumps as follows:
array(2) {
[0] =>
array(1) {
'name' =>
string(5) "man_1"
}
[1] =>
NULL
}
Hence, I had to explicitely pass null as second argument.
So finally the working map had to be:
$this->additionalServicesRepoMock
->method('findOneBy')
->willReturnMap([
[['name' => 'man_1'], null, $manOneService],
[['name' => 'recycling'], null, $recyclingService],
]);
It looks like the parameter of returnValueMap() in testDenormalize() needs brackets to make it indexed array.
Here's a slightly modified version of code snippet from the PHPUnit's document:
<?php
namespace App\Tests;
use PHPUnit\Framework\TestCase;
class ReturnValueMapTest extends TestCase
{
public function testReturnValueMapWithAssociativeArray()
{
$stub = $this->createMock(SomeClass::class);
$map = [
[
'name' => ['man_1'],
'Hello'
],
];
$stub->method('doSomething')
->will($this->returnValueMap($map));
// This will fail as doSomething() returns null
$this->assertSame('Hello', $stub->doSomething(['name' => ['man_1']]));
}
public function testReturnValueMapWithIndexedArray()
{
$stub = $this->createMock(SomeClass::class);
$map = [
[
['name' => ['man_1']], // Notice the difference
'Hello'
],
];
$stub->method('doSomething')
->will($this->returnValueMap($map));
$this->assertSame('Hello', $stub->doSomething(['name' => ['man_1']]));
}
}
class SomeClass
{
public function doSomething()
{}
}
First I will explain why and how the solution works and then the problems I have encountered. If you think there is a better way to do what I do, I'd love to hear it. I would also like to know why doctrine behaves in this way.
It turns out that my aplication needs to connect to a different database according to the client. I have a table, in a fixed database, containing the connection information that is used in some request.
I have had success with the following code:
class DynamicEntityManager {
protected $em;
private $request;
private $client_id;
public function __construct(RequestStack $request, EntityManagerInterface $em){
$this->em = $em;
$this->request = $request;
}
public function getEntityManager(ClientConn $client = null) {
$request = $this->request->getCurrentRequest();
if($client == NULL){
$domain = $request->attributes->get('domain');
if($domain == "" || $domain == NULL){
throw new \Exception("Error de conexion", 1);
}
$client = $this->em->getRepository(ClientConn::class)->findOneBy(array(
"subdomain" => $domain
));
if($client == NULL){
throw new \Exception("Error de conexion", 1);
}
}
$connectionDB = $client->getConnection();
$dbdriver = 'oci8';
$conexionSplit = explode(':',$connectionDB);
$dbhost = $conexionSplit[0];
$dbport = $conexionSplit[1];
$dbname = $conexionSplit[2];
$dbuser = $client->getUsuarioBd();
$dbpass = $client->getClaveBd();
$service = false;
$this->client_id = $client->getId();
if(strpos($dbname,'SN=') !== false){
$parts = explode('=',$dbname);
$dbname = $parts[1];
$service = true;
}
$request->attributes->set('client_id',$client->getId());
$conn = array(
'driver' => $dbdriver,
'host' => $dbhost,
'port' => $dbport,
'dbname' => $dbname,
'user' => $dbuser,
'password' => $dbpass,
'service' => $service,
'charset' => 'UTF8',
'schema' => null
);
return EntityManager::create($conn, $this->em->getConfiguration());
}
}
As you can see I return EntityManager::create($conn, $this->em->getConfiguration ()) with the new connection. The way I use it is the next:
/**
* #Route("/api/client/{id}/conf/{confID}", name="conf.show")
* #Method({"GET"})
*/
public function show(ClientConn $client, Request $request, DynamicEntityManager $dem ,$confId){
try {
$em = $dem->getEntityManager($client);
$entity = $em->getRepository(Configuration::class)->find($confId);
return new JsonResponse($entity, 200);
}
catch(\Exception $ex) {
return new JsonResponse([
"excepcion" => $ex->getMessage()
], $ex->getCode());
}
}
It works as expected or so I believed until I saw that when the entity has a custom repository it is unable to use the dynamic connection and therefore the previous route will return a table not found exception.
#ORM\Entity() <-- Works like a charm
#ORM\Entity(repositoryClass="App\Repository\ConfigurationRepository")<-- Table not found.
It works in the repository if I create the connection again, although I do not like the solution. So, what do I want? I would like to be able to use the basic methods like find (), findBy () and others without having to rewrite them every time I use a custom repository.
class ConfigurationRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry, DynamicEntityManager $dem)
{
parent::__construct($registry, Configuration::class);
$this->dem= $dem;
}
public function uglyFind($client, $confID)
{
$query = $this->dem->getEntityManager($client)->createQueryBuilder('conf')
->select("conf")
->from(ConfPedidosLentes::class,'conf')
->where('conf.id = :value')->setParameter('value', $confID)
->getQuery();
return $query->getOneOrNullResult();
}
I will really appreciate any contribution and thought in this matter.
Instead of:
class ConfigurationRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry, DynamicEntityManager $dem)
{
parent::__construct($registry, Configuration::class);
$this->dem= $dem;
}
...
try extending EntityRepository (without using a constructor) and use find as you did in your controller:
use Doctrine\ORM\EntityRepository;
class ConfigurationRepository extends EntityRepository
{
}
ServiceEntityRepository is an optional EntityRepository base class with a simplified constructor for autowiring, that explicitly sets the entity manager to the EntityRepository base class. Since you have not configured your doctrine managers to handle these connections properly (it's not even possible actually with so many connections), ServiceEntityRepository will pass a wrong EntityManager instance to the EntityRepository subclass, that's why you should not extend ServiceEntityRepository but EntityRepository.
I am currently porting my old custom framework to a Symfony-based custom framework using Symfony's components. So far everything is going smoothly, except for the login part. Here are a few details about the project:
I'm using Symfony Security Component v2.8
My sessions are being stored in a database using PDOSessionHandler
I'm using Guard to authenticate my users.
The problem arises when a user tries to login to an admin area using a form. After the form submission, the user is forwarded to the login_check route where all credentials are successfully checked. The user role ROLE_ADMIN is set and finally the user is redirected to a secure page, but then gets redirected automatically back to the login. The order of events is like so:
login -> login_check -> admin -> login
I have done some debugging by setting breakpoints in ContextListener::OnKernelResponse and found out that a token is never saved in the session,because the method returns here:
if (!$event->getRequest()->hasSession()) {
return;
}
Also, I am able to see a session being added to the database table and the session id remains constant throughout the redirect. In the end I am bounced back to the login page and my user is set to .anon Somewhere between /login_check and /admin my token is lost.
I have run out of ideas on how to debug this. I am pasting some code to help get an idea of my setup, but I think these are fine.
My firewall configuration is looking like this
return[
'security'=>[
//Providers
'providers'=>[
'user' => array(
'id' => 'security.user.provider.default',
),
],
//Encoders
'encoders'=>[
'Library\\Security\\Users\\User::class' => array('algorithm' => 'bcrypt', 'cost'=> 15)
],
'firewalls'=>
[
'backend'=>array(
'security' =>true,
'anonymous' => true,
'pattern' => '^/',
'guard' => array(
'authenticators' => array(
'security.guard.form.authenticator',
'security.authenticator.token'
),
'entry_point'=>'security.guard.form.authenticator'
),
),
],
'access_control'=>array(
array('path' => '^/admin', 'roles' => ['ROLE_ADMIN']),
array('path' => '^/api', 'roles' => ['ROLE_API']),
array('path' => '^/pos', 'roles' => ['ROLE_POS']),
array('path' => '^/dashboard', 'roles' => ['ROLE_SUPER_ADMIN']),
array('path' => '^/login', 'roles' => ['IS_AUTHENTICATED_ANONYMOUSLY']),
array('path' => '/', 'roles' => ['IS_AUTHENTICATED_ANONYMOUSLY']),
)
]];
My UserInterface:
class User implements UserInterface, EquatableInterface{
private $username;
private $password;
private $salt;
private $roles;
public function __construct($username, $password, $salt, array $roles)
{
$this->username = $username;
$this->password = $password;
$this->salt = $salt;
$this->roles = $roles;
}
public function getRoles()
{
return $this->roles;
}
public function getPassword()
{
return $this->password;
}
public function getSalt()
{
return $this->salt;
}
public function getUsername()
{
return $this->username;
}
public function eraseCredentials()
{
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof DefaultUserProvider) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}}
My UserProvider
namespace Library\Security\UserProviders;
use Library\Nosh\Project\Project;
use Library\Security\Users\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use PDO;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
class DefaultUserProvider implements UserProviderInterface{
private $db;
private $project;
public function __construct(\PDO $db, Project $project)
{
$this->db = $db;
$this->project=$project;
}
public function loadUserByUsername($username)
{
$projectId = $this->project->id();
$statement = $this->db->prepare("SELECT * FROM users WHERE :userLogin IN (user_login, user_email) AND project_id=:project_id AND user_active=:user_active");
$statement->bindParam(':userLogin', $username, PDO::PARAM_STR);
$statement->bindValue(':user_active', 1, PDO::PARAM_INT);
$statement->bindValue(':project_id', $projectId, PDO::PARAM_INT);
$statement->execute();
if (!$user = $statement->fetch()) {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
$roles = explode(',', $user['user_roles']);
return new User($user['user_login'], $user['user_password'],$salt='',$roles);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'Library\\Security\\Users\\User';
}
}
I was able to solve my own problem after several days of debugging. The reason I had no session is because I had failed to implement a SessionListener to store the session into the request. This should not be an issue for anyone using the Symfony Framework or Silex. It was only an issue for me, because I am actually creating something from scratch.
For anyone wondering how to do this, here are the necessary steps:
Create a class which extends Symfony\Component\HttpKernel\EventListener\SessionListener
Implement the method getSession()
Make sure you add the class to the dispatcher with addSubscriber()
See my example below:
SessionListener
use Symfony\Component\HttpKernel\EventListener\SessionListener as AbstractSessionListener;
class SessionListener extends AbstractSessionListener {
private $container;
public function __construct(Container $container)
{
$this->container=$container;
}
protected function getSession()
{
if (!$this->container->has('session')) {
return;
}
return $this->container->get('session');
}
}
SessionServiceProvider
use Core\Container;
use Interfaces\EventListenerProviderInterface;
use Interfaces\ServiceProviderInterface;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class SessionServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface {
protected $options=[
'cookie_lifetime'=>2592000,//1 month
'gc_probability'=>1,
'gc_divisor'=>1000,
'gc_maxlifetime'=>2592000
];
public function register(Container $container){
switch($container->getParameter('session_driver')){
case 'database':
$storage = new NativeSessionStorage($this->options, new PdoSessionHandler($container->get('db')));
break;
case 'file':
$storage = new NativeSessionStorage($this->options, new NativeFileSessionHandler($container->getParameter('session_dir')));
break;
default:
$storage = new NativeSessionStorage($this->options, new NativeFileSessionHandler($container->getParameter('session_dir')));
break;
}
$container->register('session',Session::class)->setArguments([$storage]);
}
public function subscribe(Container $container, EventDispatcherInterface $dispatcher)
{
$dispatcher->addSubscriber(new SessionListener($container));
}
}
I have some Controllers defined as services and I need to have the classname of my controllers from the route name.
For non-service controllers I can get the route collection with the router service:
$route = $this->router->getRouteCollection()->get($routeName);
//Retrieve an object like that:
Route {
-path: "/admin/dashboard"
-host: ""
-schemes: []
-methods: []
-defaults: array:1 [
"_controller" => "AppBundle\Controller\Admin\AdminController::dashboardAction"
]
-requirements: []
-options: array:1 []
-compiled: null
-condition: ""
}
I can access the controller classname with $route["defaults"]["_controller"] so this is fine.
The issue is with my controllers as services, the _controller attribute is the name of the service, not the Controller class (like app.controller.admin.user:listAction) I have the name of the service but I need to have the classname (AppBundle\Controller\Admin\UserController)
The only solution I came up with is to get the service from the Container and use get_class() on the service but it will have a huge performance impact only to retrieve the class of the controller/service.
Is there any other solution ?
as suggested in https://github.com/FriendsOfSymfony/FOSUserBundle/issues/2751, I implemented a cached map to get route-names resolved to controllers classes and methods.
<?php
// src/Cache/RouteClassMapWarmer.php
namespace App\Cache;
use Symfony\Component\Cache\Simple\PhpFilesCache;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\Routing\RouterInterface;
class RouteClassMapWarmer implements CacheWarmerInterface
{
/** #var ContainerInterface */
protected $container;
/** #var RouterInterface */
protected $router;
public function __construct(ContainerInterface $container, RouterInterface $router)
{
$this->container = $container;
$this->router = $router;
}
public function warmUp($cacheDirectory)
{
$cache = new PhpFilesCache('route_class_map', 0, $cacheDirectory);
$controllers = [];
foreach ($this->router->getRouteCollection() as $routeName => $route) {
$controller = $route->getDefault('_controller');
if (false === strpos($controller, '::')) {
list($controllerClass, $controllerMethod) = explode(':', $controller, 2);
// service_id gets resolved here
$controllerClass = get_class($this->container->get($controllerClass));
}
else {
list($controllerClass, $controllerMethod) = explode('::', $controller, 2);
}
$controllers[$routeName] = ['class' => $controllerClass, 'method' => $controllerMethod];
}
unset($controller);
unset($route);
$cache->set('route_class_map', $controllers);
}
public function isOptional()
{
return false;
}
}
And in my RouteHelper, the implementation reading this looks like this
$cache = new PhpFilesCache('route_class_map', 0, $this->cacheDirectory);
$controllers = $cache->get('route_class_map');
if (!isset($controllers[$routeName])) {
throw new CacheException('No entry for route ' . $routeName . ' forund in RouteClassMap cache, please warmup first.');
}
if (null !== $securityAnnotation = $this->annotationReader->getMethodAnnotation((new \ReflectionClass($controllers[$routeName]['class']))->getMethod($controllers[$routeName]['method']), Security::class))
{
return $this->securityExpressionHelper->evaluate($securityAnnotation->getExpression(), ['myParameter' => $myParameter]);
}
This should be much faster than getting the routeCollection and resolve the service_id:method notated _controller-properties against the container on every request.