Use several times global parameter in controller symfony - symfony

I need your help in Symfony controller, there is a way to use a global parameter and get the different value in different method.
Actually I have this.
class ArticleController extends Controller
{
//Injection of white october bundle
/** #DI\Inject("white_october_breadcrumbs") */
private $wob;
public function indexAction(Request $request)
{
$this->wob->addRouteItem("Article", "article_index");
//Some stuff
}
public function addAction(Request $request, $id=0)
{
if($request->get('_route') === "article_add"){
$this->wob->addRouteItem("Add article", "article_add");
} else {
$this->wob->addRouteItem("Edit article", "article_edit");
}
//Some stuff
}
//Other functions..
}
As you can see, actually my breadcrumb only keep the last value of the global parameter $wob
Example :
Home > Article
Home > Edit article
But i want :
Home > Article > Edit article
Don't know if it's possible
Thank you in advance !

Your addAction is completely separated from indexAction and these are different requests, so you cannot expect to keep value of $wob property between requests.
In yours example you can make parent only inside the same action:
public function addAction(Request $request, $id=0)
{
$this->wob->addRouteItem("Article", "article_index");
if($request->get('_route') === "article_add"){
$this->wob->addRouteItem("Add article", "article_add");
} else {
$this->wob->addRouteItem("Edit article", "article_edit");
}
//Some stuff
}

Related

Symfony 4: I decorated UrlGeneratorInterface, but it's not used, it uses CompiledUrlGenerator instead

I decorated UrlGeneratorInterface
app.decorator.url_generator:
class: App\CoreBundle\Routing\Extension\UrlGenerator
decorates: Symfony\Component\Routing\Generator\UrlGeneratorInterface
arguments: ['#app.decorator.url_generator.inner']
but it's not used in cases where some bundle in example executes $this->generator->generate(), and I tracked what Symfony does through XDebug and CompiledUrlGenerator is used instead. I can see where this happens, namely in Symfony\Component\Routing\Router in getGenerator it specifically checks for CompiledUrlGenerator::class. But I don't want to override vanilla Symfony code. How am I supposed to override/decorate/extend which class in order for mine to be chosen always, as I have special parameters I need to add to the path. Thank you in advance!
I found it.
app.decorator.router:
class: App\CoreBundle\Routing\Extension\Router
decorates: 'router.default'
arguments: ['#app.decorator.router.inner']
Decorating this actually makes all packages use your Router. And as the UrlGenerator it has the generate function which can be extended.
EDIT: On request I provide the router class as well:
class Router implements RouterInterface {
protected $innerRouter;
public function __construct(RouterInterface $innerRouter) {
$this->innerRouter = $innerRouter;
}
public function setContext(RequestContext $context)
{
$this->innerRouter->setContext($context);
}
public function getContext()
{
return $this->innerRouter->getContext();
}
public function getRouteCollection()
{
return $this->innerRouter->getRouteCollection();
}
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
//add here to $parameters...
return $this->innerRouter->generate($name, $parameters, $referenceType);
}
public function match($pathinfo)
{
$parameters = $this->innerRouter->match($pathinfo);
//add here to $parameters...
return $parameters;
}
}

Prevent the doctrine postLoad event for some cases

