I have a drupal module called hello_world.
I have an info file in drupal/web/modules/custom/hello_world called hello_world.info.yml that contains:
name: Hello World
description: Hello World module
type: module
core: 8.x
package: Custom
This works perfectly ; the module is in the extension list.
Now I tried to make a help hook so I made in the same folder a hello_world.module file with this code:
<?php
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function hello_world_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.hello_world':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('This is an example module.') . '</p>';
return $output;
break;
}
}
This does not work at all. The help page is not shown.
I have also tried to make a hello world page using this controller in drupal/web/modules/custom/hello_world/src/HelloWOrldCOntroller.php:
<?php
namespace Drupal\hello_world\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Controller for the salutation message.
*/
class HelloWorldController extends ControllerBase
{
/**
* Hello World.
*
* #return string
*/
public function helloWorld()
{
return [
'#markup' => $this->t('Hello World')
];
}
}
And this route in drupal/web/modules/custom/hello_world called hello_world.routing.yml:
hello_world.hello:
path: '/hello'
defaults:
_controller:
'\Drupal\hello_world\Controller\HelloWorldController::helloWorld'
_title: 'Our first route'
requirements:
_permission: 'access content'
This does not work either, even after clearing the cache. As I said, the hello_world.info.yml works perfectly fine, but the help hook and the controller/routing does not. The rest of the core modules work. I am using Vagrant if it matters.
Try to reinstall the module and clear caches and routes should be intended properly otherwise will run into error
hello_world.hello:
path: '/hello'
defaults:
_controller:'\Drupal\hello_world\Controller\HelloWorldController::helloWorld'
_title: 'Our first route'
requirements:
_permission: 'access content'
In Symfony, after a user successfully log out, how to display a success message like "you have successfully logged out" ?
1) Create a new service to handle the logout success event.
In services.yml add the service:
logout_success_handler:
class: Path\To\YourBundle\Services\LogoutSuccessHandler
arguments: ['#security.http_utils']
And add the class, replacing /path/to/your/login with the url of your login page (in the last line of the controller):
<?php
namespace Path\To\YourBundle\Services;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
class LogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
protected $httpUtils;
protected $targetUrl;
/**
* #param HttpUtils $httpUtils
*/
public function __construct(HttpUtils $httpUtils)
{
$this->httpUtils = $httpUtils;
$this->targetUrl = '/path/to/your/login?logout=success';
}
/**
* {#inheritdoc}
*/
public function onLogoutSuccess(Request $request)
{
$response = $this->httpUtils->createRedirectResponse($request, $this->targetUrl);
return $response;
}
}
2) Configure your security.yml to use the custom LogoutSuccessHandler just created:
firewalls:
# ...
your_firewall:
# ...
logout:
# ...
success_handler: logout_success_handler
3) In the twig template of your login page add:
{% if app.request.get('logout') == "success" %}
<p>You have successfully logged out!</p>
{% endif %}
Imho, there is a definitively simpler way. In security.yaml, define a route to redirect to after logout thanks to the target key :
security:
firewalls:
main:
[...]
logout:
path: /logout
target: /logout_message
Then in a controller (SecurityController.php is fine for this), define this action, wich only add the flash message and then redirect where you want (home in this exemple) :
/**
* #Route("/logout_message", name="logout_message")
*/
public function logoutMessage()
{
$this->addFlash('success', "You've been disconnected. Bye bye !");
return $this->redirectToRoute('home');
}
I am working on a symfony application where my goal is no matter what page the user is on it will navigate to the locale version of the page.
For example, if the user navigates to "/" the home page, it will redirect to "/en/"
If they are on "/admin" page it will redirect to "/en/admin", in such a way that the _locale property is set from the route.
Also it needs to determine the locale if they visit /admin from the users browser since no locale was determined so it knows which page to redirect to.
Currently my default controller looks like below since I'm testing. I'm using the dev mode & profiler to test that translations are working in correct.
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
* #Route("/{_locale}/", name="homepage_locale")
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
This current method will keep the user at "/" if they navigate there, but I want to have it redirect to "/en/". This should work for other pages too, like /admin, or /somepath/pathagain/article1 (/en/admin , /en/somepath/pathagain/article1)
How would I do this?
References I've read that did not help:
Symfony2 Use default locale in routing (one URL for one language)
Symfony2 default locale in routing
::Update::
I have not solved my issue but I've come close as well as learned a few tricks to be more efficient.
DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="home", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* #Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* #Route("/admin", name="admin", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* #Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
Config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
# Put parameters here that don't need to change on each machine where the app is deployed
# http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: en
app.locales: en|es|zh
framework:
#esi: ~
translator: { fallbacks: ["%locale%"] }
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
templating:
engines: ['twig']
#assets_version: SomeVersionScheme
default_locale: "%locale%"
trusted_hosts: ~
trusted_proxies: ~
session:
# handler_id set to null will use default session handler from php.ini
handler_id: ~
save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
fragments: ~
http_method_override: true
assets: ~
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
# path: "%database_path%"
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
# Swiftmailer Configuration
swiftmailer:
transport: "%mailer_transport%"
host: "%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
spool: { type: memory }
Notice under parameters the value app.locales: en|es|zh. This is now a value I can reference whenever I create my routes if I plan to support more locales in the future which I do. Those routes are english, spanish, chinese in that order for those curious. In the DefaultController in the annotations the "%app.locales%" is the part that references the config parameter.
The problem with my current method is going to /admin for example does not redirect the user to /{browsers locale}/admin, which would be the more elegant solution to keep everything organized... but at least the routes work. Still looking for better solution.
****Update****
I think I may have possibly found the answer here as the bottom answer given (Add locale and requirements to all routes - Symfony2), the answer by Athlan. Just not sure how to implement this in symfony 3 as his directions were not clear enough to me.
I think this article might help also (http://symfony.com/doc/current/components/event_dispatcher/introduction.html)
I don't have enough reputation to add a comment to the correct solution. So I'm adding a new answer
You can add "prefix: /{_locale}" at app/config/routing.yml like this:
app:
resource: "#AppBundle/Controller/"
type: annotation
prefix: /{_locale}
So you don't need to add it to every route to every action. For the following steps. Thank you very much it's perfect.
After 12 hours of looking into this I finally found an acceptable solution. Please post revised versions of this solution if you can make it more efficient.
Some things to note, my solution is particular to my need. What it does is force any URL to go to a localized version if it exists.
This requires some conventions to be followed when you create routes.
DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* #Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
Notice that both routes always start with "/{_locale}/". For this to work every route in your project needs to have this. You just put the real route name afterwards. For me I was okay with this scenario. You can modify my solution to fit your needs easily enough.
The first step is to create a listen on the httpKernal to intercept requests before they go to the routers to render them.
LocaleRewriteListener.php
<?php
//src/AppBundle/EventListener/LocaleRewriteListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\RouteCollection;
class LocaleRewriteListener implements EventSubscriberInterface
{
/**
* #var Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* #var routeCollection \Symfony\Component\Routing\RouteCollection
*/
private $routeCollection;
/**
* #var string
*/
private $defaultLocale;
/**
* #var array
*/
private $supportedLocales;
/**
* #var string
*/
private $localeRouteParam;
public function __construct(RouterInterface $router, $defaultLocale = 'en', array $supportedLocales = array('en'), $localeRouteParam = '_locale')
{
$this->router = $router;
$this->routeCollection = $router->getRouteCollection();
$this->defaultLocale = $defaultLocale;
$this->supportedLocales = $supportedLocales;
$this->localeRouteParam = $localeRouteParam;
}
public function isLocaleSupported($locale)
{
return in_array($locale, $this->supportedLocales);
}
public function onKernelRequest(GetResponseEvent $event)
{
//GOAL:
// Redirect all incoming requests to their /locale/route equivlent as long as the route will exists when we do so.
// Do nothing if it already has /locale/ in the route to prevent redirect loops
$request = $event->getRequest();
$path = $request->getPathInfo();
$route_exists = false; //by default assume route does not exist.
foreach($this->routeCollection as $routeObject){
$routePath = $routeObject->getPath();
if($routePath == "/{_locale}".$path){
$route_exists = true;
break;
}
}
//If the route does indeed exist then lets redirect there.
if($route_exists == true){
//Get the locale from the users browser.
$locale = $request->getPreferredLanguage();
//If no locale from browser or locale not in list of known locales supported then set to defaultLocale set in config.yml
if($locale=="" || $this->isLocaleSupported($locale)==false){
$locale = $request->getDefaultLocale();
}
$event->setResponse(new RedirectResponse("/".$locale.$path));
}
//Otherwise do nothing and continue on~
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
Finally you set the services.yml to start the listener up.
Services.yml
# Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html
parameters:
# parameter_name: value
services:
# service_name:
# class: AppBundle\Directory\ClassName
# arguments: ["#another_service_name", "plain_value", "%parameter_name%"]
appBundle.eventListeners.localeRewriteListener:
class: AppBundle\EventListener\LocaleRewriteListener
arguments: ["#router", "%kernel.default_locale%", "%locale_supported%"]
tags:
- { name: kernel.event_subscriber }
Also in the config.yml you will want to add the following under parameters:
config.yml
parameters:
locale: en
app.locales: en|es|zh
locale_supported: ['en','es','zh']
I wanted there to be only one place you define the locales but I wound up having to do 2...but at least they are in the same spot so easy to change.
app.locales is used in default controller (requirements={"_locale" = "%app.locales%"}) and locale_supported is used in the LocaleRewriteListener. If it detects a locale that is not in the list it will fallback to the default locale, which in this case is the value of locale:en.
app.locales is nice with the requirements command because it will cause a 404 for any locales that do not match.
If you are using forms and have a login you will need to do the following to your security.yml
Security.yml
# To get started with security, check out the documentation:
# http://symfony.com/doc/current/book/security.html
security:
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
cost: 12
AppBundle\Entity\User:
algorithm: bcrypt
cost: 12
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
database:
entity: { class: AppBundle:User }
#property: username
# if you're using multiple entity managers
# manager_name: customer
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
anonymous: true
form_login:
check_path: login_check
login_path: login_route
provider: database
csrf_token_generator: security.csrf.token_manager
remember_me:
secret: '%secret%'
lifetime: 604800 # 1 week in seconds
path: /
httponly: false
#httponly false does make this vulnerable in XSS attack, but I will make sure that is not possible.
logout:
path: /logout
target: /
access_control:
# require ROLE_ADMIN for /admin*
#- { path: ^/login, roles: ROLE_ADMIN }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/(.*?)/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
The important change to note here is that (.*?)/login will authenticate anonymously so your users can still login. This does mean that routes like..dogdoghere/login could trigger, but the requirements I will show you shortly on the login routes prevent this and will throw 404 errors. I like this solution with the (.*?) versus [a-z]{2} incase you wanted to use en_US type locales.
SecurityController.php
<?php
// src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class SecurityController extends Controller
{
/**
* #Route("{_locale}/login", name="login_route", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
*/
public function loginAction(Request $request)
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
}
/**
* #Route("/{_locale}/login_check", name="login_check", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
/**
* #Route("/logout", name="logout")
*/
public function logoutAction()
{
}
}
?>
Note that even these paths use {_locale} in front. I like this however so I can give custom logins for different locales. Just keep that in mind. The only route that does not need the locale is logout which works just fine since its really only an intercept route for the security system. Also notice it uses the requirements which is set from the config.yml, so you only have to edit it in one place for all the routes across your projects.
Hope this helps someone trying to do what I was doing!
NOTE:: To test this easily I use 'Quick Language Switcher' extension for Google Chrome, which changes the accept-language header on all requests.
final function smallResumeOfResearching($localeRewrite, $opinion = 'IMHO') :)
The method, provided by mr. Joseph working great with routes like /{route_name}, or /, but not with routes like /article/slug/other.
If we use modified mr.Joseph's method, provided by https://stackoverflow.com/a/37168304/9451542, we will lost profiler and debugger in dev mode.
If we want more flexible solution, onKernelRequest method can be modified like this (thanks to mr. Joseph, thanks to https://stackoverflow.com/a/37168304/9451542):
public function onKernelRequest(GetResponseEvent $event)
{
$pathInfo = $event->getRequest()->getPathinfo();
$baseUrl = $event->getRequest()->getBaseUrl();
$checkLocale = explode('/', ltrim($pathInfo, '/'))[0];
//Or some other logic to detect/provide locale
if (($this->isLocaleSupported($checkLocale) == false) && ($this->defaultLocale !== $checkLocale)) {
if ($this->isProfilerRoute($checkLocale) == false) {
$locale = $this->defaultLocale;
$event->setResponse(new RedirectResponse($baseUrl . '/' . $locale . $pathInfo));
}
/* Or with matcher:
try {
//Try to match the path with the locale prefix
$this->matcher->match('/' . $locale . $pathInfo);
//$event->setResponse(new RedirectResponse($baseUrl . '/' . $locale . $pathInfo));
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
} catch (\Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
}
*/
}
}
note: $this->profilerRoutes = array('_profiler', '_wdt', '_error');
Thanks to Susana Santos for pointing to simple config method :)
Small improvement for Symfony 3.4:
Be sure, that the getSubscribedEvents() will register LocaleRewriteListener BEFORE RouterListener::onKernelRequest and BEFORE LocaleListener::onKernelRequest. Integer 17 must be greater than RouterListener::onKernelRequest priotity. Otherwise you will got 404.
bin/console debug:event-dispatcher
Service definition in services.yml must be (depends on Symfony configuration):
AppBundle\EventListener\LocaleRewriteListener:
arguments: ['#router', '%kernel.default_locale%', '%locale_supported%']
tags:
- { name: kernel.event_subscriber, event: kernel.request }
I am using symfony and I want to get the url of a specific route ,
my route is like this
project_sign_in:
pattern: /signin
defaults: { _controller: ProjectContactBundle:User:signIn }
i want to generate the url from this route so i can get
localhost/app_dev.php/signin
or {SERVER-ADDRESS}/app_dev/signin
if I was browsing the server.
Using the Routing Component at version 4.0:
<?php
use Symfony\Component\Routing\Generator\UrlGenerator;
UrlGenerator->generate('project_sign_in', [], UrlGenerator::ABSOLUTE_URL);
The last facultative parameter has to be true to generate absolute url:
$router->generate('project_sign_in', array(), true);
in twig:
{{ path('project_sign_in', {}, true) }}
{# or #}
{{ url('project_sign_in') }}
in controller:
$this->generateUrl('project_sign_in', array(), true );
EDIT: for symfony 4, see #Michael B. answer
UrlGenerator->generate('project_sign_in', [], UrlGenerator::ABSOLUTE_URL);
In Symfony 5.0, if you are using Routing within a service:
namespace App\Service;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
.
.
.
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
public function foo()
{
$this->router->generate('bar', [], urlGeneratorInterface::ABSOLUTE_URL);
}
I'm trying to write a symfony 2 functional test. This is my code:
<?php
namespace WebSite\MainBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ProductControllerTest extends WebTestCase
{
public function testPageContents()
{
$domCategoryLinksExpr = '.catalog-col-block > ul > li > a';
$client = static::createClient();
$crawler = $client->request('GET', '/catalog/');
$this->assertTrue($client->getResponse()->getStatusCode() == '200');
$countCategories = $crawler->filter($domCategoryLinksExpr)->count();
$this->assertTrue($crawler->filter($domCategoryLinksExpr)->count() > 0);
$categoryLink = $crawler->filter($domCategoryLinksExpr)->eq(rand(1, $countCategories))->link();
$crawler = $client->click($categoryLink);
}
}
But when i run this test:
phpunit -c app src/WebSite/MainBundle/Tests/Controller/
I got this:
1) WebSite\MainBundle\Tests\Controller\ProductControllerTest::testPageContents
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: No route found for "GET /app_dev.php/catalog/psp"
...
/app_dev.php/catalog/psp is the dynamic value of $categoryLink->getUri(). And
this route exists and correctly works in web browser. Any ideas?
UPD:
This is my routing rules:
routing_dev.yml:
...
_main:
resource: routing.yml
....
routing.yml:
....
WebSiteCategoryBundle:
resource: "#WebSiteCategoryBundle/Controller/"
type: annotation
prefix: /
....
src/WebSite/CategoryBundle/CategoryController.php:
/**
* #Route("/catalog")
*/
class CategoryController extends Controller
{
/**
* #Route("/{alias}", name="show_category" )
* #Template()
*/
public function showAction( $alias )
{
// some action here
}
}
It works fine in browser, but seems like $crowler does`not see this annotation rules.
UPD2: The problem was in "routing_test.yml" which missing in Symfony 2 standard edition.
So I create it:
routing_test.yml:
_main:
resource: routing_dev.yml
and exception disappear. Thanks to all.
It would be nice if you posted your routing.yml
You can also take a look at:
http://symfony.com/doc/master/bundles/SensioFrameworkExtraBundle/annotations/routing.html
You can use routing annotation at your actions.
To solve your problem I would like to see your routing config.
echo $client->getResponse()->getContent() will help you with debugging (even there is an exception). It will output html of the request.
It looks like your route does not exist and might be in wrong location (wrong environment specified?)