How do I obtain the name of the current route in the controller in Symfony 5?
When I try this I get NULL:
$this->Request = Request::createFromGlobals();
$route = $this->Request->attributes->get('_route');
var_dump($route);
It's not recommended to create request inside your controller.
Preferred way of obtaining already created Request is DI and autowiring:
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class BlogController extends AbstractController
{
/**
* #Route("/blog", name="blog_list")
*/
public function list(Request $request)
{
$routeName = $request->attributes->get('_route');
$routeParameters = $request->attributes->get('_route_params');
var_dump($routeName);
}
}
This request is processed by Symfony HttpKernel and filled with additional information.
More info: https://symfony.com/doc/current/routing.html#getting-the-route-name-and-parameters
How did it get there: https://github.com/symfony/symfony/blob/b2609c4bae69ca383b97cb520da2ed9be1c48449/src/Symfony/Component/Routing/Matcher/UrlMatcher.php#L217
Found an answer here:
Just add RequestStack as a function parameter and call RequestStack->getCurrentRequest()->get('_route');
use Symfony\Component\HttpFoundation\RequestStack;
public function yourCalledFunction(Utilities $u, RequestStack $requestStack) {
$route = $requestStack->getCurrentRequest()->get('_route');
}
Related
I tried to call a function in a controller using service:
#BookManager.php
<?php
namespace App\Service;
use App\Entity\BookContent;
use Doctrine\ORM\EntityManagerInterface;
class BookManager
{
protected $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function getBookTitle(string $page){
return $this->entityManager->getRepository(BookContent::class)
>findOneBy(["page"=>$page])->getTitle();
}
In service.yml
....
services:
book.manager:
class: App\Service\BookManager
arguments: ['#doctrine.orm.entity_manager']
public: true
Finally I call it in Controller ;
$pageName = $this->container->get('request_stack')->getMasterRequest()->get('_route');
$bookTitle = $this->container->get('book.manager')->getBookTitle($pageName);
But I get this error
Service "book.manager" not found: even though it exists in the app's container, the container inside "App\Controller\HomeController" is a smaller service locator that only knows about the "doctrine", "form.factory", "http_kernel", "parameter_bag", "request_stack", "router", "security.authorization_checker", "security.csrf.token_manager", "security.token_storage", "serializer", "session" and "twig" services. Try using dependency injection instead.
Any idea?
EDIT
it's work when I use dependency injection but only when I do query with $id
$this->entityManager->getRepository(BookContent::class)-
>findOneById(["id"=>$id])->getTitle();
when I do it with findOneBy(["page"=>$page]) I get this error:
Impossible to access an attribute ("title") on a null variable.
By default Symfony 5 will autowire/auto configure your services. You can remove the book.manager from your service.yaml.
You can then use dependency injection in your controllers to access your services, like this for example:
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\BookManager; //<-- Add use
class YourController extends AbstractController
{
/**
* #Route("/", name="home")
*/
public function index(Request $request, BookManager $bookManager): Response
{
$pageName = $request->attributes->get('_route');
$bookTitle = $bookManager->getBookTitle($pageName);
return ...
}
}
As my IDE points out, the AbstractController::getDoctrine() method is now deprecated.
I haven't found any reference for this deprecation neither in the official documentation nor in the Github changelog.
What is the new alternative or workaround for this shortcut?
As mentioned here:
Instead of using those shortcuts, inject the related services in the constructor or the controller methods.
You need to use dependency injection.
For a given controller, simply inject ManagerRegistry on the controller's constructor.
use Doctrine\Persistence\ManagerRegistry;
class SomeController {
public function __construct(private ManagerRegistry $doctrine) {}
public function someAction(Request $request) {
// access Doctrine
$this->doctrine;
}
}
You can use EntityManagerInterface $entityManager:
public function delete(Request $request, Test $test, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$test->getId(), $request->request->get('_token'))) {
$entityManager->remove($test);
$entityManager->flush();
}
return $this->redirectToRoute('test_index', [], Response::HTTP_SEE_OTHER);
}
As per the answer of #yivi and as mentionned in the documentation, you can also follow the example below by injecting Doctrine\Persistence\ManagerRegistry directly in the method you want:
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
* #Route("/product", name="create_product")
*/
public function createProduct(ManagerRegistry $doctrine): Response
{
$entityManager = $doctrine->getManager();
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
}
Add code in controller, and not change logic the controller
<?php
//...
use Doctrine\Persistence\ManagerRegistry;
//...
class AlsoController extends AbstractController
{
public static function getSubscribedServices(): array
{
return array_merge(parent::getSubscribedServices(), [
'doctrine' => '?'.ManagerRegistry::class,
]);
}
protected function getDoctrine(): ManagerRegistry
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
...
}
read more https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
In my case, relying on constructor- or method-based autowiring is not flexible enough.
I have a trait used by a number of Controllers that define their own autowiring. The trait provides a method that fetches some numbers from the database. I didn't want to tightly couple the trait's functionality with the controller's autowiring setup.
I created yet another trait that I can include anywhere I need to get access to Doctrine. The bonus part? It's still a legit autowiring approach:
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Symfony\Contracts\Service\Attribute\Required;
trait EntityManagerTrait
{
protected readonly ManagerRegistry $managerRegistry;
#[Required]
public function setManagerRegistry(ManagerRegistry $managerRegistry): void
{
// #phpstan-ignore-next-line PHPStan complains that the readonly property is assigned outside of the constructor.
$this->managerRegistry = $managerRegistry;
}
protected function getDoctrine(?string $name = null, ?string $forClass = null): ObjectManager
{
if ($forClass) {
return $this->managerRegistry->getManagerForClass($forClass);
}
return $this->managerRegistry->getManager($name);
}
}
and then
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\Foobar;
class SomeController extends AbstractController
{
use EntityManagerTrait
public function someAction()
{
$result = $this->getDoctrine()->getRepository(Foobar::class)->doSomething();
// ...
}
}
If you have multiple managers like I do, you can use the getDoctrine() arguments to fetch the right one too.
I am adherent of Action Class approach using instead of Controller. The explanation is very simple: very often Controller includes many actions, when following the Dependency Injection principle we must pass all required dependencies to a constructor and this makes a situation when the Controller has a huge number of dependencies, but in the certain moment of time (e.g. request) we use only some dependencies. It's hard to maintain and test that spaghetti code.
To clarify, I've already used to work with that approach in Zend Framework 2, but there it's named Middleware. I've found something similar in API-Platform, where they also use Action class instead of Controller, but the problem is that I don't know how to cook it.
UPD:
How can I obtain the next Action Class and replace standard Controller and which configuration I should add in regular Symfony project?
<?php
declare(strict_types=1);
namespace App\Action\Product;
use App\Entity\Product;
use Doctrine\ORM\EntityManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class SoftDeleteAction
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #Route(
* name="app_product_delete",
* path="products/{id}/delete"
* )
*
* #Method("DELETE")
*
* #param Product $product
*
* #return Response
*/
public function __invoke(Request $request, $id): Response
{
$product = $this->entityManager->find(Product::class, $id);
$product->delete();
$this->entityManager->flush();
return new Response('', 204);
}
}
The question is a bit vague for stackoverflow though it's also a bit interesting. So here are some configure details.
Start with an out of the box S4 skeleton project:
symfony new --version=lts s4api
cd s4api
bin/console --version # 4.4.11
composer require orm-pack
Add the SoftDeleteAction
namespace App\Action\Product;
class SoftDeleteAction
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function __invoke(Request $request, int $id) : Response
{
return new Response('Product ' . $id);
}
}
And define the route:
# config/routes.yaml
app_product_delete:
path: /products/{id}/delete
controller: App\Action\Product\SoftDeleteAction
At this point the wiring is almost complete. If you go to the url you get:
The controller for URI "/products/42/delete" is not callable:
The reason is that services are private by default. Normally you would extend from AbstractController which takes care of making the service public but in this case the quickest approach is to just tag the action as a controller:
# config/services.yaml
App\Action\Product\SoftDeleteAction:
tags: ['controller.service_arguments']
At this point you should have a working wired up action.
There of course many variations and a few more details. You will want to restrict the route to POST or fake DELETE.
You might also consider adding an empty ControllerServiceArgumentsInterface and then using the services instanceof functionality to apply the controller tag so you no longer need to manually define your controller services.
But this should be enough to get you started.
The approach I was trying to implement is named as ADR pattern (Action-Domain-Responder) and Symfony has already supported this started from 3.3 version. You can refer to it as Invokable Controllers.
From official docs:
Controllers can also define a single action using the __invoke() method, which is a common practice when following the ADR pattern (Action-Domain-Responder):
// src/Controller/Hello.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/hello/{name}", name="hello")
*/
class Hello
{
public function __invoke($name = 'World')
{
return new Response(sprintf('Hello %s!', $name));
}
}
I know how to generate an URL for a route. However now I need to generate an URL for a controller or for a controller with a method. I checked the sourced of UrlGenerator but did not find any relevant information. No information in Symfony docs as well.
The method of the controller has an associate url. This url will be used in controller but I need the generator to be a service.
Basically you need to:
1. Add a route to the controller
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
final class BlogController extends AbstractController
{
/**
* #Route(path="blog", name="blog")
*/
public function __invoke(): Response
{
return $this->render('blog/blog.twig');
}
}
2. Generate url route for a route
See Symfony docs
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
/**
* #var UrlGeneratorInterface
*/
private $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function go()
{
// ...
// generate a URL with no route arguments
$signUpPage = $this->urlGenerator->generateUrl('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->urlGenerator->generateUrl('user_profile', [
'username' => $user->getUsername(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->urlGenerator->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->urlGenerator->generateUrl('sign_up', ['_locale' => 'nl']);
}
}
So, here is the service. At least an example of how it could implemented. SF4
namespace App\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
class UrlGenerator
{
/**
* #var RouteCollection
*/
private $collection;
public function __construct(RouterInterface $router)
{
$this->collection = $router->getRouteCollection();
}
public function generate(string $controllerClass, string $method): string
{
foreach ($this->collection as $item) {
$defaults = $item->getDefaults();
$controllerPath = $defaults['_controller'];
$parts = explode('::', $controllerPath);
if ($parts[0] !== $controllerClass) {
continue;
}
if ($parts[1] !== $method) {
continue;
}
return $item->getPath();
}
throw new \RuntimeException(
'Route for such combination of controller and method is absent'
);
}
}
Poorly tested but working solution.
I am struggling with the combination of a Controller, an EntityRepository, and my Doctrine Fixtures in Symfony2.
My ImageParts Entity are pieces of images. I want to randomly generate images, using random pieces.
So, I have an entity called ImagePart, and an EntityRepository with the name 'ImagePartRepository'.
Within that EntityRepository, I created a function called 'getRandomImagePart()', which is working fine when testing it using a route.
But I cannot figure out how I can use this function within my fixtures. I THINK I have to declare a service, but even then I cannot get it to work. The type of error messages I get tells me that I'm clearly doing something structurally wrong.
Furthermore, I am wondering if I use the Symfony2 framework the correct way, functional. Eg: Should I be able to use an EntityRepository within my fixtures.
Controller:
<?php
namespace AMM\AMMBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\Finder\Finder;
use AMM\AMMBundle\Entity\ImagePart;
use Doctrine\ORM\Mapping as ORM;
class ImagePartController extends Controller implements ContainerAwareInterface
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* {#inheritDoc}
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function getRandomImagePartAction($type)
{
/*
$sql = "
SELECT a.imagePartBase64 FROM imageparts a
WHERE a.imagePartCategory = '".$type."'
ORDER BY RAND()
LIMIT 1
";
*/
$em = $this->getDoctrine()
->getManager();
$imagePart = $em->createQueryBuilder()
->select('g')
->from('AMMBundle:ImagePart', 'i')
->addOrderBy('i.id', 'ASC')
->getQuery()
->getSingleResult();
// $conn = $this->get('database_connection');
//$randomimagepart = $conn->fetchAll($sql);
// return $randomimagepart[0]['imagePartBase64'];
return $imagePart;
}
// ..
public function generateRandomImage()
{
$object->getRandomImagePartAction('BACKGROUND');
}
Fixture file ImageFixtures.php
<?php
namespace AMM\AMMBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use AMM\AMMBundle\Entity\ImagePart;
class GenerateImages implements FixtureInterface, ContainerAwareInterface, OrderedFixtureInterface
{
private $container;
public function load(Objectmanager $manager)
{
$image= new Image();
$imageGenerator = $this->container
->get('imageGenerator')
->GenerateRandomImage();
$manager->persist($image);
$manager->flush();
}
My services.yml:
services:
imageGenerator:
class: AMM\AMMBundle\Controller\ImagePartController
So, basically what I do is call a function from a function in the same controller. This works fine when testing it, for example, in a view.
But when trying to load doctrine fixtures, the following error occurs:
Fatal error: Call to a member function has() on a non-object in C:\wamp\www\amm\vendor\symfony\symfony\src\Symfony\Bundle\FrameworkBundle\Controller\Controller.php on line 198
The end of the stack trace:
5.7077 41983128 15. AMM\AMMBundle\Controller\ImagePartController->getRandomImagePartAction() C:\wamp\www\amm\src\AMM\AMMBundle\Controller\ImagePartController.php:119
5.7077 41983128 16. Symfony\Bundle\FrameworkBundle\Controller\Controller->getDoctrine() C:\wamp\www\amm\src\AMM\AMMBundle\Controller\ImagePartController.php:44
Ok, I found the problem.
I had to add this to my services.yml:
calls: - [setContainer, [#service_container]] –