I have an entity BlogPost with a status property. This status property depends on an external API call which is handled via the doctrine postLoad event. All other properties are stored in the local database.
public function postLoad(BlogPost $post)
{
$this->postHandler->calculateStatus($post);
}
The problem is, in some cases i don't want to calculate the status at all. For example if i want to get only the description of all blogposts.
With the code above, all blog entities being loaded will trigger the postLoad event even if i just want to have values from a local database. That is very expensive and not acceptable.
So for example in my repository class i want to get all BlogPosts having a website without invoking the postLoad event.
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getResult();
}
Is there a way to say "Yes, load the BlogPost collection, but do not fire event!" ???
Any other approaches? Custom event?
Thanks
Why don't just move this logic outside the post entity and event listener? If you know when you need to calculate the status you can do it explicitly.
For example
$post = $this->entityManager->find(BlogPost::class, $postId);
$status = $this->postHandler->calculateStatus($post);
The other approach I could suggest is not good but works. You could use lazy calculation and instead of calling $this->postHandler->calculateStatus($this) in postLoad event listener you could inject postHandler service into entity and perform the calculation in the moment you actually need it.
For example if you need calculation when calling $blogPost->getStatus() method, you could do it this way:
interface PostHandlerAwareInterface
{
public function setPostHandler(PostHandlerInterface $postHandler): void;
}
class EntityServiceInjectorEventSubscriber implements EventSubscriber
{
/** #var PostHandlerInterface */
private $postHandler;
public function postLoad($entity): void
{
$this->injectServices($entity);
}
public function postPersist($entity): void
{
$this->injectServices($entity);
}
private function injectServices($entity): void
{
if ($entity instanceof PostHandlerAwareInterface) {
$entity->setPostHandler($this->postHandler);
}
}
}
class BlogPost extends PostHandlerAwareInterface
{
/** #var PostHandlerInterface */
private $postHandler;
private $status;
public function setPostHandler(PostHandlerInterface $postHandler): void
{
$this->postHandler = $postHandler;
}
public function getStatus()
{
if (null === $this->status) {
$this->postHandler->calculateStatus($this);
}
return $this->status;
}
}
If you don't like this idea you still could manage it via (BUT I STRONGLY DO NOT RECOMMEND DO THIS DIRTY HACK) setting the flag to your entity event listener.
You could inject your entity event listener to the code and set flag before fetching data:
class BlogPostCalculateStatusListener
{
/** #var bool */
private $calculationEnabled = true;
public function suspendCalculation(): void
{
$this->calculationEnabled = false;
}
public function resumeCalculation(): void
{
$this->calculationEnabled = true;
}
public function postLoad(BlogPost $post): void
{
if ($this->calculationEnabled) {
$this->postHandler->calculateStatus($post);
}
}
}
$this->calculateStatusListener->suspendCalculation();
$blogPosts = $blogPostRepository->findBlogPosts();
$this->calculateStatusListener->resumeCalculation();
Hope this helps.
PS. If you want to get only the descriptions of all blog posts you can do this way:
class BlogPostRepository
{
public function findBlogPosts()
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('bp.description')
->from('AppBundle:BlogPosts', 'bp')
->innerJoin('bp.website', 'w');
return $qb->getQuery()->getArrayResult();
}
}
getArrayResult does not invoke lifecycle callbacks.
Since i haven't found a real similar use case on the internet, i'll go for the following solution which seems the easiest and most acceptable cleanest to me. Maybe someone else could find this useful.
Implement a TransientLoadable Interface
interface TransientLoadable
{
public function isLoaded() : bool;
public function setLoaded(bool $loaded) : TransientLoadable;
public function setTransientLoadingFunction(\Closure $loadingFunction) :
TransientLoadable;
}
Implement the entity
class BlogPost implements TransientLoadable
{
...
}
Setup Loading function on postLoad Event
public function postLoad(BlogPost $post)
{
$func = function() use ($postHandler, $post)
{
//Since there may be another fields being loaded from the same API, catch them also since data is anyway in the same request
$postHandler->setAllDataFromAPI($post)
//Set the loading state to true to prevent calling the API again for the next property which may also be transient
$post->setLoaded(true);
}
$post->setTransientLoadingFunction($func)
}
Use the built-in lazy loading mechanism to get the property from the API only when it's needed
class BlogPost implements TransientLoadable
{
private function getStatus() : int
{
if (!$this->isLoaded) {
call_user_function($this->loadingFunction)
}
return $this->status;
}
private function getVisitorCount() : int
{
if (!$this->isLoaded) {
call_user_function($this->loadingFunction)
}
return $this->visitorCount;
}
}
So what's happening? Let's imagine we want to get the status and the visitor count, both are loaded via a single external API call.
If some api-dependent property of the entity is needed, all other properties gets loaded too (since we don't want to have for each property another call). This in ensured through the loaded function of the TransientLoadable interface. All data gets loaded by the setAllDataFromAPI function which is injected as a closure function.
I think that is not the cleanest solution. The loading stuf should be done by an extra layer on top of the entity class. Since sonata admin does not deal with such an layer, i think that this solution is cleaner than writing the loading mechanism directly to the entity class.
I am open to another suggestions or feedback
Thanks

Symfony: internal forwarding vs render controller

I have simple action in simple controller:
public function _targetAction(RequestStack $requestStack)
{
$request = $requestStack->getMasterRequest();
// ...
}
And two ways to call it. First:
// The same or other controller
public function topAction(Request $request)
{
// forward to SimpleController:_target
return $this->forward('AppBundle:Simple:_target');
}
Second from twig (subrequest):
// SimpleController
public function topAction(Request $request)
{
// render
return $this->render('AppBundle:Simple:top.html.twig');
}
// top.html.twig
{{ render(controller('AppBundle:Simple:_target')) }}
How can i idenitfy which way i get to the SimpleController::_targetAction in this method:
public function _targetAction(RequestStack $requestStack)
{
// what can i do here to uniquely identify current way
// Note: $requestStack->getParentRequest() is not null in both cases
}
In my opinion, if you need to execute different code depending on the call type, you should considere create separate routes for each action.
In case you really want to use the same, my best shot is to add a parameter on the route to identify the request.
/**
*
* #Route("/target/{from}", name="_target")
*/
public function _targetAction($from)
{
if($from == 'view'){
// execute code for view call
} else {
// execute code for controller call
}
}
And then, when you call it, pass a different parameter depending on caller type:
TWIG
{{ render(controller('AppBundle:Simple:_target', { 'from': 'view' })) }}
CONTROLLER
return $this->forward('_target', array('from' => 'controller'));

Symfony2 : Doctrine : PHPUnit : Set entity Id during flushing with mocked entity manager in unit tests

Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
I want to test a method of a class that makes use of the doctrine entity manager. This public method calls a private one that instantiates a Bookmark entity, flushes it and returns this entity. Then later, in the tested method I need to access the entity Id. Everything is mocked excepted the Bookmark entity itself. The main problem is that there is no setId() method in my entity. Here is the code and my main idea to solve this issue but I don't know if it is correct ?
Tested class and method
class BookmarkManager
{
//...
public function __construct(TokenStorageInterface $tokenStorage, ObjectManager $em, Session $session)
{
//...
}
public function manage($bookmarkAction, $bookmarkId, $bookmarkEntity, $bookmarkEntityId)
{
//...
$bookmark = $this->add($bookmarkEntity, $bookmarkEntityId);
//...
$bookmarkId = $bookmark->getId();
//...
}
private function add($entity, $entityId)
{
//...
$bookmark = new Bookmark();
//...
$this->em->persist($bookmark);
$this->em->flush();
return $bookmark;
}
}
Test
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
//...
// THIS WON'T WORK AS NO setId() METHOD EXISTS
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$bookmark->setId(1);
}
}));
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManagerMock, $sessionMock);
//...
}
}
Solutions ?
1- Make usage of reflection class as proposed here :
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$class = new \ReflectionClass($bookmark);
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($bookmark, 1);
//$bookmark->setId(1);
}
}));
2- Create a test Boookmark entity that extends from the real one and add a setId() method. Then create a mock of this class and replace and customize the one got from the ReturnCallback method with this one ? It seems crappy...
Any thoughts ? Thanks for your help.
The reflection looks interesting but it decreases readability of tests (mixing with mocks makes the situation tough).
I would create a fake for entity manager and implements there setting id based on reflection:
class MyEntityManager implements ObjectManager
{
private $primaryIdForPersitingObject;
public function __construct($primaryIdForPersitingObject)
{
$this->primaryIdForPersitingObject = $primaryIdForPersitingObject;
}
...
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
}
public function flush() { }
...
}
Once you implemented this, you can inject the instance of MyEntityManager and make your tests small and easier to maintain.
You test would look like
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
Of course, a situation may be harder if there is a need of setting different ids for many persisting objects. Then you can, for example, increase $primaryIdForPersitingObject on persist call
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}
It may be extended even further to have separate primaryIdForPersitingObject each entity class, and your tests will be still clean.

