Sonata Admin Class : add KnpMenu links pointing Admin class with custom route - symfony

Using SonataAdminBundle with Symfony2, I'm looking for a solution to access some Admin classes with a specific route.
For example, I have a ContractAdmin class with boolean fields such as "Enabled".
What I would like is to add in the left KnpMenu of sonata admin, some links pointing to the same Admin class but with a custom route (other than the default "list" route), for example:
Contracts
All Contracts
Contracts enabled (Listing only enabled contract)
Contracts not yet enabled (Listing only not enabled contract)
This would avoid me to use filters.
So, how could I create and put these links to the menu which target the corresponding admin class controller with a custom route?
Thank you ;)

I've solved it declaring a custom CRUDController for this admin class and adding the actions needed calling listAction method :
class ContractAdminController extends Controller {
public function contractsEnabledAction() {
return $this->listAction();
}
I've declared this custom route into the Admin class :
protected function configureRoutes(RouteCollection $collection) {
parent::configureRoutes($collection);
$collection->add('contracts_enabled', 'contractsEnabled/');
}
Then, overriding the createQuery method in the admin class, I'm using the request "_route" attribute like that :
public function createQuery($context = 'list') {
$query = parent::createQuery($context);
switch ($this->getRequest()->get("_route")) {
case "admin_acme_contract_contracts_enabled" :
$query->andWhere(
$query->expr()->eq($query->getRootAliases()[0] . '.enabled', ':param')
);
$query->setParameter('param', true);
break;
}
return $query;
}

Related

Get data in twig function

Is it bad practice to get data from db in twig function or I should pass it to view in controller?
My function is some kind of interface widget that is used on all pages of site admin section. Then on data change I will have to make changes in all actions. But when I get data directly in extension class our teamlead tells that it's bad MVC.
It would be best if you pass it to a view from a controller.
Your team leader is right. What you can do is create an action specific to render that widget. I.e create a custom widget, let's say you want to show the number of current active users:
class WidgetController extends Controller
{
public function usersCountWidgetAction()
{
return $this->render('widget/usersCount.html.twig', array(
"usersCount" => $this->getUsersCount();
));
}
public function getUsersCount()
{
// call the manager and get the result
}
}
Now in all your other twigs you can use
{{ render(controller('AppBundle:Widget:usersCountWidget')) }}

Share a method with all controllers : Best practice

I'm developing a notification system in symfony2 and I need to get the notifications for every page I'm running.
the trivial solution is to copy the content of the function in every controller and call the function from $this.
How can I make the notification function accessible for every controller? I heard that setting a controller as service is bad practice. what's the best practice then ?
If just using it for output in the template then best approach would be to use a custom TwigFunction and then calling that in a base/layout/extended template like so..
TwigExtension
namespace Acme\NotificationBundle\Twig;
use Acme\NotificationBundle\Provider\NotificationProviderInterface;
class AcmeNotificationExtension extends \Twig_Extension
{
protected $container;
protected $notificationProvider;
public function __construct(
ContainerInterface $container,
NotificationProviderInterface $notificationProvider
)
{
$this->notificationProvider = $notificationProvider;
}
public function getFunctions()
{
return array(
new \Twig_SimpleFunction(
'acme_render_notifications',
array($this, 'renderNotifications')
),
);
}
public function renderNotification($template = 'default:template.html.twig')
{
$notifications = $this->notificationsProvider->getCurrentNotifications();
// Or whatever method provides your notifications
return $this->container->get('templating')->render(
$template,
array('notifications' => $notifications)
);
}
public function getName()
{
return 'acme_notification_extension';
}
}
Services
parameters:
acme.twig.notification_extension.class:
Acme\NotificationBundle\Twig\AcmeNotificationExtension
services:
acme.twig.notification_extension:
class: %acme.twig.notification_extension.class%
arguments:
- #service_container
- #acme.provider.notifcation
// Or what ever your notification provider service is named
tags:
- { name: twig.extension }
This way you would be able to call your notifications in any template using acme_render_notifications() (with the default template) or acme_render_notifications('AcmeOtherBundle:Notifications:in_depth.html.twig') (with a different template if needed) and your controller aren't even touched.
If it was put in a parent template in a block like ..
{% block notifications %}
{{ acme_render_notifications() }}
{% endblock notifications %}
..then it would run on every page unless you had overridden the block in your child class.
The way I would do it, and I think it is among the best practices, is setting up a service with the function then just instantiate it in each controller.
No doubt its bad practice,
Many solutions are possible, Here we will discuss on abstract level
A global utility can be used with different scopes (application, session scope) depending upon the requirements
Make this utility accessible to all available controllers

How can I override the layout of the form at /admin/sonata/user/user/{id}/edit

I have extended the Sonata UserAdmin by creating Application\Sonata\UserBundle\Admin\Model\UserAdmin and extending Admin, then commenting out some fields I would rather not display.
From sonata_user in config.yml:
admin: # Admin Classes
user:
class: Application\Sonata\UserBundle\Admin\Entity\UserAdmin
controller: SonataAdminBundle:CRUD
translation: SonataUserBundle
Where is the template for the form which gets displayed at /admin/sonata/user/user/{id}/editand what are the steps required to override it?
The templates for your forms are in vendor/Sonata/...Resources/views
There are two ways to override these templates. The easiest is to override an individual template by creating it at app/Resources/PATH/view.html.twig.
PATH => the path to access the view you override in vendor, you have to recreate it. I said view.html.twig, but it can be another name, just need to be the same.
So the same way you did with the UserAdmin entity, but in the resources.
The other way is in the case you did your own bundle, that will be the son of one of your vendors bundle.
To get more information, FOSUserBundle documentation is great about how to override things from a parent bundle.
Check this : https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/overriding_templates.md
There is also doc on how to override form & controllers.
Good luck !
Override getTemplate method in UserAdmin class:
public function getTemplate($name)
{
switch ($name) {
case 'edit':
return 'Application\Sonata:User:edit.html.twig';
break;
default:
return parent::getTemplate($name);
break;
}
}
and create Application\Sonata\Resources\views\User\edit.html.twig that will override Sonata's template:
{# edit.html.twig #}
{% extends 'SonataAdminBundle:CRUD:edit.html.twig' %}
And now you can override the blocks from SonataAdminBundle:CRUD:edit.html.twig as you want.

Global values: Define as service or define abstract Controller class?

In my Symfony2 app, I want to globally fetch a value from my database on each template and don't want to call on each Controller. I know I could define that as a service and inject that service into my twig templates (by defining it as a twig global).
Is that the common and recommended way? Or should I rather create an abstract Controller class where I fetch that value in my constructor and then inherit from all my other Controllers?
Note: It is actually not a static value that is the same for all users, but it is a user specific value which is different for each user.
If this variables are used to render the same spot on your page you can render an embedded controller. Like this:
<div id="sidebar">
{{ render(controller('YourBundle:User:stats')) }}
</div>
This will inject whole output of YourBundle/UserController/statsAction to the #sidebar div. Inside this action you can extract all inforamtion that you need.
If you need to use this variables in other way maybe you should look at response event.
Are you familiar with event listeners? http://symfony.com/doc/current/cookbook/service_container/event_listener.html
An event listener can be used to inject twig globals.
class ModelEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => array(
array('doProject', -1300),
),
KernelEvents::VIEW => array(
array('doView', -2100),
),
);
}
public function doProject(FilterControllerEvent $event)
{
$project = $whatever_is_needed_to_find_the_project();
if (!$project) throw new NotFoundHttpException('Project not found ' . $projectSearch);
// Add to request
$event->getRequest()->attributes->set('project',$project);
// Give all twig templates access to project
$twig = $this->container->get('twig');
$twig->addGlobal('project',$project);
}
# services.yml
cerad_core__model__event_listener:
class: '%cerad_core__model__event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
If it's a user value like you said you can get app.user.XXX on every twig template you need without processing nothing ;)

disable action in sonata admin bundle CRUD

IS there a simple way to disable some CRUD actions for given admin class? E.g. I just want a list of users added via front-end without the option to manually add them.
In your admin class :
protected function configureRoutes(RouteCollection $collection)
{
// to remove a single route
$collection->remove('delete');
// OR remove all route except named ones
$collection->clearExcept(array('list', 'show'));
}
Also use routeCollection at top of admin class
use Sonata\AdminBundle\Route\RouteCollection;
Docs : http://sonata-project.org/bundles/admin/master/doc/reference/routing.html#removing-a-single-route

Resources