I have successfully set up VichGeographicalBundle to display a bunch of places in a Google Map. Everything works ok, except the infowindows that do not show on click.
$this->setShowInfoWindowsForMarkers(true); is set but does not seem to work.
Any ideas ?
EDIT:
class allShopsMap extends Map
{
/**
* Constructs a new instance of LocationMap.
*/
public function __construct(EntityManager $em)
{
parent::__construct();
// configure your map in the constructor
// by setting the options
$this->setShowZoomControl(true);
$this->setZoom(13);
$this->setAutoZoom(false);
$this->setContainerId('map_canvas');
$this->setWidth(980);
$this->setHeight(360);
$this->setShowInfoWindowsForMarkers(true);
$this->setCenter(23.232323,23.232323);
$this->setShowMapTypeControl(true);
$query = $em->createQuery("SELECT st
FROM acme\ShopBundle\Entity\Shop sh
WHERE sh.published = 1 ");
$shops = $query->getResult();
foreach ($shops as $shop) {
$this->addMarker(new MapMarker($shop->getLatitude(), $shop->getLongitude(),$icon='images/map_marker.png'));
}
}
}
Called from twig template:
{{ vichgeo_map('allShops') }}
config.yml
vich_geographical:
db_driver: orm
query_service: vich_geographical.query_service.default
map_renderer: vich_geographical.map_renderer.google
templating:
engine: twig
info_window: msgrShopBundle:Map:infoWindow.html.twig
services:
msgr.map.allShops:
class: msgr\ShopBundle\Map\allShopsMap
tags:
- { name: vichgeo.map, alias: allShops }
arguments:
entityManager: "#doctrine.orm.entity_manager"
HTML Code generated by {{ vichgeo_map('allShops') }} : http://pastebin.com/jqvzG67N
Try this:
class allShopsMap extends Map
{
/**
* Constructs a new instance of LocationMap.
*/
public function __construct(EntityManager $em, $infoWindowBuilder)
{
parent::__construct();
// configure your map in the constructor
// by setting the options
$this->setShowZoomControl(true);
$this->setZoom(13);
$this->setAutoZoom(false);
$this->setContainerId('map_canvas');
$this->setWidth(980);
$this->setHeight(360);
$this->setShowInfoWindowsForMarkers(true);
$this->setCenter(23.232323,23.232323);
$this->setShowMapTypeControl(true);
$query = $em->createQuery("SELECT st
FROM acme\ShopBundle\Entity\Shop sh
WHERE sh.published = 1 ");
$shops = $query->getResult();
foreach ($shops as $shop) {
$marker = new MapMarker($shop->getLatitude(), $shop->getLongitude(),$icon='images/map_marker.png');
$marker->setInfoWindow($infoWindowBuilder->build($marker));
$this->addMarker($marker);
}
}
}
infoWindowBuilder is vich_geographical.info_window_builder service that is available in the container.
And modify your config:
services:
msgr.map.allShops:
class: msgr\ShopBundle\Map\allShopsMap
tags:
- { name: vichgeo.map, alias: allShops }
arguments:
entityManager: "#doctrine.orm.entity_manager"
infoWindowBuilder: "#vich_geographical.info_window_builder"
Related
The application that I am building is not going to work in a traditional way. All the routes ar going to be stored in the database. And based on the route provided I need to get the correct controller and action to be executed.
As I understand this can be achieved using the "kernel.controller" event listener: https://symfony.com/doc/current/reference/events.html#kernel-controller
I am trying to use the docs provided, but the example here does not exacly show how to set up a new callable controller to be passed. And I have a problem here, because I dont know how to inject the service container to my newly called controller.
At first the setup:
services.yaml
parameters:
db_i18n.entity: App\Entity\Translation
developer: '%env(DEVELOPER)%'
category_directory: '%kernel.project_dir%/public/uploads/category'
temp_directory: '%kernel.project_dir%/public/uploads/temp'
product_directory: '%kernel.project_dir%/public/uploads/product'
app.supported_locales: 'lt|en|ru'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
App\Translation\DbLoader:
tags:
- { name: translation.loader, alias: db }
App\Extension\TwigExtension:
arguments:
- '#service_container'
tags:
- { name: twig.extension }
App\EventListener\RequestListener:
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onControllerRequest }
The listener:
RequestListener.php
<?php
namespace App\EventListener;
use App\Controller\Shop\HomepageController;
use App\Entity\SeoUrl;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
class RequestListener
{
public ManagerRegistry $doctrine;
public RequestStack $requestStack;
public function __construct(ManagerRegistry $doctrine, RequestStack $requestStack)
{
$this->doctrine = $doctrine;
$this->requestStack = $requestStack;
}
/**
* #throws Exception
*/
public function onControllerRequest(ControllerEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
if(str_contains($this->requestStack->getMainRequest()->getPathInfo(), '/admin')) {
return;
}
$em = $this->doctrine->getManager();
$pathInfo = $this->requestStack->getMainRequest()->getPathInfo();
;
$route = $em->getRepository(SeoUrl::class)->findOneBy(['keyword' => $pathInfo]);
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = new HomepageController();
$event->setController([$controller, $route->getAction()]);
break;
default:
break;
}
} else {
throw new Exception('Route not found');
}
}
}
So this is the most basic example. I get the route from the database, if it a "homepage" route, I create the new HomepageController and set the action. However I am missing the container interface that I dont know how to inject. I get this error:
Call to a member function has() on null
on line: vendor\symfony\framework-bundle\Controller\AbstractController.php:216
which is:
/**
* Returns a rendered view.
*/
protected function renderView(string $view, array $parameters = []): string
{
if (!$this->container->has('twig')) { // here
throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
}
return $this->container->get('twig')->render($view, $parameters);
}
The controller is as basic as it gets:
HomepageController.php
<?php
namespace App\Controller\Shop;
use App\Repository\CategoryRepository;
use App\Repository\Shop\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomepageController extends AbstractController
{
#[Route('/', name: 'index', methods: ['GET'])]
public function index(): Response
{
return $this->render('shop/index.html.twig', [
]);
}
}
So basically the container is not set. If I dump the $event->getController() I get this:
RequestListener.php on line 58:
array:2 [▼
0 => App\Controller\Shop\HomepageController {#417 ▼
#container: null
}
1 => "index"
]
I need to set the container by doing $controller->setContainer(), but what do I pass?
Do not inject the container, controllers are services too and manually instanciating them is preventing you from using constructor dependency injection. Use a service locator which contains only the controllers:
Declared in config/services.yaml:
# config/services.yaml
services:
App\EventListener\RequestListener:
arguments:
$serviceLocator: !tagged_locator { tag: 'controller.service_arguments' }
Then in the event listener, add the service locator argument and fetch the fully configured controllers from it:
# ...
use App\Controller\Shop\HomepageController;
use Symfony\Component\DependencyInjection\ServiceLocator;
class RequestListener
{
# ...
private ServiceLocator $serviceLocator;
public function __construct(
# ...
ServiceLocator $serviceLocator
) {
# ...
$this->serviceLocator = $serviceLocator;
}
public function onControllerRequest(ControllerEvent $event)
{
# ...
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = $this->serviceLocator->get(HomepageController::class);
# ...
break;
default:
break;
}
}
# ...
}
}
If you dump any controller you will see that the container is set. Same will go for additionnal service that you autowire from the constructor.
I'm using sumfony 3.3.10, I have installed a fresh project of symfony and I added knpMenuBundle using this command,
composer require knplabs/knp-menu-bundle "^2.0"
Now I followed everything exactly as mentioned here http://symfony.com/doc/master/bundles/KnpMenuBundle/menu_builder_service.html
and added this line {{ knp_menu_render('main') }} in default/index.html.twig file.
Now when I execute the project, its showing me this error,
[InvalidArgumentException]
Menu builder services must be public but "app.menu_builder" is a private service.
config.yml
knp_menu:
# use "twig: false" to disable the Twig extension and the TwigRenderer
twig:
template: KnpMenuBundle::menu.html.twig
# if true, enables the helper for PHP templates
templating: false
# the renderer to use, list is also available by default
default_renderer: twig
MenuBuilder.php
<?php
namespace AppBundle\Menu;
use Knp\Menu\FactoryInterface;
class MenuBuilder
{
private $factory;
/**
* #param FactoryInterface $factory
*
* Add any other dependency you need
*/
public function __construct(FactoryInterface $factory)
{
$this->factory = $factory;
}
public function createMainMenu(array $options)
{
$menu = $this->factory->createItem('root');
$menu->addChild('Home', array('route' => 'homepage'));
// ... add more children
return $menu;
}
}
services.yml
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
arguments: ["#knp_menu.factory"]
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main } # The alias is what is used to retrieve the menu
How can I resolve it. Any help is much appreciated. Thanks
I added public: true to the app.menu_builder service in services.php,
app.menu_builder:
class: AppBundle\Menu\MenuBuilder
public: true
arguments: ["#knp_menu.factory"]
tags:
- { name: knp_menu.menu_builder, method: createMainMenu, alias: main } # The alias is what is used to retrieve the menu
And everything is working fine now.
Refer : https://symfony.com/doc/current/service_container/alias_private.html#marking-services-as-public-private
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 %}
I am working on a Symfony 2.7 WebApp. One of the bundles I created includes a service that offer some user related stuff, e.g. userHasPurchases().
Problem is, that including a Twig Extesion breaks another service:
AppShopService
namespace AppShopBundle\Service;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
...
class AppShopService {
protected $user;
public function __construct(TokenStorageInterface $tokenStorage, ...) {
$this->user = $tokenStorage->getToken() ? $tokenStorage->getToken()->getUser() : null;
...
}
public function userHasPurchases(User $user) {
$user = $user ? $user : $this->user;
$result = $user...
return result;
}
}
AppShopBundle\Resources\config\services.yml
services:
app_shop.service:
class: AppShopBundle\Service\AppShopService
arguments:
- "#security.token_storage"
- ...
So far everything works fine: The AppShopServices is created with the current user and userHasPurchases() work as expected.
Now I have add a Twig Extension to be able to use userHasPurchases() within my templates:
Twig Extension
namespace AppShopBundle\Twig;
use AppShopBundle\Service\AppShopService;
class AppShopExtension extends \Twig_Extension {
private $shopService;
public function __construct(AppShopService $shopService) {
$this->shopService = $shopService;
}
public function getName() {
return 'app_shop_bundle_extension';
}
public function getFunctions() {
$functions = array();
$functions[] = new \Twig_SimpleFunction('userHasPurchases', array(
$this,
'userHasPurchases'
));
return $functions;
}
public function userHasPurchases($user) {
return $this->shopService->userHasPurchases($user);
}
}
Including Extension in AppShopBundle\Resources\config\services.yml
services:
app_shop.service:
class: AppShopBundle\Service\AppShopService
arguments:
- "#security.token_storage"
- ...
app_shop.twig_extension:
class: AppShopBundle\Twig\AppShopExtension
arguments:
- "#app_shop.service"
tags:
- { name: twig.extension }
After icluding the Twig Extension, AppShopService and its method userHasPurchases does not work any more. Problem is, that the constructor of AppShopService does not set user anymore since $tokenStorage->getToken() now returns null.
How is this possible? I have changed nothing except including the Twig Extension. As soon as I remove the Twig Extension from services.yml everything works correctly again.
My only guess is, that the creation fo the Twig Extension is done before any security. But why?
Any idea what might be wrong here?
don't interact with the tokenStorage in the constructor but only in the userHasPurchases method.
namespace AppShopBundle\Service;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
...
class AppShopService {
protected $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage, ...) {
$this->tokenStorage = $tokenStorage;
}
public function userHasPurchases(User $user) {
$user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
$result = $user...
return result;
}
}
Hope this help
I use a service to get a basic search form on my website. I need to set the action form to a search result page. I can set the form action to be a specific url (/search) but can't use a "generateUrl"-like method in a yaml.
I want to be able to test my form in dev environment /app_dev.php as well as in prod environment. Any suggestion or idea?
services.yml
parameters:
form.search.default.value: "search for things"
services:
app_bundle.form.type.search:
class: AppBundle\Form\SearchType
arguments: [AppBundle\Entity\Search]
tags:
- { name: form.type, alias: tab_search }
app_bundle.form.search:
factory_method: create
factory_service: form.factory
class: Symfony\Component\Form\Form
arguments:
- tab_search
- #app_bundle.form.entity.search
- { action: /search } # I'd like something similar to $this->generateUrl("search")
app_bundle.form.entity.search:
class: AppBundle\Entity\Search
arguments: [%form.search.default.value%]
DefaultController.php
/**
* #Route("/", name="homepage")
* #Template()
*/
public function indexAction(Request $request)
{
$form = $this->get('app_bundle.form.search');
// [...]
return array('search' => $form->createView());
}
Untested, but I've done something like this before with this code:
protected $action;
public function setAction($action)
{
$this->action = $action;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// rest of form configuration
$builder->setAction($this->action);
}
Then in your services.yml:
services:
app_bundle.form.type.search:
class: AppBundle\Form\SearchType
arguments: [AppBundle\Entity\Search]
calls:
- [setAction, ["%app_bundle.form.type.search.action%"]]
tags:
- { name: form.type, alias: tab_search }
And then set the app_bundle.form.type.search.action value in parameters somewhere.
If you want to allow the method to be overridden but set a default, set the action in setDefaultOptions and then override from the controller or a listener.