Problem monolog doctrine channel logging, symfony 4, with php - php

im have problem in monolog doctrine logging (in symfony 5):
framework.yaml:
monolog:
channels: [doctrine_channel]
handlers:
main:
channels: ["!event", "!doctrine_channel"]
doctrine:
type: service
channels: [doctrine_channel]
id: app.logger.doctrine_handler
services.yaml:
app.logger.doctrine_handler:
class: App\Util\DoctrineHandler
arguments:
- "#doctrine.orm.entity_manager"
src/Utils/DoctrineHandler.php:
<?
namespace App\Util;
use App\Entity\Logs;
use Doctrine\ORM\EntityManagerInterface;
use Monolog\Handler\AbstractProcessingHandler;
class DoctrineHandler extends AbstractProcessingHandler
{
private $initialized;
private $entityManager;
private $channel = 'doctrine_channel';
public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct();
$this->entityManager = $entityManager;
}
protected function write(array $record): void
{
if (!$this->initialized) {
$this->initialize();
}
if ($this->channel != $record['channel']) {
return;
}
$log = new Logs();
//$log->setMessage($record['message']);
//$log->setLevel($record['level_name']);
$log->setMessage($record['message']);
$log->setLevel($record['level']);
$log->setLevelName($record['level_name']);
$log->setExtra($record['extra']);
$log->setContext($record['context']);
$this->entityManager->persist($log);
$this->entityManager->flush();
}
private function initialize()
{
$this->initialized = true;
}
}
Now in the sample controller file:
in TestController.php
// in use inject LoggerInterface $logger
$this->logger = $logger;
$this->logger->info('test');
This is not logging to mysql database what is problem ?
Thank you in advance for all the hints...

You need to to use bindings. Try this guide https://nehalist.io/logging-events-to-database-in-symfony/
And this https://symfony.com/blog/new-in-symfony-4-2-autowiring-by-type-and-name

Related

In Symfony, how to call controller function in Menubuilder?

