Add function that is called on each request in Symfony2 - php

I need to add a function in Symfony2 that has to be called on each request. (language detection on requestion & session)
I thought to do this in the constructor of my Controller classes, but there the container is not known / created.
Have you suggestions for this?

You can define your Event Listener
Please, read documentation about event listeners creation.

Here is a listener that redirects to a page with the language set in the user configuration. Adapt it to your needs.
<?php
namespace MyVendor\Listener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Routing\RouterInterface;
use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\InjectParams;
use JMS\DiExtraBundle\Annotation\Observe;
/**
* #Service
*/
class LanguageListener
{
/**
* #var \Symfony\Component\Security\Core\SecurityContextInterface
*/
private $securityContext;
/**
* #var \Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* #InjectParams
*
* #param \Symfony\Component\Security\Core\SecurityContextInterface $securityContext
* #param \Symfony\Component\Routing\RouterInterface $router
*/
public function __construct(
SecurityContextInterface $securityContext,
RouterInterface $router
) {
$this->securityContext = $securityContext;
$this->router = $router;
}
/**
* #Observe("kernel.request")
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function forceLanguage(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$token = $this->securityContext->getToken();
if (!$token) {
return;
}
if (!$this->securityContext->isGranted('ROLE_USER')) {
return;
}
/** #var $request \Symfony\Component\HttpFoundation\Request */
$request = $event->getRequest();
$locale = $request->getLocale();
$route = $request->get('_route');
if ('_' === $route[0]) {
return;
}
/** #var $user \MyVendor\Model\User */
$user = $token->getUser();
if ($user->getConfig()->getLanguage() !== $locale) {
$parameters = array_merge($request->attributes->get('_route_params'), [
'_locale' => $user->getConfig()->getLanguage(),
]);
$path = $this->router->generate($route, $parameters);
$event->setResponse(new RedirectResponse($path));
}
}
}

I believe that it depends on what you are trying to do. For language detection most of the time symfony and its bundles handle virtually everything. That means that if you want to customize the routing you have to extend the routing component by using routing.loader tag..
However if you can use event listeners but I am not sure how many stuff you can change from there.

Either use events as suggested above or if you need something quick.
You can override setContainer method.
namespace My\Namespace;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyController extends Controller
{
private $foo;
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->foo = 'bar';
}
// your actions
}

Related

How to use a DoctrineParamConverter from inside a Data Transformer Object class for a request?

I will start saying I am using Symfony 4.3.4 and Api Platform (called AP from now on). Having said that this how my custom controller (used for AP) looks like:
declare(strict_types=1);
namespace App\Controller\CaseWork\Pend;
use App\Request\PendCaseRequest;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Routing\Annotation\Route;
class PendCaseController
{
/**
* #Route("/myroute/{id}/pend", name="routeName")
* #ParamConverter("case", class="App\Entity\Cases")
*/
public function __invoke(PendCaseRequest $request, int $id)
{
// do something with the $request
}
}
As you may notice I also have a Request Data Transformer Object and here is a code snippet for it:
declare(strict_types=1);
namespace App\Request;
use App\Interfaces\RequestDTOInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraints as Assert;
class PendCaseRequest implements RequestDTOInterface
{
/**
* #var int
*
* #Assert\NotBlank()
* #Assert\NotNull()
* #Assert\Type("integer")
*/
private $param;
public function __construct(Request $request)
{
$data = json_decode($request->getContent(), true);
$this->param = (int) $data['param'];
// ...
}
}
It's suppose (as per docs here) that when the request comes in and an id matching a App\Entity\Cases is found a new attribute named case should be append to my $request object but in my scenario is not happening and I am not sure why or what I am missing.
While debugging and setting a break point at this line $this->param = (int) $data['param']; in my DTO, if I print out $this->attributes I got the following output:
‌Symfony\Component\HttpFoundation\ParameterBag::__set_state(array(
'parameters' =>
array (
),
))
What I am missing here? What is wrong with my approach?
I have found a "solution" here. I end up using a Decorator as suggested by the answer on that post.
My main controller changed into this:
declare(strict_types=1);
namespace App\Controller\CaseWork\Pend;
use App\Request\PendCaseRequest;
use App\Entity\Cases;
class PendCaseController
{
public function __invoke(PendCaseRequest $request, Cases $case)
{
// do something with the $request
}
}
A decorator was created:
declare(strict_types=1);
namespace App\Decorator;
use App\Controller\CaseWork\Pend\PendCaseController;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Cases;
use App\Request\PendCaseRequest;
class PendCaseDecorator
{
/** #var PendCaseController */
protected $decoratedController;
/** #var EntityManagerInterface */
protected $entityManager;
public function __construct(PendCaseController $controller, EntityManagerInterface $entityManager)
{
$this->decoratedController = $controller;
$this->entityManager = $entityManager;
}
public function __invoke(PendCaseRequest $request, int $id)
{
$object = $this->entityManager->getRepository(Cases::class)->find($id);
if (!$object instanceof Cases) {
throw new NotFoundHttpException('Entity with '.$id.' not found');
}
return $this->decoratedController($request, $object);
}
}
And I had registered it at services.yml:
services:
App\Controller\CaseWork\Pend\PendCaseController: ~
App\Decorator\PendCaseDecorator:
decorates: App\Controller\CaseWork\Pend\PendCaseController
That way I keep using my DTO and pass back a Cases entity object.

