I have This folder structure for my mvc project:
application
---admin
--------controller
--------model
--------view
--------language
---front
--------controller
------------------BlogController.php
--------model
--------view
--------language
core
public
I work With symfony router library and route my url like this:
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog', array(
'_controller' => [application\front\controller\BlogController::class, 'index']
)));
return $routes;
BlogController.php is:
namespace application\front\controller;
class BlogController extends Controller
{
/**
* Construct this object by extending the basic Controller class
*/
public function __construct()
{
parent::__construct();
}
public function index()
{
echo 'fine';
}
}
But In Action I can't see Any output. I think syfony can find my controller. Can I fix this problem ?!
You didn't define which symfony version you are using. But checking from similar example on https://symfony.com/doc/current/routing/custom_route_loader.html for latest one (4.1), it looks like the controller value should be a string instead of an array (I haven't needed to use this kind of 'manual' route definition myself so I don't really know whether the constructor supports several different ways of defining the controller value - meaning that this is more of an educated guess what your issue could be :)).
$routes->add('blog_list', new Route('/blog', array(
'_controller' => 'application\front\controller\BlogController::index'
)));
Related
I need to be able to use a function (redirect with some parameters) from different controlers of my application.
I need to use $this->_helper->redirector($param1, $param2), and declare it just one time somewhere. Then I'll put this function in others controllers. If one day I modify the function, I don't need to modify it in each controller.
What I'm looking for is an equivalent of Symfony's services I guess.
Thanks for help :) .
What you 're asking for is called controller plugin in Laminas or Zend. You can code your own controller plugin, that you can use in every controller you want.
<?php
declare(strict_types=1);
namespace Application\Controller\Plugin;
use Laminas\Mvc\Controller\Plugin\AbstractPlugin;
class YourPlugin extends AbstractPlugin
{
public function __invoke($param1, $param2)
{
// your logic here
}
}
You have nothing more to do as to mention this plugin in your module.config.php file.
'controller_plugins' => [
'aliases' => [
'doSomething' => \Application\Controler\Plugin\YourPlugin::class,
],
'factories' => [
\Application\Controller\Plugin\YourPlugin::class => \Laminas\ServiceManager\Factory\InvokableFactory::class,
]
]
If you want to use some dependencies in your controller plugin, you can write your own factory for your plugin and add that dependencies via injection.
As your new plugin is mentioned in the application config, you can call your plugin in every controller you want.
<?php
declare(strict_types=1);
namespace Application\Controller;
class YourController extends AbstractActionController
{
public function indexAction()
{
$this->doSomething('bla', 'blubb');
}
}
Please do not use traits as a solution for your issue. Laminas / Zends already ships a redirect controller plugin. Perhaps a ready to use solution is already there or you can extend the redirect controller plugin ...
This question has been discussed many times here, here or here but no elegant solutions were mentioned.
One particular use case would be to allow to load and route old PHP files with Laravel. I am for instance migrating a very old (> 20 years) code base into Laravel and most pages are regular PHP files that I would like to render into a particular Blade template.
To do this it would be elegant to do:
Router::php('/some/route/{id}', base_path('legacy/some/page.php'));
Behind the scenes all I need is to pass the captured variables to the PHP page, evaluate and grab the content of it and eventually return a view instance.
As Laravel claims itself to be a SOLID framework, I thought extending the Router is trivial so I wrote this:
namespace App\Services;
class Router extends \Illuminate\Routing\Router
{
public function php($uri, $filename, $template='default') {
...
return view(...
}
}
Then I tried to extend my Http Kernel with this:
namespace App\Http;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use App\Services\Router;
class Kernel extends HttpKernel
{
public function __construct(Application $app, Router $router) {
return parent::__construct($app, $router);
}
}
But it is not working it seems the Application is building the Kernel with the wrong dependency. In Application#registerCoreContainerAliases I see the core alias router is hard coded and since this method is called in the Application's constructor, I am doomed.
The only solution that remains is to override the router before loading the Kernel as follow:
$app = new Application($_ENV['APP_BASE_PATH'] ?? dirname(__DIR__));
$app->singleton('router', \App\Services\Router::class);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
But this looks a bit ugly. Is there a better way to achieve this?
Since the Router class is macroable, you may be able to do something like:
Router::macro('php', function ($uri, $filepath) {
return $this->addRoute(['GET', 'POST', etc...], $uri, function () use ($filepath) {
// here you might use the blade compiler to render the raw php along with any variables.
//
// See: https://laravel.com/api/5.7/Illuminate/View/Compilers/Concerns/CompilesRawPhp.html
//
$contents = file_get_contents($filepath);
// return compiled $contents...
});
});
I want to assign generic variables to the view renderer outside the controller action.
I'd prefer to handle this in the module class bootstrap.
My question is, how do I create a view model in the module class bootstrap that can be shared with the controller .
My end result is to have the ability to add variables to the view model before we create a new instance of one inside the controller action.
Here's something I started on, however i cannot add variables to the declared viewmodel and have it persist to the controller's new instance of a view model.
Is there a way to create a view model and have it set as the renderer before dispatch.
Here's something i started but if i can get it in the module class bootstrap instead id prefer that.
I dont think this works though.
class BaseController extends AbstractActionController
{
protected $viewModel;
public function onDispatch(MvcEvent $e)
{
$this->viewModel = new ViewModel([
'module' => 'modulename',
'controller' => 'controllername',
'action' => 'actionname'
]);
parent::onDispatch($e);
}
}
In the Module.php you have access to the event object.
In this event you can set some variables like this :
$event->getViewModel()->setVariable('isAdmin', $isAdmin);
$event->getViewModel()->setVariable('var', $var);
$event->getViewModel()->setVariable('form', $form);
$event->getViewModel()->setVariable('uri', $uri[0]);
If you want to test more you can also do :
$widgetTemplate = new ViewModel();
$widgetTemplate = $widgetTemplate->setTemplate('widget/container');
$event->getViewModel()->addChild($widgetTemplate, 'widget');
Those variables are available in your layout.phtml. I didn't test if it's the same viewModel available in your controller, give me a feedback if you test this solution.
For a variable defined in module.php, you can also use the Zend\Container\Session component to modify it in controllers
Original Question
I've read every page of the "book" about service containers, and I'm still baffled because things seem to randomly not work nearly every time I try to use $this->container. For example, I'm building a form in my custom bundle controller following the instructions.
My controller extends the base controller as usual:
namespace Gutensite\ArticleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Gutensite\ArticleBundle\Entity\Article;
class AdminEditController extends Controller
{
public function indexAction() {
$content = new Article();
$form = $this->createFormBuilder($content)
->add('content', 'text');
// same issue with the shortcut to the service which I created according the instructions
// $form = $this->createForm('myForm', $myEntity)
//...more code below...
}
}
This produces an error:
Fatal error: Call to a member function get() on a non-object in /vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php on line 176
If we look at that file at that line number we see Symfony's code:
public function createFormBuilder($data = null, array $options = array())
{
return $this->container->get('form.factory')->createBuilder('form', $data, $options);
}
So WHY is symfony's own controller NOT able to access the container->get() function?!
What am I doing wrong?
Along these same lines, I can't figure out why sometimes I can't access the container via $this->container in my own controller (if extend the framework controller or if I reference it by passing it in the construct, etc). It seems random...
Background of Project and Structure of Code
I am building a CMS that has user's routes (URLs) stored in a database. So I have one route defined which directs all requests to my main CMS Controller:
gutensite_cms_furl:
# Match Multiple Paths (the plain / path appears necessary)
path: /
path: /{furl}
defaults: { _controller: GutensiteCmsBundle:Init:index }
# Allow / in friendly urls, through more permissive regex
requirements:
furl: .*
The InitController looks up the requested URL and gets the correct Route entity which points to a View entity that defines which Bundle and Controller to load for specific page type being requested, e.g. the route for /Admin/Article/Edit points to content type that is associated with the Article bundle and AdminEdit controller, which then creates a new object for this content type (Gutensite\ArticleBundle\Controller\AdminEditController.php) and executes the required functions. This then injects the necessary variables back into the main ViewController which gets passed to the template to be rendered out to the page.
This main controller extends symfony controller and I have confirmed that the container is accessible in this controller, e.g. $this->container->get('doctrine') works.
// Gutensite\CmsBundle\Controller\InitController.php
namespace Gutensite\CmsBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Gutensite\CmsBundle\Entity;
class InitController extends Controller
{
public function indexAction(Request $request, $furl)
{
// Confirm container is accessible (yes it is)
$test = $this->container->get('doctrine');
// Look up the View Entity based on the Route Friendly URL: $furl
$viewController = $this->container->get('gutensite_cms.view');
$viewController->findView($furl, $siteId);
// Load the Requested Bundle and Controller for this View
$path = $viewController->view->namespace_controller."\\".$viewController->view->controller;
$content = new $path;
// Execute the main function for this content type controller, which adds variables back into the $viewController to be passed to the template.
$content->indexAction($viewController);
return $this->render(
$viewController->view->bundle_shortcut.'::'.$viewController->view->getTemplatesLayout(),
array('view' => $viewController)
);
}
}
FYI, the ViewController is defined as a global service:
services:
gutensite_cms.view:
class: Gutensite\CmsBundle\Controller\ViewController
arguments: [ "#service_container" ]
And then Below is a simplified version of the Gutensite/CmsBundle/Controller/ViewController.php
namespace Gutensite\CmsBundle\Controller;
use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class ViewController
{
protected $container;
public $routing;
public $view;
public function __construct(Container $container) {
$this->container = $container;
}
public function findView($furl, $siteId=NULL) {
$em = $this->container->get('doctrine')->getManager();
$this->routing = $em->getRepository('GutensiteCmsBundle:Routing\Routing')->findOneBy(
array('furl'=>$furl, 'siteId'=>$siteId)
);
if(empty($this->routing)) return false;
// If any redirects are set, don't bother getting view
if(!empty($this->routing->getRedirect())) return FALSE;
// If there is not view associated with route
if(empty($this->routing->getView())) return FALSE;
$this->view = $this->routing->getView();
$this->setDefaults();
}
}
Back in the InitController.php we retrieved the view object and loaded the right bundle and controller function. In this case it loaded `Gutensite\ArticleBundle\Controller\AdminEditController.php which is where we lose access to the service container.
namespace Gutensite\ArticleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Gutensite\ArticleBundle\Entity\Article;
class AdminEditController extends Controller
{
protected $request;
public function __contstruct(Request $request) {
$this->request = $request;
}
public function indexAction($view)
{
// TEST: Test if I have access to container (I do not)
//$doctrine = $this->container->get('doctrine');
// This loads createForm() function from the Symfony Controller, but that controller then doesn't have access to container either.
$form = $this->createForm('view', $content);
}
}
More Specific Question
So I ASSUMED that if you extend the Symfony Controller, which itself extends ContainerAware, that the object would be "aware of the container". But that evidently is not the case. And that is what I need to understand better. I assume somehow the container has to be injected manually, but why? And is that the standard method?
Ok. Your assumption that merely making an object ContainerAware will automatically cause the container to be injected is incorrect. The PHP new operator does not know anything about dependencies. It's the job of the dependency injection container to take care of automatically injecting stuff. And of course your are not using the container to create your controllers.
Easy enough to fix:
$path = $viewController->view->namespace_controller."\\".$viewController->view->controller;
$content = new $path;
$content->setContainer($this->container);
$content->indexAction($request,$viewController);
I don't really follow your flow. The view stuff seems kind of backwards to me but I trust you can see where and how the container is injected into a Symfony controller. Don't do anything in the controller's constructor which relies on the container.
===============================================================
Instead of using the new operator, you could use the service container.
$contentServiceId = $viewController->view->contentServiceId;
$content = $this->container->get($contentServiceId);
$content->indexAction($request,$viewController);
Instead of having you view return a class name, have it return a service id. You then configure your controller in services.yml and off you go. This cookbook entry might help a bit: http://symfony.com/doc/current/cookbook/controller/service.html
=============================================================
All ContainerAware does is to make the Symfony DependencyInjectContainer inject the container. Nothing more. Nothing less. You might conside reading through here: http://symfony.com/doc/current/components/dependency_injection/index.html just to get basic idea of what dependency injection and dependency injector container are all about.
I've been trying to figure it out by myself reading online and applying tons of answers form here, but to no avail unfortunately.
I have two Modules on my zf2 application, one called Services and one called Agent.
Now, in my Services module everything seems to work fine, I can get my serviceLocator, hence my configuration, and work with it. In my Agent Module's controller however, I don't seem to be able to do the same.
This is part of my AgentController:
use Zend\Mvc\Controller\AbstractActionController;
class AgentController extends AbstractActionController
{
protected $serviceLocator;
public function ValidateAction()
{
$this->serviceLocator = $this->getServiceLocator()->get('config');
//... Using the config
}
}
In my module.cofig.php I have the following:
'controllers' => array(
'invokables' => array(
'Agent\Controller\Agent' => 'Agent\Controller\AgentController',
),
),
I have tried many solutions: changing and adding methods to the Module.php, changing the module.config etc.. Where am I wrong?
Thanks,
Andrea
The class variable $this->serviceLocator is used by the controller class to hold the service locator instance. In your example you assigning the config array to this variable (thus replacing the service locator instance with an array). Subsequent calls to $this->getServiceLocator() will then return the config array instead of the service locator object, which is the likely cause of the error you're getting.
I'd suggest either using a local variable instead:
public function ValidateAction()
{
$config = $this->getServiceLocator()->get('config');
//... Using the config
}
or assigning to a class variable with a different name:
public function ValidateAction()
{
$this->config = $this->getServiceLocator()->get('config');
//... Using the config
}