followed by batch action documentation in sonata admin's website, created this custom batch action called AnalayseController.php:
<?php
namespace Admin\Store\Receipt\ReceiptBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as BaseController;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class AnalyseController extends BaseController
{
/**
* #param ProxyQueryInterface $selectedModelQuery
* #param Request $request
*
* #return RedirectResponse
*/
public function batchActionAnalyse(ProxyQueryInterface $selectedModelQuery, Request $request = null)
{
$request = $this->get('request');
$modelManager = $this->admin->getModelManager();
$target = $modelManager->find($this->admin->getClass(), $request->get('targetId'));
if ($target === null){
$this->addFlash('sonata_flash_info', 'No target!');
return new RedirectResponse(
$this->admin->generateUrl('list', $this->admin->getFilterParameters())
);
}
$selectedModels = $selectedModelQuery->execute();
$this->addFlash('sonata_flash_success', 'Done');
return new RedirectResponse(
$this->admin->generateUrl('list', $this->admin->getFilterParameters())
);
}
}
but I'm getting this error: Catchable Fatal Error: Argument 1 passed to Admin\Store\Receipt\ReceiptBundle\Controller\AnalyseController::batchActionAnalyse() must implement interface Sonata\AdminBundle\Datagrid\ProxyQueryInterface, string given, called in /home/aien/Web/Mr Alef/MRA_Dev/app/cache/dev/appDevDebugProjectContainer.php on line 852 and defined.
looked everywhere, but couldn't find any solution!
Ok, I've fixed the issue just by changing serives.yml calls first argument to setTranslationDomain
admin_store_receipt_receipt.admin.analyse:
class: Admin\Store\Receipt\ReceiptBundle\Admin\ReceiptAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: admin, label: Receipt }
arguments:
- ~
- Admin\Store\Receipt\ReceiptBundle\Entity\Receipt
- AdminStoreReceiptReceiptBundle:Analyse
calls:
- [ setTranslationDomain , [AdminStoreReceiptReceiptBundle]]
Related
I have a service in my project
services:
exemple.annotation_reader:
class: App\Annotation\ModelAnnotationReader
public: true
arguments: ["#annotations.reader"]
calls:
- [getAnnotation]
And I want call the methode "getAnnotation" without adding this line in my controller
$this->get('exemple.annotation_reader')->getAnnotation();
And there is my function getAnnotation
public function getAnnotation(){
$reflClass = new \ReflectionMethod('App\Manager\BigDataTestManager', 'getAllData');
$classAnnotations = $this->reader->getMethodAnnotations($reflClass);
foreach ($classAnnotations AS $annot) {
if ($annot instanceof ModelAnnotation) {
print_r($annot);
}
}
}
the goal of my project is to create a custom annotation in symfony 4, I want show the annotation's parameters
/**
* #Model(
* namespace="App\Model\Exemple",
* version=50,
* types={"json","xml"}
* )
*/
so I want create a service who call a function automatically when there is an annotation on a function.
I want to make a custom page twig in Sonata admin bundle (clone for example ) :
I use this tutorial :
http://symfony.com/doc/current/bundles/SonataAdminBundle/cookbook/recipe_custom_action.html
this is my controller CRUDController.php:
<?php
// src/AppBundle/Controller/CRUDController.php
namespace AppBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
class CRUDController extends Controller
{
// ...
/**
* #param $id
*/
public function cloneAction($id)
{
$object = $this->admin->getSubject();
if (!$object) {
throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
}
// Be careful, you may need to overload the __clone method of your object
// to set its id to null !
$clonedObject = clone $object;
$clonedObject->setName($object->getName().' (Clone)');
$this->admin->create($clonedObject);
$this->addFlash('sonata_flash_success', 'Cloned successfully');
return new RedirectResponse($this->admin->generateUrl('list'));
// if you have a filtered list and want to keep your filters after the redirect
// return new RedirectResponse($this->admin->generateUrl('list', $this->admin->getFilterParameters()));
}
}
but when i click in clone i show this error :
can you help me ..?
I feel like you forgot to configure your admin service for this page in the right way, please check http://symfony.com/doc/current/bundles/SonataAdminBundle/cookbook/recipe_custom_action.html#register-the-admin-as-a-service
cause sonata uses the SonataAdmin:CRUD controller by default and you should specify a custom one if you'd like to override the controller.
#src/AppBundle/Resources/config/admin.yml
services:
app.admin.car:
class: AppBundle\Admin\CarAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: Demo, label: Car }
arguments:
- null
- AppBundle\Entity\Car
- AppBundle:CRUD #put it here
You forget to configure Route for your controller.
Sonata Admin has to know about your new Action in order to generate for it route. For this purposes you have to configure configureRoutes method in you admin class:
class CarAdmin extends AbstractAdmin // For Symfony version > 3.1
{
// ...
/**
* #param RouteCollection $collection
*/
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('clone', $this->getRouterIdParameter().'/clone');
}
}
As you can see the name of the route matches the name of the action (but without action!) in your CRUDController.
You had name of the action : 'cloneAction' so the name of the route is "clone".
In my case i forgot to add baseControllerName in app/config/admin.yml, before it was '~'
Background:
I am trying to conditionally load routes based on the request host. I have a database setup that has hosts in it that map to templates. If a user comes in from the host A and that uses template TA I want to load the routes for that template. If they come in from host B then load the routes for that template (TB).
The reason I have to do this is because each template will share many routes. There are however some unique routes for a given template.
It would be fine to restrict each template routes to a given host, except that there are literally 1000's of hosts.
What I Have Tried:
I have tried a custom route loader as described in the documentation here:
http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html
However when i configure the service and try and inject the "#request" the constructor fails because $request is null
services:
acme_demo.routing_loader:
class: Acme\DemoBundle\Routing\ExtraLoader
arguments: ["#request"]
tags:
- { name: routing.loader }
Class:
<?php
namespace: Acme\DemoBundle\Routing;
use Symfony\Component\HttpFoundation\Request;
class ExtraLoader
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
// ...
}
This also doesnt work if I try and switch "#request" for "#service_container" then call
$this->container->get('request');
The closest I got to getting this working was following a guide found here:
http://marcjschmidt.de/blog/2013/11/30/symfony-custom-dynamic-router.html
The problem i have with this on is im trying to use annotation on a controller and i cant seem to get the Symfony\Component\Routing\Loader\AnnotationFileLoader working.
Ok I have finally figured out a working solution, Its a mix of several of the above mentioned guides.
Im using a kernel request listener:
services:
website_listener:
class: NameSpace\Bundle\EventListener\WebsiteListener
arguments:
- "#website_service"
- "#template_service"
- "#sensio_framework_extra.routing.loader.annot_dir"
- "#router"
- "%admin_domain%"
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 }
The Listener:
<?php
namespace NameSpace\WebsiteBundle\EventListener;
use NameSpace\TemplateBundle\Service\TemplateService;
use NameSpace\WebsiteBundle\Service\WebsiteService;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\PropertyAccess\PropertyAccess;
class WebsiteListener
{
/**
* #var WebsiteService
*/
protected $websiteService;
/**
* #var TemplateService
*/
protected $templateService;
/**
* #var AnnotationDirectoryLoader
*/
protected $loader;
/**
* #var Router
*/
protected $router;
/**
* #var string
*/
protected $adminDomain;
public function __construct(WebsiteService $websiteService, TemplateService $templateService, AnnotationDirectoryLoader $loader, Router $router, $adminDomain)
{
$this->websiteService = $websiteService;
$this->templateService = $templateService;
$this->loader = $loader;
$this->router = $router;
$this->adminDomain = $adminDomain;
}
public function loadRoutes()
{
$template = $this->templateService->getTemplateByAlias($this->websiteService->getWebsite()->getTemplate());
$routes = $this->loader->load($template['routes'],'annotation');
$allRoutes = $this->router->getRouteCollection();
$allRoutes->addCollection($routes);
}
public function onKernelRequest(GetResponseEvent $event)
{
try {
$this->websiteService->handleRequest($event->getRequest());
$this->loadRoutes();
} catch(NotFoundHttpException $e) {
if($event->getRequest()->getHost() !== $this->adminDomain){
throw $e;
}
}
}
}
The Key parts of this are:
The Loader - I found "#sensio_framework_extra.routing.loader.annot_dir" in the source code. That the annotation directory loader that symfony uses by default so thats the one that I want to use too. But if you want to use a different loader there are others available.
The Router - This is what i use to get all of the current routes. NOTE that the $allRoutes->addCollection($routes) call is on a seperate line. Im not sure why it makes a difference but calling it all in 1 like was not working.
$template['routes'] is just a namespaces controller reference like you would use to add routing in your routing.yml. Something like: "#NamespaceBundle/Controller"
I have writted an annotation who throw an AccesDeniedException when the action is not called by an AJAX request (XMLHttpRequest).
It work but when I want to use the #Secure(roles="A") annotation from JMS/SecurityExtraBundle it don't work like I omitted my custom exception.
Controller
namespace Mendrock\Bundle\SagaBundle\Controller;
use JMS\SecurityExtraBundle\Annotation\Secure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Mendrock\Bundle\SagaBundle\Entity\Saison;
use Mendrock\Bundle\SagaBundle\Form\SaisonType;
use Mendrock\Bundle\ExtraBundle\Annotation\XmlHttpRequest;
/**
* #Route("/episodesAjax")
*/
class EpisodeController extends Controller {
/**
* #XmlHttpRequest()
* #Secure(roles="ROLE_SUPER_ADMIN")
*
* #Route("/saisonAdd", options={"expose"=true})
* #Template()
*/
public function saisonAddAction() {
$entity = new Saison();
$form = $this->createForm(new SaisonType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
Annotation
namespace Mendrock\Bundle\ExtraBundle\Annotation;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* #Annotation
*/
class XmlHttpRequest
{
public $message = 'The action could be an XMLHttpRequest call.';
public function checkRequest($event){
if (!$event->getRequest()->isXmlHttpRequest()) {
throw new AccessDeniedHttpException($this->message);
}
}
public function execute($event){
$this->checkRequest($event);
}
}
Listener
namespace Mendrock\Bundle\ExtraBundle\Listener;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Mendrock\Bundle\ExtraBundle\Annotation\XmlHttpRequest;
class EventListener {
private $reader;
public function __construct(Reader $reader) {
$this->reader = $reader;
}
/**
* This event will fire during any controller call
*/
public function onKernelController(FilterControllerEvent $event) {
if (!is_array($controller = $event->getController())) {
return;
}
$method = new \ReflectionMethod($controller[0], $controller[1]);
foreach ($this->reader->getMethodAnnotations($method) as $configuration) {
if ($configuration instanceof XmlHttpRequest) {
$configuration->execute($event);
}
}
}
}
Any idea why I can't use at the same time #Secure(...) and #XMLHttpRequest?
Edit:
services.yml
services:
annotations.xmlhttprequest:
class: Mendrock\Bundle\ExtraBundle\Listener\EventListener
tags: [{name: kernel.event_listener, event: kernel.controller, method: onKernelController}]
arguments: [#annotation_reader]
I ran into the same problem when I wanted to add my own annotations. The solution using ClassUtils::getUserClass would not work (using Symfony 2.3, if that makes a difference).
Since we only use the #Secure annotation from JMS\SecurityExtraBundle, I made our codebase use LswSecureControllerBundle instead.
This bundle only provides #Secure, and does not do voodoo tricks with your controllers.
I am running into the same issue after upgrading to Symfony 2.1.
The issue, from my investigation, is that the JMS SecurityExtraBundle generates proxy classes whenever you use one of their annotations. The problem with the generated proxy classes is that custom annotations do not get proxied over, which is why the annotations appear to be missing.
The solution according to the author is to rewrite using AOP (facilities provided by JMSAopBundle) or to use ClassUtils::getUserClass.
Thanks to suihock for pointing this out:
$class = \CG\Core\ClassUtils::getUserClass($controller[0]);
$method = new \ReflectionMethod($class, $controller[1]);
I've been googling as crazy the last days trying to figure out (with no success) how override a SonataAdmin action to capture the session username and save it in the foreign key field.
AttachmentAdminController class:
<?php
namespace Application\Sonata\UserBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
#use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\UserBundle\Entity\User;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Bridge\Monolog\Logger;
use Mercury\CargoRecognitionBundle\Entity\Attachment;
class AttachmentAdminController extends Controller
{
/**
* (non-PHPdoc)
* #see Sonata\AdminBundle\Controller.CRUDController::createAction()
*/
public function createAction()
{
$result = parent::createAction();
if ($this->get('request')->getMethod() == 'POST')
{
$flash = $this->get('session')->getFlash('sonata_flash_success');
if (!empty($flash) && $flash == 'flash_create_success')
{
#$userManager = $this->container->get('fos_user.user_manager');
#$user = $this->container->get('context.user');
#$userManager = $session->get('username');
$user = $this->container->get('security.context')->getToken()->getUser()->getUsername();
$attachment = new Attachment();
$attachment->setPath('/tmp/image.jpg');
$attachment->setNotes('nothing interesting to say');
$attachment->getSystemUser($user);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($product);
$em->flush();
}
}
return $result;
}
}
service attachment:
mercury.cargo_recognition.admin.attachment:
class: Mercury\CargoRecognitionBundle\Admin\AttachmentAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: General, label: Attachments }
arguments: [ null, Mercury\CargoRecognitionBundle\Entity\Attachment, "SonataAdminBundle:CRUD" ]
Seems to me as the actionController() is been ignored by SonataAdminBundle (and maybe the whole class file), because there's not error messages at all, but I don't know why. Actually, I'm not sure if I'm fetching the username from the session.
I really need a good tutorial about this, but seems like any information I get about this is obsolete in some aspect. By the way, I'm using Symfony 2.0.16
Finally I got to the solution. I'm sure there are some others (like using event listeners, for example, that seems to be simpler), but right now it's the best I could find (it works, and that's what matters).
I was trying to override the createAction() based on examples that I found in another forum thread, but I was getting two records in the table instead of one only. The most important thing was overriding the WHOLE action method and put the custom code in it.
Controller:
<?php
namespace Mercury\CargoRecognitionBundle\Controller;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Bridge\Monolog\Logger;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Application\Sonata\UserBundle\Entity\User;
use Mercury\CargoRecognitionBundle\Entity\Attachment;
use Mercury\CargoRecognitionBundle\Entity\SystemUser;
use Mercury\CargoRecognitionBundle\Repository\SystemUserRepository;
class AttachmentAdminController extends Controller
{
/**
* Set the system user ID
*/
private function updateFields($object)
{
$userName = $this->container->get('security.context')
->getToken()
->getUser()
->getUsername();
$user = $this->getDoctrine()
->getRepository('ApplicationSonataUserBundle:User')
->findOneByUsername($userName);
$object->setSystemUser($user);
return $object;
}
/**
* (non-PHPdoc)
* #see Sonata\AdminBundle\Controller.CRUDController::createAction()
*/
public function createAction()
{
// the key used to lookup the template
$templateKey = 'edit';
if (false === $this->admin->isGranted('CREATE')) {
throw new AccessDeniedException();
}
$object = $this->admin->getNewInstance();
$object = $this->updateFields($object);
// custom method
$this->admin->setSubject($object);
$form = $this->admin->getForm();
$form->setData($object);
if ($this->get('request')->getMethod() == 'POST') {
$form->bindRequest($this->get('request'));
$isFormValid = $form->isValid();
// persist if the form was valid and if in preview mode the preview was approved
if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
$this->admin->create($object);
if ($this->isXmlHttpRequest()) {
return $this->renderJson(array(
'result' => 'ok',
'objectId' => $this->admin->getNormalizedIdentifier($object)
));
}
$this->get('session')->setFlash('sonata_flash_success','flash_create_success');
// redirect to edit mode
return $this->redirectTo($object);
}
// show an error message if the form failed validation
if (!$isFormValid) {
$this->get('session')->setFlash('sonata_flash_error', 'flash_create_error');
} elseif ($this->isPreviewRequested()) {
// pick the preview template if the form was valid and preview was requested
$templateKey = 'preview';
}
}
$view = $form->createView();
// set the theme for the current Admin Form
$this->get('twig')->getExtension('form')->setTheme($view, $this->admin->getFormTheme());
return $this->render($this->admin->getTemplate($templateKey), array(
'action' => 'create',
'form' => $view,
'object' => $object,
));
}
}
Service for the controller:
mercury.cargo_recognition.admin.attachment:
class: Mercury\CargoRecognitionBundle\Admin\AttachmentAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: General, label: Attachments }
arguments: [ null, Mercury\CargoRecognitionBundle\Entity\Attachment, "MercuryCargoRecognitionBundle:AttachmentAdmin" ]
I took the solution from the following sites:
Sonata-Users,
Symfony framework forums,
(And the Sonata Project documentation)
It might be useful to override only the preCreate hook with your own logic:
/**
* This method can be overloaded in your custom CRUD controller.
* It's called from createAction.
*
* #param mixed $object
*
* #return Response|null
*/
protected function preCreate(Request $request, $object)
{
}