ZF3 Dependency injection in Module.php

I'm currently migrating a ZF2 application to ZF3.
Mostly everything is going smoothly but I'm stuck on one thing.
In my Module.php, I have an ACL management using zend-permissions-acl.
class Module
{
protected $defaultLang = 'fr';
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
if (!$e->getRequest() instanceof ConsoleRequest){
$eventManager->attach(MvcEvent::EVENT_RENDER_ERROR, array($this, 'onRenderError'));
$eventManager->attach(MvcEvent::EVENT_RENDER, array($this, 'onRender'));
$eventManager->attach(MvcEvent::EVENT_FINISH, array($this, 'onFinish'));
$this->initAcl($e);
$eventManager->attach('route', array($this, 'checkAcl'));
}
}
public function checkAcl(MvcEvent $e) {
$app = $e->getApplication();
$sm = $app->getServiceManager();
$route = $e -> getRouteMatch() -> getMatchedRouteName();
$authService = $sm->get('AuthenticationService');
$jwtService = $sm->get('JwtService');
$translator = $sm->get('translator');
$identity = null;
try {
$identity = $jwtService->getIdentity($e->getRequest());
} catch(\Firebase\JWT\ExpiredException $exception) {
$response = $e->getResponse();
$response->setStatusCode(401);
return $response;
}
if(is_null($identity) && $authService->hasIdentity()) { // no header being passed on... we try to use standard validation
$authService->setJwtMode(false);
$identity = $authService->getIdentity();
}
$userRole = 'default';
$translator->setLocale($this->defaultLang);
if(!is_null($identity))
{
$userRole = $identity->getType();
//check if client or prospect
if($userRole >= User::TYPE_CLIENT)
{
$userManagementRight = UserRight::CREATE_USERS;
if($identity->hasRight($userManagementRight))
$userRole = 'userManagement';
}
$translator->setLocale($identity->getLang());
}
if (!$e->getViewModel()->acl->isAllowed($userRole, null, $route)) {
$response = $e -> getResponse();
$response->setStatusCode(403);
return $response;
}
public function initAcl(MvcEvent $e) {
//here is list of routes allowed
}
}
My issue here is that I'm still using the getServiceManager and therefore getting the deprecated warning : Usage of Zend\ServiceManager\ServiceManager::getServiceLocator is deprecated since v3.0.0;
Basically, I just need to inject dependencies into Module.php.
I guess otherwise I would have to move the checkAcl to the Controller directly and inject the ACL in them ? Not sure what is the proper way of doing this.
Any feedback on this would be greatly appreciated.
Regards,
Robert
To solve the issue you should use a Listener class and Factory. It would also help you with more separation of concerns :)
You seem quite capable of figuring stuff out, judging by your code. As such, I'm just going to give you an example of my own, so you should fill yours in with your own code (I'm also a bit lazy and do not wish to rewrite everything when I can copy/paste my code in ;) )
In your module.config.php:
'listeners' => [
// Listing class here will automatically have them "activated" as listeners
ActiveSessionListener::class,
],
'service_manager' => [
'factories' => [
// The class (might need a) Factory
ActiveSessionListener::class => ActiveSessionListenerFactory::class,
],
],
The Factory
<?php
namespace User\Factory\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use Interop\Container\ContainerInterface;
use User\Listener\ActiveSessionListener;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
class ActiveSessionListenerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var ObjectManager $entityManager */
$entityManager = $container->get(EntityManager::class);
/** #var AuthenticationService $authenticationService */
$authenticationService = $container->get(AuthenticationService::class);
return new ActiveSessionListener($authenticationService, $entityManager);
}
}
The Listener
<?php
namespace User\Listener;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use User\Entity\User;
use Zend\Authentication\AuthenticationService;
use Zend\EventManager\Event;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\Mvc\MvcEvent;
/**
* Class ActiveSessionListener
*
* #package User\Listener
*
* Purpose of this class is to make sure that the identity of an active session becomes managed by the EntityManager.
* A User Entity must be in a managed state in the event of any changes to the Entity itself or in relations to/from it.
*/
class ActiveSessionListener implements ListenerAggregateInterface
{
/**
* #var AuthenticationService
*/
protected $authenticationService;
/**
* #var ObjectManager|EntityManager
*/
protected $objectManager;
/**
* #var array
*/
protected $listeners = [];
/**
* CreatedByUserListener constructor.
*
* #param AuthenticationService $authenticationService
* #param ObjectManager $objectManager
*/
public function __construct(AuthenticationService $authenticationService, ObjectManager $objectManager)
{
$this->setAuthenticationService($authenticationService);
$this->setObjectManager($objectManager);
}
/**
* #param EventManagerInterface $events
*/
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $listener) {
if ($events->detach($listener)) {
unset($this->listeners[$index]);
}
}
}
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$events->attach(MvcEvent::EVENT_ROUTE, [$this, 'haveDoctrineManagerUser'], 1000);
}
/**
* #param Event $event
*
* #throws \Doctrine\Common\Persistence\Mapping\MappingException
* #throws \Doctrine\ORM\ORMException
*/
public function haveDoctrineManagerUser(Event $event)
{
if ($this->getAuthenticationService()->hasIdentity()) {
// Get current unmanaged (by Doctrine) session User
$identity = $this->getAuthenticationService()->getIdentity();
// Merge back into a managed state
$this->getObjectManager()->merge($identity);
$this->getObjectManager()->clear();
// Get the now managed Entity & replace the unmanaged session User by the managed User
$this->getAuthenticationService()->getStorage()->write(
$this->getObjectManager()->find(User::class, $identity->getId())
);
}
}
/**
* #return AuthenticationService
*/
public function getAuthenticationService() : AuthenticationService
{
return $this->authenticationService;
}
/**
* #param AuthenticationService $authenticationService
*
* #return ActiveSessionListener
*/
public function setAuthenticationService(AuthenticationService $authenticationService) : ActiveSessionListener
{
$this->authenticationService = $authenticationService;
return $this;
}
/**
* #return ObjectManager|EntityManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* #param ObjectManager|EntityManager $objectManager
*
* #return ActiveSessionListener
*/
public function setObjectManager($objectManager)
{
$this->objectManager = $objectManager;
return $this;
}
}
The important bits:
The Listener class must implement ListenerAggregateInterface
Must be activated in the listeners key of the module configuration
That's it really. You then have the basic building blocks for a Listener.
Apart from the attach function you could take the rest and make that into an abstract class if you'd like. Would save a few lines (read: duplicate code) with multiple Listeners.
NOTE: Above example uses the normal EventManager. With a simple change to the above code you could create "generic" listeners, by attaching them to the SharedEventManager, like so:
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$sharedManager = $events->getSharedManager();
$sharedManager->attach(SomeClass::class, EventConstantClass::SOME_STRING_CONSTANT, [$this, 'callbackFunction']);
}
public function callbackFunction (MvcEvent $event) {...}

