I am using Symfony 4.2.1 and I cannot somehow not use the generate() Method of router:
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Routing\RouterInterface;
class NewAcceptedOrderCommand extends Command
{
/**
* #var RouterInterface $router
*/
private $router;
public function __construct(
RouterInterface $router
) {
$this->router = $router;
parent::__construct();
}
protected function configure()
{
$this
->setName('address:new')
->setDescription('Get adresses by Status ID')
->setHelp('Get adresses by Status ID')
->addArgument('id', InputArgument::REQUIRED, 'Status ID');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$url = $this->router->generate('adresses_index');
// SOME more Code
}
}
I injected all sorts of other services and interfaces before (EntityManagerInterface, my Logservice etc.)
But I am always getting
Unable to generate a URL for the named route "adresses_index" as such
route does not exist.
But this route exists for sure I checked with php bin/console debug:router and also using it in other places. I am using the global approach from here: https://symfony.com/doc/current/console/request_context.html#configuring-the-request-context-globally
I was thinking to secure at the beginning and added the annotation "method" to every action - it has to be unset (ANY). It works fine afterwards.
Dont forget to configure your router
$context = $this->router->getContext();
$context->setHost($this->container->getParameter('router_host'));
$context->setScheme($this->container->getParameter('router_scheme'));
Related
It's possibile with security file config to redirect user already logged in to specific route (e.g homepage) if they try to access on login/register pages? One solution that I already found is to attach a listener to EventRequest, but I prefer to use security config if it's possible.
After some googling I noticed that another solution is to override the fosuserbundle controllers. But because I need that this behavior should works also for /register and /resetting pages, instead to override also those controller, I preferred to use EventListener. Maybe this's the best solution in this case. I'm using Symfony 4, so for the other versions could be different.
My code:
namespace App\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class LoggedInUserListener
{
private $router;
private $authChecker;
public function __construct(RouterInterface $router, AuthorizationCheckerInterface $authChecker)
{
$this->router = $router;
$this->authChecker = $authChecker;
}
/**
* Redirect user to homepage if tryes to access in anonymously path
* #param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$path = $request->getPathInfo();
if ($this->authChecker->isGranted('IS_AUTHENTICATED_REMEMBERED') && $this->isAnonymouslyPath($path)) {
$response = new RedirectResponse($this->router->generate('homepage'));
$event->setResponse($response);
}
}
/**
* Check if $path is an anonymously path
* #param string $path
* #return bool
*/
private function isAnonymouslyPath(string $path): bool
{
return preg_match('/\/login|\/register|\/resetting/', $path) ? true : false;
}
}
add this to services.yaml:
App\EventListener\LoggedInUserListener:
tags:
- { name: kernel.event_listener, event: kernel.request }
#Mintendo, I have errors using your code:
request.CRITICAL: Exception thrown when handling an exception (Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException: The token storage contains no authentication token.
php.CRITICAL: Uncaught Exception: The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL.
Besides that debug bar also showed error and was broken.
But you pushed me in the right direction, so I have modified your code a little.
And it works without errors now:
<?php
namespace App\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;
class LoggedInUserListener
{
private $router;
private $security;
public function __construct(RouterInterface $router, Security $security)
{
$this->router = $router;
$this->security = $security;
}
/**
* Redirect user to homepage if tries to access in anonymously path
* #param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$path = $request->getPathInfo();
if ($this->security->getUser() && $this->isAnonymouslyPath($path)) {
$response = new RedirectResponse($this->router->generate('dashboard'));
$event->setResponse($response);
}
}
/**
* Check if $path is an anonymously path
* #param string $path
* #return bool
*/
private function isAnonymouslyPath(string $path): bool
{
return preg_match('/\/login|\/register|\/resetting/', $path) ? true : false;
}
}
I created some new events like app.client_enter or app.client_leave. Now I want to register a listener to listen on this events. If I add a listener in the same command, it's working.
ClientListener.php
namespace AppBundle\Service;
use AppBundle\Event\ClientEnterEvent;
class ClientListener {
public function onClientEnter(ClientEnterEvent $event) {
echo "It could be working";
}
}
service.yml (update)
services:
app.client_listener:
class: AppBundle\Service\ClientListener
tags:
- { name: kernel.event_listener, event: app.client_enter, method: onClientEnter }
ClientCommand.php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use AppBundle\Event\ClientEnterEvent;
class ClientCommand extends ContainerAwareCommand {
protected function configure() { ... }
protected function execute(InputInterface $input, OutputInterface $output) {
$dispatcher = new EventDispatcher();
$dispatcher->dispatch('app.client_enter', new ClientEnterEvent("Maxi"));
}
It's name: kernel.event_listener for the tag
Thank you to all. I found the solution, in ContainerAwareCommand you have to use the service of event_dispatcher.
ClientCommand.php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use AppBundle\Event\ClientEnterEvent;
class ClientCommand extends ContainerAwareCommand {
protected function configure() { ... }
protected function execute(InputInterface $input, OutputInterface $output) {
$dispatcher = $this->getContainer->get('event_dispatcher');
$dispatcher->dispatch('app.client_enter', new ClientEnterEvent("Maxi"));
}
After I used this service, my event trigger the listener.
Just a tip hoẃ to make this even better.
Since great Dependency Injection changes in Symfony 3.3+ you can delegate many error prone code to Symfony.
Simplify Service Registration
# app/config/services.yml
services:
_defaults:
autowire: true
AppBundle\:
resouce: '../../src/AppBundle'
It doesn't work for listeners, because of extra tags, but it does for subscribers - I recommend to use them to prevent any extra redundant config proramming.
Get Arguments The Clean Way - Via Constructor
Using that, you can use constructor injection in your commands out of the box.
use Symfony\Component\EventDispatcher\EventDispatcherInterface
class ClientCommand extends Command
{
/**
* #var EventDispatcherInterface
*/
private $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->eventDispatcher->dispatch(...);
}
}
To read more about DI changes, see this post with before/after examples.
I need to log the last user's activity time, every page load or ajax call counts.
I suppose I need to subscribe to some event, But I just have no idea to which one.
InteractiveLoginEvent mentioned in this answer, to my understanding is fired in the event of the interactive login only. But, given a session could last a week or more, it will make the record way too inaccurate. So I need another event, but which one?
Or, is there an out of the box functionality for this?
A solution could be a listener for KernelEvents::RESPONSE event, ensuring that the user is authenticated.
namespace AppBundle\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class LastActivityListener implements EventSubscriberInterface
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function onResponse(FilterResponseEvent $event)
{
$token = $this->tokenStorage->getToken();
if ($token->isAuthenticated()) {
// save last activity for $token->getUser(); in some place.
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => 'onResponse',
];
}
}
Also, you might need inject the storage service to save this record (e.g. EntityManager if Doctrine is available).
The simplest way to do this would be to subscribe to the kernel.controller event, which will run before every controller action, whether normally or via AJAX. It would look like this:
namespace AppBundle\EventSubscriber;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class UserActivityLogSubscriber implements EventSubscriberInterface
{
/** #var TokenStorageInterface **/
private $tokenStorage;
/** #var LoggerInterface **/
private $logger;
/**
* #param TokenStorageInterface $tokenStorage
* #param LoggerInterface $logger
*/
public function __construct(
TokenStorageInterface $tokenStorage,
LoggerInterface $logger
) {
$this->tokenStorage = $tokenStorage;
$this->logger = $logger;
}
public function onKernelController(FilterControllerEvent $event)
{
$actionTime = new \DateTime();
$controller = $event->getController();
if (!is_array($controller) {
return;
}
$action = get_class($controller[0]).'::'.$controller[1];
$token = $this->tokenStorage->getToken();
$user = $token->getUser();
if ($user) {
$logger->info('User: '.$user->getId().' Action: '.$action.' at: '.$now->format('Y-m-d g:i:s');
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
);
}
}
This is just a simple example logging the controller action to your standard logger. Instead of just outputting to a log, you could inject the EntityManager and log the event time to a last_activity column in the database for example.
You could also do something like make a UserLoggableController controller interface and only perform this action if your controller implements that interface:
Interface:
namespace AppBundle\Controller;
interface UserLoggableController
{
// ...
}
Controller:
class MyController extends Controller implements UserLoggableController
Modified UserActivityLogSubscriber:
if (!$controller[0] instanceof UserActivityLogSubscriber) {
return;
}
Symfony also has some nice documentation on setting up controller before/after filters.
This is my entity, and i used a gedmo annotation, when a new register is created (persits) the slug works correctly but how can i auto-generate slug texts from existing database
/**
* #Gedmo\Slug(fields={"name"})
* #ORM\Column(type="string", unique=true)
*/
protected $slug;
You have to do it manually by selecting all the value without a slug and setting the slug value to null as describe inside the documentation of the Sluggable Behavior.
https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/sluggable.md#regenerating-slug
Here is a naive Symfony command to regenerate all slugs of the given classes:
<?php
namespace App\Command;
use App\Entity\Foo;
use App\Entity\Bar;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class RegenerateSlugs extends Command
{
private $doctrine;
protected static $defaultName = "app:regenerate-slugs";
public function __construct(ManagerRegistry $doctrine)
{
parent::__construct();
$this->doctrine = $doctrine;
}
protected function configure(): void
{
$this
->setDescription('Regenerate the slugs for all Foo and Bar entities.')
;
}
protected function execute(InputInterface $input, OutputInterface $output): void
{
$manager = $this->doctrine->getManager();
// Change the next line by your classes
foreach ([Foo::class, Bar::class] as $class) {
foreach ($manager->getRepository($class)->findAll() as $entity) {
$entity->setSlug(null);
//$entity->slug = null; // If you use public properties
}
$manager->flush();
$manager->clear();
$output->writeln("Slugs of \"$class\" updated.");
}
}
}
Hi hope it may help someone stumbling upon this question!
How can I use $this->renderView inside a symfony Command (not inside a controller)? I new about the function "renderView" but what do I have to setup to use it wihtin a command?
Thank you in advance an regards
Your command class must extends the ContainerAwareCommand abstract class and then you can do:
$this->getContainer()->get('templating')->render($view, $parameters);
When it comes to commands that extend ContainerAwareCommand the proper way to obtain the container is by getContainer() unlike in controller shortcut.
In Symfony 4 I could not get $this->getContainer()->get('templating')->render($view, $parameters); to work.
I set the namespace use for Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand and extended ContainerAwareCommand class EmailCommand extends ContainerAwareCommand
I get an exception thrown
[Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException]
You have requested a non-existent service "templating".
For Symfony 4, this is the solution I came up with.
First I installed Twig.
composer require twig
Then created my own twig service.
<?php
# src/Service/Twig.php
namespace App\Service;
use Symfony\Component\HttpKernel\KernelInterface;
class Twig extends \Twig_Environment {
public function __construct(KernelInterface $kernel) {
$loader = new \Twig_Loader_Filesystem($kernel->getProjectDir());
parent::__construct($loader);
}
}
Now my email command looks like this.
<?php
# src/Command/EmailCommand.php
namespace App\Command;
use Symfony\Component\Console\Command\Command,
Symfony\Component\Console\Input\InputInterface,
Symfony\Component\Console\Output\OutputInterface,
App\Service\Twig;
class EmailCommand extends Command {
protected static $defaultName = 'mybot:email';
private $mailer,
$twig;
public function __construct(\Swift_Mailer $mailer, Twig $twig) {
$this->mailer = $mailer;
$this->twig = $twig;
parent::__construct();
}
protected function configure() {
$this->setDescription('Email bot.');
}
protected function execute(InputInterface $input, OutputInterface $output) {
$template = $this->twig->load('templates/email.html.twig');
$message = (new \Swift_Message('Hello Email'))
->setFrom('emailbot#domain.com')
->setTo('someone#somewhere.com')
->setBody(
$template->render(['name' => 'Fabien']),
'text/html'
);
$this->mailer->send($message);
}
}
Yet another one: rely on dependency injection, i.e. inject ContainerInterface
namespace AppBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Psr\Container\ContainerInterface;
class SampleCommand extends Command
{
public function __construct(ContainerInterface $container)
{
$this->templating = $container->get('templating');
parent::__construct();
}
protected function configure()
{
$this->setName('app:my-command')
->setDescription('Do my command using render');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = retrieveSomeData();
$csv = $this->templating->render('path/to/sample.csv.twig',
array('data' => $data));
$output->write($csv);
}
private $templating;
}
This relies on Symfony to inject the container, which in turn is used to retrieve either templating or twig or whatever you need for your custom command.