This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Custom symfony routing based on doctrine
I have several url in my project related to each object type. Ex.
For products:
/product/product-1
/product/product-2
But now I created a controller for general info management. Ex. terms of use, about us ..etc.
I want urls that only contains the page slug.
/terms-use
/about-us
Not:
/page/terms-use
/page/about-us
How to define this in routing.yml?
Venu is right, Custom symfony routing based on doctrine provides a way to do this, the only complication comes if your slug parameter is built by adding slugs of parent pages such that the grandchild page has a slug like grandparent-slug/parent-slug/child-slug in which case an exception would be thrown.
We used routing to match slugs like the example I gave and it required some regex in the annotations:
/**
* #Route("{slug}", name="page_index", defaults={"slug" = false}, requirements={"slug" = "[0-9a-zA-Z\/\-]*"})
* #Template()
*/
public function indexAction($slug)
{
if ($slug !== false) {
$page = $this->findPage($slug);
The above requirements annotation means that the slug can be alphanumeric with forward slashes and hyphens. You then need to implement a method findPage($slug) that explodes the slug on forward slashes and finds the correct child page.
Because the route matches so many other routes, it is important to include this controller last in the routing.yml file and this action last of the public actions in the controller so that all other routes are matched first.
EDIT
This is the findPage method that we wrote to find a page from the above slug:
protected function findPage($slug_string, $first_page = false)
{
$slug_array = explode("/", $slug_string);
$slug = array_shift($slug_array);
$page = $this->em->getRepository("PagesBundle:Page")->getPageBySlug($slug, $this->site_id);
if (!$page) {
return false;
}
// if only the first matched page is required return it
if ($first_page) {
return $page;
}
// Otherwise loop through the slug array and match recursive
//children until slug array is empty or no match found
while (!empty($slug_array)) {
if ($page->getChildren()) {
$slug = array_shift($slug_array);
foreach ($page->getChildren() as $child_page) {
if ($child_page->getSlug() == $slug) {
$page = $child_page;
break;
}
}
} else {
return false;
}
}
return $page;
}
Related
I have a scenario where I need to perform some redirection of a not found url
http://localhost/drupal9/node/1/search
the word search is added though a plugin I am using and it is a front-end route not a backend so upon refreshing this url I get Not Found which totally makes sense what I need to do is remove the word search from the URL and redirect to,
http://localhost/drupal9/node/1/
as search is a common word and can be used in other content type I first need to check whether the URL is of my custom content type. let me show you a piece of implementation I already have.
function [module]_preprocess_page(&$variables) {
$query = \Drupal::entityQuery('node')
->condition('type', [module]);
$nids = $query->execute();
if(array_search(2,$nids)){
echo "yes";
}
}
so over here what I am doing is grabbing all the nodes with my content type and grabbing the Nid from URI and matching them and this does work but there is another problem with this.
In the page properties we have an option of ALias so if the user uses a custom alias, then I dont get the Nid in the URI anymore so this logic breaks,
the question may seem a bit tricky but the requirement is simple.I am looking for a unified solution to parse the URL into some drupal API and simply getting back the content type name.The Url may contain a custom alias or a Nid
You can create an EventSubscriber subscribing the event kernel.request to handle the case of URL <node URL>/search.
For detailed steps to create an EventSubscriber, you can see here.
And below is what you need to put in your EventSubscriber class:
RequestSubscriber.php
<?php
namespace Drupal\test\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class RequestSubscriber.
*/
class RequestSubscriber implements EventSubscriberInterface {
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents() {
return [
KernelEvents::REQUEST => 'onKernelRequest',
];
}
public function onKernelRequest($event) {
$uri = $event->getRequest()->getRequestUri(); // get URI
if (preg_match('/(.*)\/search$/', $uri, $matches)) { // check if URI has form '<something>/search'
$alias = $matches[1];
$path = \Drupal::service('path_alias.manager')->getPathByAlias($alias); // try to get URL from alias '<something>'
if (preg_match('/node\/(\d+)/', $path, $matches)) { // if it is a node URL
$node = \Drupal\node\Entity\Node::load($matches[1]);
$content_type = $node->getType();
//... some logic you need
}
}
}
}
On StackOverflow, for a question you will see in the url something like this:
https://stackoverflow.com/questions/{question.id}/{question.slug}
If you remove the slug or change it, you will be redirected to a URL which contains the right slug, all done because only the question.id needs a match. I am trying to achive the same thing in a Symfony project. I've gotten far enough that the slug that is being entered (or left out) does not have any effect on the route. The only thing I have not achieved is the user being sent (or show) the full correct slug.
This is my code:
/**
* #Route("/{id}/{slug}", name="entity_show", defaults={"slug" = null})
*/
public function showEntity(Request $request, $id, EntityRepository $entityRepository)
{
//.....rest of code here
So in short, what I have archieved is that any slug can be entered or no slug at all, but a redirect to the slug belonging to the {id} is not yet shown.
Initially you can get the Question object by using the ParamConverter and in case the slug is wrong redirect to the proper route:
/**
* #Route("/{id}/{slug}", name="entity_show", defaults={"slug" = null})
*/
public function showEntity(Request $request, Question $question, $slug, EntityRepository $entityRepository)
{
if ($question->getSlug() !== $slug) {
return $this->redirectToRoute('entity_show', ['id' => $question->getId(), 'slug' => $question->getSlug()]);
}
....
do whatever you need
I discovering symfony3, but im stuck at getting the parameters passed with a link from an action.
In my twig file, im redirecting the user to an action:
<td>go to</td>
And im my action, i'm trying to get the id with:
/**
* #Route("/AfficheDetail", name="esprit_park_affiche")
*/
public function afficheAction()
{
$id = $this->getParameter("id");
return $this->render("#EspritPark/Voiture/affiche.html.twig", array("id" => $id));
}
but each time i get: The parameter "id" must be defined.
like the getParameter isnt returning anything.
I even tried with:
$id = $this->get("request")->get("id");
but i get: You have requested a non-existent service "request". Did you mean one of these: "monolog.logger.request", "request_stack", "router.request_context", "data_collector.request"?
The getParameter() method from the base Controller class is looking up parameters from the service container.
I would make the parameters part of your route. You can then retrieve the values through the action method's parameters:
/**
* #Route("/AfficheDetail/{id}/{serie}/{dateMise}/{marque}", name="esprit_park_affiche")
*/
public function afficheAction($id, $serie, $dateMise, $marque)
{
// ...
}
If you do not add them to the route pattern, they will be accessible through the URL parameters (the current request will be injected automatically if you type hint an argument with the Request class):
public function afficheAction(Request $request)
{
$id = $request->query->get('id');
$serie = $request->query->get('serie');
$dateMise = $request->query->get('dateMise');
$marque = $request->query->get('marque');
// ...
}
I have a Sonata admin for an entity with many elements that naturally spans multiple pages in the list view.
What I'd like to do is make it so after editing, or creating a new entity, redirect the user to the appropriate page which displays the edited entity (as opposed to going back to the list's first page which is the default behavior of the sonata admin). The dafult behavior is ok when there are only 1 or 2 pages but when you have tens or even hundreds of pages, navigating back to the correct page becomes quite tedious.
So my question is what is the appropriate way to make this happen?
I'm thinking that it would involve customizing the admin controller for the entity but I'm not sure what the right extension points are. And also, how to utilize the paginator to obtain the correct page to navigate back to.
Another potential hack would be to capture the query parameters state when navigating from the list view to the edit, and then returning the user to the same URL. This won't work correctly for creating new items.
There's also the matter of the state of filters when navigating from the list view (if the user had sorted and/or filtered the list before navigating to the edit page).
I know I'm late but this can be useful for someone else...
Here is the way I've made it, by overriding AdminBundle CRUDController:
<?php
namespace MyProject\AdminBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as BaseController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
class CRUDController extends BaseController
{
protected function redirectTo($object, Request $request = null)
{
$response = parent::redirectTo($object, $request);
if (null !== $request->get('btn_update_and_list') || null !== $request->get('btn_create_and_list')) {
$url = $this->admin->generateUrl('list');
$last_list = $this->get('session')->get('last_list');
if(strstr($last_list['uri'], $url) && !empty($last_list['filters'])) {
$response = new RedirectResponse($this->admin->generateUrl(
'list',
array('filter' => $last_list['filters'])
));
}
}
return $response;
}
public function listAction(Request $request = null)
{
$uri_parts = explode('?', $request->getUri(), 2);
$filters = $this->admin->getFilterParameters();
$this->get('session')->set('last_list', array('uri' => $uri_parts[0], 'filters' => $filters));
$response = parent::listAction($request);
return $response;
}
}
I am having the same problem, I was thinking of passing a variable in the route to the edit page, thus giving you where the request for the edit originated from, then you could redirect to the originating page given the variable.
I'd like the following urls to serve the appropriate actions:
/ - indexAction
/fr - indexAction
/foo - detailsAction (slug = foo)
/fr/foo - detailsAction (slug = foo)
I have added the following action methods:
/**
* #Route("/{_locale}", name="home", defaults={"_locale": ""}, requirements={"_locale": "fr|es"})
*/
public function indexAction() {
...
}
/**
* #Route("/{_locale}/{slug}", name="details", defaults={"_locale": ""}, requirements={"_locale": "fr|es"})
*/
public function detailsAction($slug) {
...
}
This works fine if I go to /, /fr and /fr/foo. However when I go to /foo it doesn't find a matching route. I'd appreciate it if someone could show me how to this.
Please note that ideally i'd like to achieve this without having to add multiple #Route annotations for a particular action method. That way I can use the UrlGenerator and point to the same name to produce the localized and none localized route whether I pass the _locale parameter or not.
I have managed to get this to work although my solution is slightly hacky. First I removed {_locale} part of the path, the defaults and requirements from my routes above.
Then when I created my routes I said:
$routes = new RouteCollection();
// Load the routes
...
$routes->addPrefix('/{_locale}', ['_locale' => ''], ['_locale' => '|fr|es']);
This automatically adds the localization bits (removed above) to the routes so it can easily be configured in one place. I changed RouteCollection to my own type with the following:
use Symfony\Component\Routing\RouteCollection as BaseRouteCollection;
class RouteCollection extends BaseRouteCollection {
public function addPrefix($prefix, array $defaults = [], array $requirements = []) {
foreach ($this->all() as $route) {
$route->setPath($prefix . rtrim($route->getPath(), '/'));
$route->addDefaults($defaults);
$route->addRequirements($requirements);
}
}
}
This makes sure the localized home page route doesn't end with a forward slash e.g. /fr/.
Finally I had to override the Route class with the following:
use ReflectionProperty;
use Symfony\Component\Routing\Route as BaseRoute;
class Route extends BaseRoute {
public function compile() {
// Call the parent method to get the compiled route
$compiledRoute = parent::compile();
// Override the regex property
$property = new ReflectionProperty($compiledRoute, 'regex');
$property->setAccessible(true);
$property->setValue($compiledRoute, str_replace('^/', '^/?', $compiledRoute->getRegex()));
return $compiledRoute;
}
}
This is particularly hacky but does save you from having to add a heap more code. All it does is replace the regular expression so that the first forward slash is optional which allows the /foo url to work. Note: You will have to make sure your RouteCollection is a collection of this class and not the Symfony Route class.
Hope this helps.