What design pattern to use for a time-measurement profiler service?

I have a symfony2 application. It abstracts a bunch of external APIs, all of them implementing an ExternalApiInterface.
Each ExternalApiInterface has a lot of methods, e.g. fetchFoo and fetchBar.
Now, I want to write a service that measures the time of each method call of an instance of an ExternalApiInterface.
My current thinking is to implement a StopWatchExternalApiDecorator, that wraps each method call. Yet this approach leads, in my understanding, to code duplication.
I think I am going to use the StopWatch component for the time measurement, yet this feels odd:
class StopWatchExternalApiDecorator implements ExternalApiInterface {
public function __construct(ExternalApiInterface $api, Stopwatch $stopWatch)
{
$this->api = $api;
$this->stopWatch = $stopWatch;
}
public function fetchBar() {
$this->stopWatch->start('fetchBar');
$this->api->fetchBar()
$this->stopWatch->stop('fetchBar');
}
public function fetchFoo() {
$this->stopWatch->start('fetchFoo');
$this->api->fetchFoo()
$this->stopWatch->stop('fetchFoo');
}
}
It seems like I am hurting the DNRY (do not repeat yourself) approach. Am I using the right pattern for this kind of problem, or is there something else more fit? More fit in the sense of: One place to do all the measurement, and no code duplication.
I also dislike of having to touch the decorator in case there will be a new method in the interface. In my mind, that should be independent.
i am thinking of some apis i worked on that use one generic function for calls and a method parameter
heres some very basic pseudocode
public function call($method = 'fetchBar',$params=array()){
$this->stopWatch->start($method);
$this->{"$method"}($params);
$this->stopWatch->stop($method);
}
private function fetchBar(){
echo "yo";
}
maybe that helps
I went with the decorator approach, just on a different level.
In my architecture, api service was using an HttpClientInterface, and each request was handled in the end with a call to doRequest. So there, the decorator made most sense without code duplication:
<?php
namespace Kopernikus\BookingService\Component\Http\Client;
use Kopernikus\BookingService\Component\Performance\PerformanceEntry;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* ProfileClientDecorator
**/
class ProfileClientDecorator implements HttpClientInterface
{
/**
* #var Stopwatch
*/
private $stopwatch;
/**
* #var HttpClientInterface
*/
private $client;
/**
* #var LoggerInterface
*/
private $logger;
/**
* ProfileClientDecorator constructor.
* #param HttpClientInterface $client
* #param Stopwatch $stopwatch
* #param LoggerInterface $logger
*/
public function __construct(HttpClientInterface $client, Stopwatch $stopwatch, LoggerInterface $logger)
{
$this->client = $client;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
}
/**
* #param RequestInterface $request
*
* #return ResponseInterface
*/
public function doRequest(RequestInterface $request)
{
$method = $request->getMethod();
$response = $this->doMeasuredRequest($request, $method);
$performance = $this->getPerformance($method);
$this->logPerformance($performance);
return $response;
}
/**
* #param RequestInterface $request
* #param string $method
*
* #return ResponseInterface
*/
protected function doMeasuredRequest(RequestInterface $request, $method)
{
$this->stopwatch->start($method);
$response = $this->client->doRequest($request);
$this->stopwatch->stop($method);
return $response;
}
/**
* #param $method
* #return PerformanceEntry
*/
protected function getPerformance($method)
{
$event = $this->stopwatch->getEvent($method);
$duration = $event->getDuration();
return new PerformanceEntry($duration, $method);
}
/**
* #param PerformanceEntry $performance
*/
protected function logPerformance(PerformanceEntry $performance)
{
$context = [
'performance' => [
'duration_in_ms' => $performance->getDurationInMs(),
'request_name' => $performance->getRequestName(),
],
];
$this->logger->info(
"The request {$performance->getRequestName()} took {$performance->getDurationInMs()} ms",
$context
);
}
}
And in my services.yml:
performance_client_decorator:
class: Kopernikus\Component\Http\Client\ProfileClientDecorator
decorates: http.guzzle_client
arguments:
- #performance_client_decorator.inner
- #stopwatch
- #logger

