I'm trying to create a menu for my page with ezpublish5. I followed this tutorial http://partialcontent.com/Code/working-with-ez-publish-5-subrequests.
I'm pretty new to Symfony, but I have my own bundle there and running, at least it is using my own pagelayout.html.twig.
I understand, what routing does, but I as far as I'm concerned in order to create a menu I need something else, probably a service, so I can do this in my twig-template
{{ render( controller( "myMenuController:myFunction" ) ) }}
So I add this to my my\Bundle\Resources\config\services.yml
parameters:
my_root.menucontroller.class: my\Bundle\Controller\MenuController
services:
my_root.controller:
class: %my_root.menucontroller.class%
arguments: [#ezpublish.view_manager]
calls:
- [setContainer, [#service_container] ]
myalias:
alias: my_root.controller
When I open it in the browser it says:
You have requested a non-existent service "myalias".") in "{% extends "Bundle::pagelayout.html.twig" %}
I checked other repos of ezp5 installations on github, they have pretty much the same yml-setup.
I also realized, that when I make syntax errors on purpose in my services.yml it (while leaving out the call to the controller in the template) it doesn't change anything.
Also I realized, that when I do the same in my\Bundle\DependencyInjection\myBundleExtension.php (which is supposed to load my services.yml file) it doesn't happen anything either.
So I'm getting the feeling something with bundle-setup is wrong, that somehow not everything is loaded correctly. But what could it be?
Somewhere in the docu of symfony2 it says that the load-method in the Bundle\DependencyInjection\xyzExtension.php gets called automatically.
Would anyone have an idea of what could possibly be wrong with my setup? I'm really running out of ideas.
And for the ezpublish5 part.. is this really the best way to create a menu right now?
It does seem like your services are not being loaded.
Symfony relies on a naming convention to load xyzExtension.php. It's tripped me up a few times. You can stick a die statement in xyzExtension.load just to verify it is indeed being called.
If it is not being called than you can either change the extension class name to meet the convention or do what I do and just override the convention in your bundle class.
namespace Cerad\Bundle\GameV2Bundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Cerad\Bundle\GameV2Bundle\DependencyInjection\GameExtension;
class CeradGameV2Bundle extends Bundle
{
public function getContainerExtension()
{
return new GameExtension();
}
}
And of course xyzExtension should have something like:
namespace Cerad\Bundle\GameV2Bundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Config\FileLocator;
class GameExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
public function getAlias() { return 'cerad_game_v2'; }
}
From the command line you can verify your service is getting picked up:
app/console container:debug : grep my_root
Related
I've just started working my way through the symfony2 book.
I wonder why do we named our controller's functions Action:
public function [something]Action() { // ...
In everyone example in the book thus far and all code I see online Action is the function name. There's any reason for it?
This works perfectly:
<?php
// src/AppBundle/Controller/LuckyController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class LuckyController extends Controller{
/**
* #Route("/lucky/number/{count}")
*/
public function countTESTING($count){
return new Response(
'<html><body>I DONT HAVE TO CALL THIS somethingACTION</body></html>
');
}
}
?>
I've tried googline this but I see no mention or reasoning as to why. Could someone explain why we use that suffix?
It's just a conventions. You can use those suffixes, but you can also do without it.
If you have
public function somethingAction()
in your controller, you can refer it in the routing configuration in this way:
index:
path: /path_for_something
defaults: { _controller: AppBundle:Index:something }
The _controller parameter uses a simple string pattern called the logical controller name. So, AppBundle:Index:something means:
Bundle: AppBundle
Controller class: IndexController
Method name: somethingAction
But, you can also do this without this feature. Symfony is very flexible, and it does not force you to do almost anything. It is just one of many ways you have to do the same thing.
If you adopt this convention, it's easier for you to understand which action do you have in your controller, it's easy for other developer to understand your code, and it's easier for symfony2 to locate your actions/controllers inside your bundle, so that you can also overriding controllers. This is the best practice.
But if you don't want these benefits, you can using its fully-qualified class name and method as well:
index:
path: /something
defaults: { _controller: AppBundle\Controller\IndexController::indexAction }
But, as the documentation say:
if you follow some simple conventions, the logical name is more
concise and allows more flexibility.
No it wasn't just a naming convention. It was used to execute some code before or after every controller 'action' method. Like checking is user has logged in.
It is based on magic __call function which is executed for a non-existent or non-public method call.
$controller = new Posts();
$controller->index();
class Posts
{
public function __call($name, $args)
{
//run code before
call_user_func_array()[$this, "$nameAction"], $args);
//run code after
}
public function indexAction()
{
}
}
You HAVE TO name your actions
public function somethingAction(){}
because your routes point to a controller, and the action you want to call.
you can also have private functions in your controller, that you will only name
private function something(){}
I say that using yml to configure controllers, i dont believe its different when using annotations, but my advise is to use yml for configuring controllers... really !
I would like to extend Laravels Router class (Illuminate\Routing\Router) to add a method I need a lot in my application.
But sadly I can't get this to work. I already extended other classes successfully so I really have no idea where my wrong thinking comes from.
Anyway, right into the code:
<?php
namespace MyApp\Extensions;
use Illuminate\Routing\Router as IlluminateRouter;
class Router extends IlluminateRouter
{
public function test()
{
$route = $this->getCurrentRoute();
return $route->getParameter('test');
}
}
So as you see I want to get the parameter set by {test} in routes.php with a simple call like:
Router::test();
Not sure how to go on now. Tried to bind it to the IOC-Container within my ServiceProvider in register() and boot() but I got no luck.
Whatever I try I get either a constructor error or something else.
All solutions I found are too old and the API has changed since then.
Please help me!
edit:
I already tried binding my own Router within register() and boot() (as said above) but it doesn't work.
Here is my code:
<?php
namespace MyApp;
use Illuminate\Support\ServiceProvider;
use MyApp\Extensions\Router;
class MyAppServiceProvider extends ServiceProvider {
public function register()
{
$this->app['router'] = $this->app->share(function($app)
{
return new Router(new Illuminate\Events\Dispatcher);
}
// Other bindings ...
}
}
When I try to use my Router now I have the problem that it needs an Dispatcher.
So I have to do:
$router = new Router(new Illuminate\Events\Dispatcher); // Else I get an exception :(
Also it simply does nothing, if I call:
$router->test();
:(
And if I call
dd($router->test());
I get NULL
Look at: app/config/app.php and in the aliases array. You will see Route is an alias for the illuminate router via a facade class.
If you look at the facade class in Support/Facades/Route.php of illuminate source, you will see that it uses $app['router'].
Unlike a lot of service providers in laravel, the router is hard coded and cannot be swapped out without a lot of work rewiring laravel or editing the vendor source (both are not a good idea). You can see its hardcoded by going to Illuminate / Foundation / Application.php and searching for RoutingServiceProvider.
However, there's no reason i can think of that would stop you overriding the router class in a service provider. So if you create a service provider for your custom router, which binds to $app['router'], that should replace the default router with your own router.
I wouldn't expect any issues to arise from this method, as the providers should be loaded before any routing is done. So overriding the router, should happen before laravel starts to use the router class, but i've not this before, so be prepared for a bit of debugging if it doesn't work straight away.
So I was asking in the official Laravel IRC and it seems like you simply can't extend Router in 4.1 anymore. At least that's all I got as a response in a pretty long dialogue.
It worked in Laravel 4.0, but now it doesn't. Oh well, maybe it will work in 4.2 again.
Other packages suffer from this as well: https://github.com/jasonlewis/enhanced-router/issues/16
Anyway, personally I'll stick with my extended Request then. It's not that much of a difference, just that Router would've been more dynamic and better fitting.
I'm using Laravel 4.2, and the router is really hard coded into the Application, but I extended it this way:
Edit bootstrap/start.php, change Illuminate\Foundation\Application for YourNamespace\Application.
Create a class named YourNamespace\Application and extend \Illuminate\Foundation\Application.
class Application extends \Illuminate\Foundation\Application {
/**
* Register the routing service provider.
*
* #return void
*/
protected function registerRoutingProvider()
{
$this->register(new RoutingServiceProvider($this));
}
}
Create a class named YourNamespace\RoutingServiceProvider and extend \Illuminate\Routing\RoutingServiceProvider.
class RoutingServiceProvider extends \Illuminate\Routing\RoutingServiceProvider {
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function($app)
{
$router = new Router($app['events'], $app);
// If the current application environment is "testing", we will disable the
// routing filters, since they can be tested independently of the routes
// and just get in the way of our typical controller testing concerns.
if ($app['env'] == 'testing')
{
$router->disableFilters();
}
return $router;
});
}
}
Finally, create YourNamespace\Router extending \Illuminate\Routing\Router and you're done.
NOTE: Although you're not changing the name of the class, like Router and RoutingServiceProvider, it will work because of the namespace resolution that will point it to YourNamespace\Router and so on.
I have started to create a project using Symfony 2. I need to share data between all controllers.
I have added a base controller which extends symfony\controller and each of my controllers extends this base controller
class BaseController extends Controller
class HomeController extends BaseController
This base controller will be used for things like assigning global twig variables ( I know I can do this in the config but some of the variables will be gotten from other config files and database ).
So I thought I could reference container since Controller is container aware, however it isn't at the point I am using the functions (from constructor).
public function __construct ()
I have seen people mention passing the container in as a parameter and mention services but I have had a look and cannot figure it out. All I want to achieve is this:
public function __construct (Container $container) {
$container->get('twig').addGlobal('foo');
}
This is a common stumbling block to Symfony 2 newbies. The controller/container question has been asked hundreds of time before so you are not alone(hint).
Why doesn't your controller constructor code work?
Start by looking under vendor/symfony...FrameworkBundle/Controller/Controller.php. Hmm. No constructor there so where the heck is the container coming from? We see that Controller extends ContainerAware. That seems promising. We look at ContainerAware (the namespace helps to find where the file is) and once again, no constructor. There is however a setContainer method so we can assume that the container is injected into the controller after the constructor is called. Quite common in a dependency injection based framework.
So now we know why the constructor code fails. The container has not yet been injected. Stupid design right? Time for a different framework? Not really. Let's face it, having to have all your controllers extend a base controller just to get some twig variables set is not really the best design.
The Symfony way to execute code before the controller action is executed is to make a controller event listener. It will look something like this:
namespace Cerad\Bundle\CoreBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ModelEventListener extends ContainerAware implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(KernelEvents::CONTROLLER => array(
array('doTwig', 0), // 0 is just the priority
));
}
public function doTwig(FilterControllerEvent $event)
{
// Ignore sub requests
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) return;
$this->container->get('twig')->addGlobal('foo');
}
}
// This goes in services.yml
parameters:
cerad_core__model_event_listener__class:
Cerad\Bundle\CoreBundle\EventListener\ModelEventListener
services:
cerad_core__model_event_listener:
class: '%cerad_core__model_event_listener__class%'
calls:
- [setContainer, ['#service_container']]
tags:
- { name: kernel.event_subscriber }
So now we have the desired functionality without the need for a base controller class.
Notice also that the controller can be accessed through the event. Since the controller has been created but the action method not yet called, you could call controller methods or inject data directly into the controller. This is seldom needed. In most cases, you would add additional information to the request object which then gets injected into the controller's action method.
It's really a nice design once you get comfortable with listeners and services.
Please read carefully that question - Symfony2 passing data between bundles & controllers, try to use code included in it.
You can use service to solve your problem, for example.
If you look at the Controller class you'll se the following:
class Controller extends ContainerAware
This means you can retrieve twig from the container as simple as this:
$twig = $this->get('twig');
But I would recommend you to use custom twig extension in your case.
I'm trying to get the root dir in symfony2.
If I use:
$this->get('kernel')->getRootDir();
I get this error:
FatalErrorException: Error: Call to undefined method Test\Component\ClassLoader\DebugClassLoader::get()
How can I fix this?
Edit, seeing as this post has garnered so much attention and mine is at the top, the best way to get the root directory is to pass it in to your class as a constructor argument. You would use services.yml to do this, and in arguments:
serviceName:
class: Name\Of\Your\Service
arguments: %kernel.root_dir%
Then, the following code will have the root directory given to it when the framework instantiates it:
namespace Name\Of\Your;
class Service
{
public function __construct($rootDir)
{
// $rootDir is the root directory passed in for you
}
}
The rest of the answer below is the old, poor way of doing it without using Dependency Injection.
I want to make everyone aware that this is the Service Locator, which is an anti-pattern. Any developer should be able to see what a class, or controller, requires to function from the method signature only. Injecting a whole "container" is very generic, hard to debug and isn't the best way of doing things. You should use a Dependency Injection Container that allows you to inject specifically what you want anywhere in your application. Be specific. Check out a seriously awesome recursively instantiating dependency injection container called Auryn. Where your framework resolves your controller / action, place it there and use the container to create the controller and run the method instead. Boom! Instant SOLID code.
You're correct, the service container is accessed using $this->get('service').
However, in order to use $this->get(), you're going to need access to the get() method.
Controller Access
You gain access to this, and many other handy methods, by making sure your controller extends the base controller class that Symfony uses.
Make sure you're referencing the correct Controller base class:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloController extends Controller
{
/** The Kernel should now be accessible via the container **/
$root = $this->get('kernel')->getRootDir();
}
Service Access
If you want to access the container from a service, you're going to have to define your controller as a service. You can find more information in this post, this post and this post about how to do this. Another useful link. Either way, you now know what to look for. This post may also be useful:
use Symfony\Component\DependencyInjection\ContainerInterface;
class MyClass
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function doWhatever()
{
/** Your container is now in $this->container **/
$root = $this->container->get('kernel')->getRootDir();
}
}
In your config.yml, define your new type:
myclass:
class: ...\MyClass
arguments: ["#service_container"]
You can read more about the service container in the docs.
The parameter kernel.root_dir points to the app directory. Normally to get to the root directory, I user kernel.root_dir/../
So in controller you can use $this->container->getParameter('kernel.root_dir')."/../"
In service definition you can use:
my_service:
class: \Path\to\class
arguments: [%kernel.root_dir%/../]
The best option is to declare tour class as a service in your services.yml file:
services:
myclass:
class: Your\Class\Namespace\MyClass
arguments: ["#service_container"]
and adapt yhe constructor of you class:
use Symfony\Component\DependencyInjection\ContainerInterface
class MyClass
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
}
I'm working with SonataAdminBundle and SonataUserBundle.
SonataUserBundle registers a service sonata.user.admin.group which is automatically detected by SonataAdminBundle to set links in the admin dashboard to group CRUD operations.
How can I disable sonata.user.admin.group? I've been following that recipes in Symfony2 documentation:
How to Override any Part of a Bundle - Services and Configuration
Compiling the Container - Creating a Compiler Pass
Working with Container Parameters and Definitions
So far, I have the following code in my bundle definition to add a compiler pass:
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new CompilerPass());
}
And here it is the compiler pass:
<?php
namespace NS\Service\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class CompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$container->removeDefinition('sonata.user.admin.group');
}
}
I thought that this should work but no. Symfony is throwing an exception telling me that sonata.user.admin.group service does not exist. But it exists, and if I do $container->getDefinition('sonata.user.admin.group') the actual definition is return.
Thanks
Try marking the service as abstract and set its public property to false e.g.
#in any services.yml
services:
sonata.user.admin.group:
abstract: true
public: false
#...
Addition to completeness:
And add to the CompilerPass:
$container->getDefinition('sonata.user.admin.group')->setSynthetic(true);
You've removed the service definition but it's still used on the dashboard. That's why Symfony complains (dashboard tries to access it). It's not an optional service.
You could try to overwrite the dashboard template and avoid using the service? This way service wouldn't be called and you wouldn't have to remove it. If service is not used it's never created.
Alternative would be overloading the service with your implementation.