Symfony2 common session strategy

I have a symfony2 web project consisting of ten pages which are rendered via 5 controllers. User can land a site through any of the pages above (via shared link for example). And I need to show a welcome popup (just div with position:absolute) to users who opens the page for the first time during the current session.
I've already placed my popup in the common twig template which is used by all the pages needed. Now I have to determine whether to show popup or not. I'm going to show popup based on boolean value from controller.
I have to work with session and cookies, but I have to do that on each page and I don't want to write the same code (check and set cookies, output a boolean to show popup in template) in every method of each controller. Is there a way to this according to DRY concepts?
You could make a wrapper class which handles checking, setting, and getting the current session values and make it a service.
<?php
namespace My\Bundle\My\Namespace;
use Symfony\Component\HttpFoundation\Session\Session;
class SessionManager /* or whatever you want to call it */
{
public function __construct(Session $session)
{
$this->session = $session;
}
public function getMyValue()
{
return $this->session->get('my.value.key',null);
}
public function setMyValue($value)
{
$this->session->set('my.value.key',$value);
return $this;
}
public function hasMyValue()
{
return $this->session->has('my.value.key');
}
}
And in your bundle services.yml
<service id="mytag.session_manager" class="My\Bundle\My\Namespace\SesionManager">
<argument type="service" id="session" />
</service>
And in your controllers
public function someAction()
{
$sessionManager = $this->get('mytag.session_manager');
if($sessionManager->hasMyValue())
{
// do something
}
}
Thanks to the Sgoettschkes answer here Where to place common business logic for all pages in symfony2
I tried this method
http://symfony.com/doc/current/book/templating.html#embedding-controllers
And it looks awesome:
My Popup is included in the main template like this
{{ render(controller('MalyutkaMainBundle:WelcomePopup:index')) }}
Than inside the controller I manipulate session vars
class WelcomePopupController extends Controller {
public function indexAction(Request $request) {
$session = $this->get('session');
$showWelcomePopup = 0;
if ($session->has("have_seen_welcome_popup_on")) {
// tbd compare to the date of publishing of the new popup screen
} else {
$showWelcomePopup = 1;
$session->set("have_seen_welcome_popup_on", new \DateTime());
}
$params = array (
'show_welcome_popup' => $showWelcomePopup
);
return $this->render('MalyutkaMainBundle:WelcomePopup:welcome_popup.html.twig', $params);
}
}
And nothing is to be added in other controllers - just what I wanted to do.
But it is impossible to change cookies that way, so I store my data in the session.

Resources