I have written one function as getAccess() in controller file Appbundle/Controller/BackendController.php.
I want to access this controller's method in Menu/Menubuilder.php file. How can I do that?
Menu and Appbundle folders are at same level.
For me, a controller can not be called in menuBuilder and it would not be "clean". I suggest you create a manager or service that contains this feature and call your service in your controller and in MenuBuilder.
namespace App\Service;
class MessageGenerator
{
public function getHappyMessage()
{
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}
What version of symfony are you on?
You can use Trait
Traits are a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.
So, you can create your function getAccess() in trait file and just use it in BackendController.php and Menubuilder.php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
I have created service as follows:
namespace AppBundle\Services;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class UserAccessService {
private $conn;
private $container;
private $tokenStorage;
public function __construct(EntityManagerInterface $entityManager, ContainerInterface $container, TokenStorageInterface $tokenStorage) {
$this->conn = $entityManager;
$this->container = $container;
$this->tokenStorage = $tokenStorage;
}
and added following code in services.yml:
app.service.useraccessservice:
class: AppBundle\Services\UserAccessService
arguments: ['#doctrine.orm.default_entity_manager','#service_container','#security.token_storage']
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
arguments: ["#knp_menu.factory", "#security.authorization_checker", '#security.token_storage', '#translator', '#app.service.useraccessservice','#kernel']
public: true
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main_menu }
- { name: knp_menu.menu_builder, method: createManagementMenu, alias: management_menu }
- { name: knp_menu.menu_builder, method: createUserMenu, alias: user_menu }
It works as expected.

How can I use doctrine method inside my service (Symfony 4)?

I created my first own service in Symfony :
// src/Service/PagesGenerator.php
namespace App\Service;
class PagesGenerator
{
public function getPages()
{
$page = $this->getDoctrine()->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}
But I get the error message:
Attempted to call an undefined method named "getDoctrine" of class
"App\Service\PagesGenerator".
I tried then to add in my services.yaml:
PagesGenerator:
class: %PagesGenerator.class%
arguments:
- "#doctrine.orm.entity_manager"
But then I get the error message:
The file "/Users/work/project/config/services.yaml" does not contain
valid YAML in /Users/work/project/config/services.yaml (which is
loaded in resource "/Users/work/project/config/services.yaml").
So, in comments I was saying that is better to let Symfony doing his job and autowiring EntityManager. This is what you should do. Also, can you tell us what Symfony version are you using and if autowiring is enabled (check services.yaml for that)?
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
class PagesGenerator
{
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
public function getPages()
{
$page = $this->em->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}
With Symfony 4 and the new autowiring you can easily inject certain number of class
To find out, which classes/interface you can use for autowiring, use this command:
bin/console debug:autowiring
We are going to use this one :
Doctrine\ORM\EntityManagerInterface
(doctrine.orm.default_entity_manager)
So let's make it, add this just before getPages function
/**
* #var EntityManagerInterface
*/
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
Then you can use it like this:
$page = $this->em->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
Hope it helps !
make sure you use proper indent using "spaces" for YAML.
A YAML file use spaces as indentation, you can use 2 or 4 spaces for
indentation, but no tab
read more about this
Before symfony 3.3
for example we have service sms_manager in AppBundle/FrontEndBundle/Services
services:
AppBundle.sms_manager:
class: AppBundle\FrontEndBundle\Services\SmsManager
arguments: [ '#service_container' ,'#doctrine.orm.entity_manager' ]
then your service can receive your arguments in constructor
<?php
namespace AppBundle\FrontEndBundle\Services;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class SmsManager {
private $container;
private $DM;
public function __construct( Container $container, \Doctrine\ORM\EntityManager $DM )
{
$this->container = $container;
$this->DM = $DM;
}
/**
* #return \Doctrine\ORM\EntityManager
*/
public function getDoctrine() {
return $this->DM;
}
}
With Symfony 3.3 or more,
Is there a way to inject EntityManager into a service
use Doctrine\ORM\EntityManagerInterface
class PagesGenerator
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
// ...
}

How to get the current user in twig extension service [symfony2.8]

I'm trying to get the current user in my NotificationExtension.php. But the page become very slow to load and I also get this error:
Error: Call to a member function getUser() on null
The error say that is impossible to get the current user, but i'm login.
This is my service:
notification:
class: Application\Sonata\UserBundle\Twig\NotificationExtension
arguments: ['#doctrine.orm.entity_manager', '#service_container', '#security.context']
tags:
- { name: twig.extension }
NotificationExtension :
<?php
namespace Application\Sonata\UserBundle\Twig;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Twig_Extension;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\Security;
class NotificationExtension extends \Twig_Extension
{
protected $container;
protected $em;
public function __construct(EntityManager $em,ContainerInterface $container, SecurityContext $context)
{
$this->container = $container;
$this->em = $em;
$this->doctrine = $container->get('doctrine');
$this->context = $context;
}
public function getGlobals()
{
$user = $this->container->get('security.context')->getToken()->getUser();
return(array(
'unreadMessagesCount' => $this->em->getRepository('ApplicationSonataUserBundle:Notif')->findBy(
array(
'user' => $user,
'seen' => true
),
array('date' => 'DESC')
)));
}
public function getName()
{
return 'notification';
}
}
ADD:
service:
notification:
class: Application\Sonata\UserBundle\Twig\NotificationExtension
arguments: ['#doctrine.orm.entity_manager','#security.token_storage']
tags:
- { name: twig.extension }
get current user:
public function getUser()
{
return $this->tokenStorage->getToken()->getUser();
}
Define instead a service as a global Twig variable:
# app/config/config.yml
twig:
# ...
globals:
user_notification: '#app.user_notification'
The service class:
// src/AppBundle/Twig/Globals/UserNotification.php
class UserNotification
{
private $tokenStorage;
// ...
public function __construct(TokenStorageInterface $tokenStorage, ...)
{
$this->tokenStorage = $tokenStorage;
// ...
}
public function getUnreadMessages()
{
if (null === $token = $this->tokenStorage->getToken()) {
return array();
}
$user = $token->getUser();
// $unreadMessages = <DB query for get the unread messages from current user>
return $unreadMessages;
}
}
The service definition:
# app/config/config.yml
services:
app.user_notification:
class: AppBundle\Twig\Globals\UserNotification
arguments: ['#security.token_storage', ...]
Finally, for all templates you can to use this service:
# foo.html.twig
{{ user_notification.unreadMessages|length }}
Whenever the global variable is accessed in the template, the service will be requested from the service container and you get access to that object.
More information http://symfony.com/doc/current/templating/global_variables.html
you can access the username like that : app.user.username
If you want to check if the user is logged, you can use the is_granted twig function.
eg :
{% if is_granted("ROLE") %}
Hi {{ app.user.username }}
{% endif %}

FOSUserBundle - Force password change after first login

In a Symfony2 application using FOSUserBundle for user management, the user table has been filled through an import script from a csv file and the password generated from a combination of data.
I would like to force the user to change his password at the first login.
When the event FOSUserEvents::SECURITY_IMPLICIT_LOGIN occurs, redirect to the route fos_user_change_password if the field last_login is NULL.
My idea was rewriting the method onImplicitLogin(UserEvent $event) of the class AGI\UserBundle\EventListener\LastLoginListener like this but the class is not overwritten:
public function onImplicitLogin(UserEvent $event) {
$user = $event->getUser ();
if ($user->getLastLogin () === null) {
$user->setLastLogin ( new \DateTime () );
$this->userManager->updateUser ( $user );
$response = new RedirectResponse ( $this->router->generate ( 'fos_user_change_password' ) );
$this->session->getFlashBag ()->add ( 'notice', 'Please change your password' );
$event->setResponse ( $response );
}
}
I already have a bundle overwriting FOSUserBundle and it works for controllers, forms, etc but It looks like it is not the way to do it with eventListeners.
How can I force the user to change the password after the first login?
With the help of the precious hint from #sjagr about fos_user.security.implicit_login that drove me to fos_user.security.implicit_login and an external topic about doing stuff right after login, I got a working solution.
AGI\UserBundle\Resources\config\services.yml
login_listener:
class: 'AGI\UserBundle\EventListener\LoginListener'
arguments: ['#security.context', '#router', '#event_dispatcher']
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login', method: onSecurityInteractiveLogin }
AGI\UserBundle\EventListener\LoginListener.php
<?php
namespace AGI\UserBundle\EventListener;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class LoginListener {
private $securityContext;
private $router;
private $dispatcher;
public function __construct(SecurityContext $securityContext, Router $router, EventDispatcherInterface $dispatcher) {
$this->securityContext = $securityContext;
$this->router = $router;
$this->dispatcher = $dispatcher;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event) {
if ($this->securityContext->isGranted ( 'IS_AUTHENTICATED_FULLY' )) {
$user = $event->getAuthenticationToken ()->getUser ();
if ($user->getLastLogin () === null) {
$this->dispatcher->addListener ( KernelEvents::RESPONSE, array (
$this,
'onKernelResponse'
) );
}
}
}
public function onKernelResponse(FilterResponseEvent $event) {
$response = new RedirectResponse ( $this->router->generate ( 'fos_user_change_password' ) );
$event->setResponse ( $response );
}
}
Thank you
If you require user change password due to some business rules, you can use kernel request EventListener:
<?php
namespace App\EventListener;
use App\Model\UserInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class ChangePasswordListener
{
private TokenStorageInterface $security;
private RouterInterface $router;
private array $excludedRoutes = [
'admin_change_password',
'admin_login',
'admin_login_check',
'admin_logout',
];
public function __construct(
TokenStorageInterface $security,
RouterInterface $router
) {
$this->security = $security;
$this->router = $router;
}
public function onKernelRequest(RequestEvent $event): void
{
if (false === $event->isMasterRequest()) {
return;
}
if ($event->getRequest()->isXmlHttpRequest()) {
return;
}
$currentRoute = $event->getRequest()->get('_route');
if (\in_array($currentRoute, $this->excludedRoutes, true)) {
return;
}
$token = $this->security->getToken();
if (null === $token) {
return;
}
$user = $token->getUser();
if ($user instanceof UserInterface && $user->shouldPasswordChange()) {
$response = new RedirectResponse($this->router->generate('admin_security_profile_change_password'));
$event->setResponse($response);
}
}
}
services.yaml:
services:
App\EventListener\ChangePasswordListener:
arguments:
- '#security.token_storage'
- '#router'
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: -100 }
You should provide also own UserInterface with method "shouldPasswordChange" and custom implementation of it.
It works great with Symfony 5.0 and PHP 7.4 but if you modify this code it should works also for lower PHP versions.

FOSUserBundle : get repository from inside FormHandler

I need to set a default value to a new user before saving it.
The problem is that I can't find a way to get an object through its repository from inside the FormHandler.
<?php
namespace Acme\UserBundle\Form\Handler;
use FOS\UserBundle\Form\Handler\RegistrationFormHandler as BaseHandler;
use FOS\UserBundle\Model\UserInterface;
class RegistrationFormHandler extends BaseHandler
{
protected function onSuccess(UserInterface $user, $confirmation)
{
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
if($user->isMale()){
$photo = $repository->getDefaultForMale();
$user->setPhoto($photo);
}
else {
$photo = $repository->getDefaultForFemale();
$user->setPhoto($photo);
}
parent::onSuccess($user, $confirmation);
}
}
The problem comes from the following line :
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
... and I can't find a way to get this repository, or the entity manager from this FormHandler.
Many thanks for your help !
A
You have to define a service that reference your extended handler class and point it in app/config.yml. e.g
The class,
//namespace definitions
class MyHandler extends RegistrationFormHandler{
private $container;
public function __construct(Form $form, Request $request, UserManagerInterface $userManager, MailerInterface $mailer, ContainerInterface $container)
{
parent::__construct($form, $request, $userManager, $mailer);
$this->container = $container;
}
protected function onSuccess(UserInterface $user, $confirmation)
{
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
// your code
}
The service,
my.registration.form.handler:
scope: request
class: FQCN\Of\MyHandler
arguments: [#fos_user.registration.form, #request, #fos_user.user_manager, #fos_user.mailer, #service_container]
Lastly in app/config.yml,
fos_user:
#....
registration:
#...
form:
handler: my.registration.form.handler
FOS got his own UserManager. Try to use this.

Categories