I have a base controller for most of the controllers in my app like so:
class BaseController extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
/**
*
* #Route("/")
*/
public function indexAction($partial = false)
{
$this->partial = $partial;
$this->currentAction = 'index';
return $this->r();
}
}
This is accompanied by a pack of templates that can be either full html pages with a layout or just the content. This is done by a line in the templates:
{% extends this.partial ? "SomeProject:_base:partial.html.twig" : "SomeProject::layout.html.twig" %}
(where this is a the controller reference).
These templates can then be rendered in other controllers without the duplication of layout via.
{% render 'SomeProject:SomeController:index' with { "partial":true } %}
My problem with this approach is:
Every action that needs to be partial controller must have a $partial argument. Since most actions have the potential to be partial, all the controllers must be sprinkled with it.
Every potentially partial action must have the $this->partial = $partial line, which can be easily forgotten.
It there a cleaner way by using some Symfony or Twig magic (overriding the render tag etc. ). For getting rid of the above problems?
I have overridden the render method in most of my controllers in order to inject some standard variables into my templates. It seems to work well but all it will do for you will be to make rendering a template from a controller a bit easier since you won't have to explicitly pass $this to the template.
I am not so sure passing a reference to the controller is such a good idea. For your example at least, Just passing partial would seem to be all you need.
Do you really need to give your templates this partial capability? Seems easier to just have two templates (one for the page and one for the partial). But perhaps your use case requires it.
After some research and digging around in the internals I've come up with an elegant solution.
The answer is to build an Event Listener (covered in Symfony2 Docs). More precisely: a Controller Listener with the meat of the class looking like below. This allows for transparent handling of partial without any changes made to the Controller code.
class ControllerListener
{
/**
*
* #param BaseController $ctrl
* #param Request $request
* #return BaseController
*/
public function injectPartial($ctrl,Request $request)
{
if ($ctrl instanceof BaseController)
{
if ($request->get("partial",false))
$ctrl->setPartial($request->get ("partial"));
}
return $ctrl;
}
public function onKernelController(FilterControllerEvent $event)
{
$ctrl = $event->getController();
$ctrl[0] = $this->injectPartial($ctrl[0], $event->getRequest());
$event->setController($ctrl);
}
}
Related
How can I set different layouts for different modules in Zend Framework 3 instead of using single one same layout template all around the site?
Zend Framework - Issue
> You may want to alter the layout based on the current
module. This requires (a) detecting if the controller matched in
routing belongs to this module, and then (b) changing the template of
the View Model.
The place to do these actions is in a listener. It should listen
either to the “route” event at low (negative) priority, or on the
“dispatch” event, at any priority. Typically, you will register this
during the bootstrap event.
namespace Content;
class Module
{
/**
* #param \Zend\Mvc\MvcEvent $e The MvcEvent instance
* #return void
*/
public function onBootstrap($e)
{
// Register a dispatch event
$app = $e->getParam('application');
$app->getEventManager()->attach('dispatch', array($this, 'setLayout'));
}
/**
* #param \Zend\Mvc\MvcEvent $e The MvcEvent instance
* #return void
*/
public function setLayout($e)
{
$matches = $e->getRouteMatch();
$controller = $matches->getParam('controller');
if (false === strpos($controller, __NAMESPACE__)) {
// not a controller from this module
return;
}
// Set the layout template
$viewModel = $e->getViewModel();
$viewModel->setTemplate('content/layout');
}
}
The manual says above, but if you want to use these code, you'll need:
// module/Content/config/module.config.php
return [
/* whatever else */
'view_manager' => [
'template_map' => [
'content/layout' => __DIR__ . '/../view/layout/layout.phtml'
]
]
];
Shortly, when all modules initialized(bootstrap) successfully, Zend will call onBootstrap() automatically, which bind 'dispatch' event to setLayout() method, where the controller name is matched with current module's namespace, and if success, use setTemplate() to set layout template.
e.g.
Module/Namespace: Content,
Controller: Content\Controller\MatchMeController,(success!)
Controller: Other\Controller\DontMatchMeController,(fail!)
But there is a tiny drawback: setLayout() use
strpos(controller, __NAMESPACE__) === false
to identify current module, but what if I had a ContentController in some other module? So use
strpos(controller, __NAMESPACE__) !== 0
instead.
----------
Zend Framework - Issue
The manual is quite detailed, it also mentions lots of other things like set different layouts for different controllers (or actions).
You can switch between layouts for a particular controller's action by using the following code:
// A controller's action method that uses an alternative
// layout template.
public function indexAction()
{
//...
// Use the Layout plugin to access the ViewModel
// object associated with layout template.
$this->layout()->setTemplate('layout/layout2');
//...
}
In addition to the Layout controller plugin, there is the Layout view
helper which provides the same capabilities. With the Layout view
helper, you can, for example, switch layout from the "static" page
which has no specific controller action.
Setting Layout for All Actions of a Controller
If all action methods of a controller class need to use the same alternative layout, you can override the onDispatch() method of the AbstractActionController class and call the setTemplate() method there, as shown in the example below:
// Add this alias in the beginning of the controller file
use Zend\Mvc\MvcEvent;
// ...
class IndexController extends AbstractActionController
{
/**
* We override the parent class' onDispatch() method to
* set an alternative layout for all actions in this controller.
*/
public function onDispatch(MvcEvent $e)
{
// Call the base class' onDispatch() first and grab the response
$response = parent::onDispatch($e);
// Set alternative layout
$this->layout()->setTemplate('layout/layout2');
// Return the response
return $response;
}
}
Reference
I have seen a few similar questions to mine, with the common answer being to use a view composer. I have a HomeController that shows articles from a database by passing query data to an associated view, which works see this image link
As you can see there is a nav bar, which is generated by the master layout, layout.master.
For each title in the navigation I am trying to show each article for that section via a for loop which generates the links.
My code is this.
public function index()
{
$loans_articles = Article::byDepartment('Loans')->get();
$digital_articles = Article::byDepartment('Digital')->get();
$consulting_articles = Article::byDepartment('Consulting')->get();
return view('welcome',
[
'loans_articles' => $loans_articles,
'digital_articles' => $digital_articles,
'consulting_articles' => $consulting_articles,
]);
}
As you can see I'm returning this data to the welcome blade.
In my nav bar I tried
#if(count($loans_articles) > 0)
#foreach($loans_articles as $ls)
<!--for each loop which grabs the articles with department Loans-->
<li>{{ $ls->title }}</li>
#endforeach
#endif
But as soon as you navigate away from the home page the nav bar doesn't know what $loans_article is.
Is there a clean way to pass this data to the master blade navigation without sending the same data to every subview?
The way I tend to achieve this is by making a variable available to every view in this way:
All of your controller should extend a base controller, which is usually located in app/Http/Controllers/Controller.php. Inside this controller you can put some code that will be used by all extending controllers.
In this base controller you can make a variable available to all views, like this...
class Controller extends BaseController
{
public function __construct()
{
// Load your objects
$loans_articles = Article::byDepartment('Loans')->get();
// Make it available to all views by sharing it
view()->share('loans_articles', $loans_articles);
}
}
All of your controllers must extend this controller for this to work.
If any of your controllers have their own constructors, you must also make sure to call parent::__construct() to ensure the above code is run. If your controllers don't have their own constructors, you can omit calling parent::__construct().
public class HomeController extends Controller
{
public function __construct()
{
parent::__construct();
// Your constructor code here..
}
}
This way you should be able to use $loans_articles in all of your views.
You must use View Composer to achieve what you are trying to do :
https://laravel.com/docs/5.2/views
if you would like to share data with all of your blade templates, a simple way is trough
appServiceProvider.php and boot method.
you can find this file in app/Providers/AppServideProvider.php and do the changes like bellow:
namespace App\Providers;
use Illuminate\Support\Facades\View;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
View::share('key', 'value');
}
}
after that you can access to the value in all blade files using {{ $key }}
note: do not forget to use Illuminate\Support\Facades\View in top of the class
<?php $contact = DB:table('tbl_contact')->get(); ?>
Use this code in master.blade.php in top section
And
Use such as
<a href='{{ $contact[0]->fbLink ? $contact[0]->fbLink : " " }}'>
I've used another class as Dependency Injection is it good to work around or I've messed up the OOP way.
Helper.php
class Helper {
public function getModulePermission($role_id, $module, $type) {
// my work code
}
}
DesignationController.php
use App\Helpers\Helper;
class DesignationController extends Controller {
protected $designation;
protected $helper;
/**
* #param DesignationContract $designation
* #param Helper $helper
*/
public function __construct(DesignationContract $designation, Helper $helper) {
$this->designation = $designation;
$this->helper = $helper;
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index(Request $request) {
$permission = $this->helper->getModulePermission($request->id, 'Designation', 'view');
if ($permission) {
return true;
} else {
return view('errors.forbidden');
}
}
So I've a class named Helper which can be accessed within each and every controller for checking permissions but I thought that I've messed up the OOP functionality over here. Is it good to work like it as or I need to create an Interface instead of class
Those are two different OOP concepts. the interface forces whoever class implement it to implement functions stated in the interface (called function signature). So for multiple classes implement the same interface, you will end up with multiple classes implement the same set of functions (wither it is the same function body or not).
The second concept is called, dependency injection or inversion of control in some cases. you inject a class either via class constructor or via setters and you call certain function provided by the injected class. Here you will have the same function called by multiple classes which is good for less modification by using common (injected) class, easier unit-testing (you can mock objects easily), more modular code (you can inject different class if you want different functionality).
So the current situation is good enough but it all depends in what you want to do which stated above.
I don't like the name, it speaks nothing to me or gives me an idea that this class can help me get the permissions of the module. Moreover, with a name like this, one can simply put another method, like lets say Helper::login($id) or you name it, which will instantly break the single responsibility principle (laravel controllers do that anyway).
The injection is relatively OK, perhaps a middleware class would be better place to do that.
Just imagine, I have a controller with several actions in it.
And to reach each action I have to define it strictly in routing.yml file like this:
admin_edit_routes:
pattern: /administrator/edituser
defaults: { _controller: MyAdminBundle:Default:edituser }
admin_add_routes:
pattern: /administrator/adduser
defaults: { _controller: MyAdminBundle:Default:adduser }
And I can have plenty of such pages. What I want to achieve is to define necessary action in my URI, just like Kohana routes do.
I mean, I want simply to use one route for all actions:
(code below is not valid, just for example)
admin_main_route:
pattern: /administrator/{action}
defaults: { _controller: MyAdminBundle:Default:{action} action:index}
It will pass all requests to /administrator/{anything} to this route, and after that, according to the action, stated in the uri, the needed action is gonna to be called.
If action is not stated in the uri, then we got thrown to index action.
Is it possible in Symfony 2 in any ways and if yes, then how?
Thanking in advance.
This doesn't feel right to do so. By exposing your controller API (methods) "on the fly" directly via GET method, you open your application to security vulnerabilities. This can also easily lead to unwanted behaviours.
Moreover, how would you generate routes in twig for example ? By giving explicit constants tied to the method names?
{{ path('dynamic_route', { 'method': 'addUser' }) }}
This is not how Symfony works nor what it recommands. Symfony is not CodeIgniter nor Kohana. You don't need a second "Front Controller"
If this is a laziness matter, you can switch your routing configurations to annotations. And let Symfony auto-generate the routes you need, with minimal efforts.
namespace Acme\FooBundle\Controller;
/**
* #Route("/administrator")
*/
class DefaultController extends Controller
{
/**
* #Route
*/
public function indexAction(Request $request);
/**
* #Route("/adduser")
*/
public function addUserAction(Request $request);
/**
* #Route("/edituser")
*/
public function editUserAction(Request $request);
/**
* #Route("/{tried}")
*/
public function fallbackAction($tried, Request $request)
{
return $this->indexAction($request);
}
}
The fallbackAction methods, returns any /administrator/* route which does not exist to the indexAction content.
To sum up, I advise not to use dynamic routing "on the fly" directly to method names.
An alternative would be to create a command which will generate routes once executed.
Well, maybe that's not the best way to do that, but it works. Your dynamicAction accepts action string parameter and checks if such action exists. If so, it executes given action with params.
/**
* Default Controller.
*
* #Route("/default")
*/
class DefaultController extends Controller
{
/**
* #Route("/{action}/{params}", name="default_dynamic")
*/
public function dynamicAction($action, $params = null)
{
$action = $action . 'Action';
if (method_exists($this, $action)) {
return $this->{$action}($params);
}
return $this->indexAction();
}
}
I'm curious if someone have any other ideas :>
Take for example the following controller/action:
public function indexAction()
{
return $this->render('TestBundle:TestController:index.html.twig');
}
I would like to write the template expression (or whatever it's name is) this way:
public function indexAction()
{
return $this->render('*:TestController:index.html.twig');
}
So that symfony knows I'm looking for a template in this very bundle. Having to write the whole Owner + Bundle for every template/action/repository I want to refer is very annoying. Even more so considering most of the time I refer to actions and templates in the same bundle.
NOTE: I know templates can be put at the app level and be refernced like this:
'::index.html.twig'
But that is not what I need.
It's possible with a bit of custom code.
Basically, you want to override the controller's render() method and include logic to fetch the name of the current bundle.
Note that instead of my controllers extending Symfony\Bundle\FrameworkBundle\Controller\Controller, they extend a custom controller (which then extends Symfony's controller). This allows you to conveniently give the controller more ability by adding your own methods.
Ex:
MyBundle\Controller\MyController\ extends MyCustomBaseController which extends Symfony\Bundle\FrameworkBundle\Controller\Controller.
So, in my custom controller I have these two methods:
public function render($view, array $parameters = array(), Response $response = null) {
$currentBundle = $this->getCurrentBundle();
$view = str_replace('*', $currentBundle, $view);
return parent::render($view, $parameters, $response);
}
public function getCurrentBundle() {
$controller = $this->getRequest()->attributes->get('_controller');
$splitController = explode('\\', $controller);
return $splitController[1];
}
Take a look at render(). It fetches the current bundle name and uses it to build the $view variable. Then it just calls parent::render() and it's as if you had manually defined the bundle name in the render statement.
The code here is very simple, so you should be able to easily extend it to do other things, such as allow you to also avoid typing the controller name.
Important: If you do use a custom controller, make sure you use Symfony\Component\HttpFoundation\Response, otherwise PHP will complain that the method signatures for render() don't match.