Refactor some calls on each Zf2 controller action

I need to do a custom isGranted method (not using Rbac or acl module from community). So I have a service which provides the functionality. But this code:
if (!$this->userService->isGrantedCustom($this->session->offsetGet('cod_lvl'), 'ZF_INV_HOM')) {
throw new \Exception("you_are_not_allowed", 1);
}
...is duplicated in each controller and each action I have. Parameters are changing of course depends on the permission ('ZF_INV_HOM', 'ZF_TODO_DELETE' ...).
I think it's not a bad idea to do this code before the controller is called, but I can't figure what is the best solution (best architecture), and how to pass those parameters to it (I thought about annotation on controllers but how to handle this ?).
The point is, if I have to modify this code I can't imagine to do that hundreds of times, for each controllers, each action I have I need to have this code in one place.
If you don't want to pollute your Module with all this code you can also make a listener class and attach only the listener in your bootstrap method:
<?php
namespace Application\Listener;
use Application\Service\UserService;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Mvc\MvcEvent;
use Zend\EventManager\SharedEventManagerInterface;
use Zend\EventManager\SharedListenerAggregateInterface;
use Zend\Authentication\AuthenticationServiceInterface;
class IsAllowedListener implements SharedListenerAggregateInterface
{
/**
* #var AuthenticationServiceInterface
*/
protected $authService;
/**
* #var UserService
*/
protected $userService;
/**
* #var \Zend\Stdlib\CallbackHandler[]
*/
protected $sharedListeners = array();
/**
* #param SharedEventManagerInterface $events
*/
public function attachShared(SharedEventManagerInterface $events)
{
$this->sharedListeners[] = $events->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, array($this, 'isAllowed'), 1000);
}
public function __construct(AuthenticationServiceInterface $authService, UserService $userService ){
$this->authService = $authService;
$this->userService = $userService;
}
/**
* #param MvcEvent $event
*/
protected function isAllowed(MvcEvent $event)
{
$authService = $this->getAuthService();
$identity = $authService->getIdentity();
$userService = $this->getUserService();
if($userService->isGrantedCustom()){
// User is granted we can return
return;
}
// Return not allowed response
}
/**
* #return AuthenticationServiceInterface
*/
public function getAuthService()
{
return $this->authService;
}
/**
* #param AuthenticationServiceInterface $authService
*/
public function setAuthService(AuthenticationServiceInterface $authService)
{
$this->authService = $authService;
}
/**
* #return UserService
*/
public function getUserService()
{
return $this->userService;
}
/**
* #param UserService $userService
*/
public function setUserService(AuthenticationServiceInterface $userService)
{
$this->userService = $userService;
}
}
You need to setup a factory to inject your dependencies:
<?php
namespace Application\Listener;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
/**
* Factory for creating the IsAllowedListener
*/
class IsAllowedListenerFactory implements FactoryInterface
{
/**
* Create the IsAllowedListener
*
* #param ServiceLocatorInterface $serviceLocator
* #return RenderLinksListener
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$userService = $serviceLocator->get('Application\Service\UserService');
return new IsAllowedListener($authService, $userService );
}
}
And register all this in config:
'service_manager' => array(
'factories' => array(
'Application\Listener\IsAllowedListener' => 'Application\Listener\IsAllowedListenerFactory'
)
)
And then in bootstrap:
public function onBootstrap(EventInterface $event)
{
$application = $event->getTarget();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
$isAllowedListener = $serviceManager->get('Application\Listener\IsAllowedListener')
$sharedEventManager->attachAggregate($isAllowedListener);
}
Instead of using AbstractActionController::class, you could also make a specific class, so you will only listen to instances of that class.
So for example AbstractIsAllowedActionController::class or something like that.
By attaching an event listener to the SharedEventManager you can target all controllers and have the authorization check in just one place.
In this case the target is Zend\Mvc\Controller\AbstractActionController which means any controller extending it will execute the listener. The high priority of this listener will mean that it is executed prior to the target controller action, giving you the chance to handle any requests that have not been authorized.
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach(
\Zend\Mvc\Controller\AbstractActionController::class, // Identity of the target controller
MvcEvent::EVENT_DISPATCH,
[$this, 'isAllowed'],
1000 // high priority
);
}
In each controller there would need to be some way that you can determine which 'resource' is being accessed.
As an example it could implement this interface
interface ResourceInterface
{
// Return a unique key representing the resource
public function getResourceId();
}
The listener could then look like this.
public function isAllowed(MvcEvent $event)
{
$serviceManager = $event->getApplication()->getServiceManager();
// We need the 'current' user identity
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$identity = $authService->getIdentity();
// The service that performs the authorization
$userService = $serviceManager->get('MyModule\Service\UserService');
// The target controller is itself a resource (the thing we want to access)
// in this example it returns an resource id so we know what we want to access
// but you could also get this 'id' from the request or config etc
$controller = $event->getTarget();
if ($controller instanceof ResourceInterface) {
$resourceName = $controller->getResourceId();
// Test the authorization, is UserX allowed resource ID Y
if (empty($resourceName) || $userService->isGrantedCustom($identity, $resourceName)) {
// early exit for success
return;
} else {
// Denied; perhaps trigger a new custom event or return a response
}
}
}

Symfony2 how to get entity manager in Listener

Im trying to insert data into database in onKernelRequest using doctrine
//** src/MPN/CRMBundle/Listener/AnalyticsListener.php
namespace MPN\CRMBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use MPN\CRMBundle\Manager\AnalyticsManager;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Response;
use Doctrine\ORM\EntityManager;
use vRonn\StatisticsBundle\Entity\Statistics as Statistics;
class AnalyticsListener
{
/**
* #var AnalyticsManager
*/
private $am;
private $em;
public function __construct(AnalyticsManager $am)
{
$this->am = $am;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (!$event->isMasterRequest()) {
return;
}
$request = $event->getRequest();
$session = $request->getSession();
$uri = $request->getRequestUri();
$route = $request->get('_route');
if (!preg_match('#^/(app_dev\.php/)?(admin|js|media|_[^/]+)/?.*#', $uri)) {
// add page view count
$this->am->addPageView();
//Here is how i try to insert data
$d = new Statistics();
$d->setType(1);
$d->setName('blabla');
$d->setValue('val');
$em = $this->am->em->getRepository('vRonnStatisticsBundle:Statistics');
$em->persist($d);
$em->flush();
if (!$session->has('visitor') || $session->get('visitor') < time()) {
$session->set('visitor', time() + (24 * 60 * 60));
$this->am->addVisitor();
}
$this->am->save();
}
}
but i get error below
Undefined method 'persist'. The method name must start with either
findBy or findOneBy!
Here is the manager which i take the entity manager from, i take entity from here because i tried both $this->get('doctrine') or $this->getDoctrine() inside onKernelRequest but error
//** src/MPN/CRMBundle/Manager/AnalyticsManager.php
namespace MPN\CRMBundle\Manager;
use Doctrine\ORM\EntityManager;
use MPN\CRMBundle\Entity\Analytics;
use MPN\CRMBundle\Service\DateTimeBuilder;
class AnalyticsManager
{
/**
* #var EntityManager
*/
public $em;
/**
* #var DateTimeBuilder
*/
private $dateTimeBuilder;
/**
* #var array
*/
private $analytics;
public function __construct(EntityManager $em, DateTimeBuilder $dateTimeBuilder)
{
$this->em = $em;
$this->dateTimeBuilder = $dateTimeBuilder;
$this->setup();
}
/**
* Flushes the data to the database.
*
* #return void
*/
public function save()
{
$this->em->flush();
}
}
It looks like you're setting $em to a repository, not an entity manager. If you just do $em = $this->am->em it oughta work.

Categories