How to get part of the page from other controller - php

My site I have some content can be voted (+/-). It is working fine now, when all content has its own voter.
Now I'm looking for a way to create a single voting bundle with a entity(votedModel,votedId, user, vote).
Basically the bundle is ready. My problem is how to use it. I'd like to be able to do something like:
class ... extends Controller {
function showAction(Request $request,$id) {
...
$voter=new Voter('myCOntentType',$id,$userid);
...
return $this->render('...', array('voter'=>$voter->getVoter(),...))
}
}
getVoter() would create the voter view.
but I'm stacked how exactly start. I tried to call for the other controller in this manner, but can't create the voter form.
It worked with $voter=$this->forward('VoterbundleNewAction', array('id=>$id,'user'=>$user)->getContent();But this wasn't I had in mind.
I think my approach is all wrong and I may need to do this as service. I cant find my way around.

You can use include or render in your twig template to get other templates' output. So you could create a template (say, voter.html.twig) that contains the HTML for your voting system, and in the Twig, in any place where you need a voter, you could use:
{% include "AcmeVoterBundle:Voter:voter.html.twig" %}
or
{% render "AcmeVoterBundle:Voter:voter" with {"item": item} %}
In the first example, you simply include another template (see also: http://symfony.com/doc/current/book/templating.html#including-other-templates), in the latter situation you actually execute another action method of a controller and the output of that is put into your current template (see also: http://symfony.com/doc/current/book/templating.html#embedding-controllers)

Related

Dealing with Views in Phalcon Controllers

I am working on a newly created Phalcon project, and I don't really know how to actually use multiples views.
What is the entry point? I don't really know when each method in the controller is called, under which conditions, etc.
Where is the control flow defined? is it based in the name of the view? or is there a place where you can register them?
Phalcon is a bit different than other well-known PHP frameworks, in that not much is pre-configured or pre-built by default. It's quite loosely-coupled. So you have to decide where and how your control flow will work. This means that you will need to dig deeper in the documentation and also that there could be different way to achieve the same thing.
I'm going to walk you through a simple example and provide references, so you can understand it more.
1) You would start by defining a bootstrap file (or files) that will define the routes, or entry points, and will setup and create the application. This bootstrap file could be called by an index.php file that is the default file served by the web server. Here is an example of how such bootstrap file will define the routes or entry points (note: these are just fragments and do not represent all the things that a bootstrap file should do):
use Phalcon\Di\FactoryDefault;
// initializes the dependency injector of Phalcon framework
$injector = new FactoryDefault();
// defines the routes
$injector->setShared('router', function () {
return require_once('some/path/routes.php');
});
Then it the routes.php file:
use Phalcon\Mvc\Router;
use Phalcon\Mvc\Router\Group as RouterGroup;
// instantiates the router
$router = new Router(false);
// defines routes for the 'users' controller
$user_routes = new RouterGroup(['controller' => 'users']);
$user_routes->setPrefix('/users');
$user_routes->addGet('/show/{id:[0-9]{1,9}}', ['action' => 'show']);
$router->mount($user_routes);
return $router;
Im defining routes in an alternate way, by defining routes groups. I find it to be more easy to organize routes by resource or controller.
2) When you enter the url example.com/users/show/123, the routes above will match this to the controller users and action show. This is specified by the chunks of code ['controller' => 'users'], setPrefix('/users') and '/show/{id:[0-9]{1,9}}', ['action' => 'show']
3) So now you create the controller. You create a file in, let's say, controllers/UsersController.php. And then you create its action; note the name that you used in the route (show) and the suffix of Action:
public function showAction(int $id) {
// ... do all you need to do...
// fetch data
$user = UserModel::findFirst(blah blah);
// pass data to view
$this->view->setVar('user', $user);
// Phalcon automatically calls the view; from the manual:
/*
Phalcon automatically passes the execution to the view component as soon as a particular
controller has completed its cycle. The view component will look in the views folder for
a folder named as the same name of the last controller executed and then for a file named
as the last action executed.
*/
// but in case you would need to specify a different one
$this->view->render('users', 'another_view');
}
There is much more stuff related to views; consult the manual.
Note that you will need to register such controller in the bootstrap file like (Im also including examples on how to register other things):
use Phalcon\Loader;
// registers namespaces and other classes
$loader = new Loader();
$loader->registerNamespaces([
'MyNameSpace\Controllers' => 'path/controllers/',
'MyNameSpace\Models' => 'path/models/',
'MyNameSpace\Views' => 'path/views/'
]);
$loader->register();
4) You will also need to register a few things for the views. In the bootstrap file
use Phalcon\Mvc\View;
$injector->setShared('view', function () {
$view = new View();
$view->setViewsDir('path/views/');
return $view;
});
And this, together with other things you will need to do, particularly in the bootstrap process, will get you started in sending requests to the controller and action/view defined in the routes.
Those were basic examples. There is much more that you will need to learn, because I only gave you a few pieces to get you started. So here are some links that can explain more. Remember, there are several different ways to achieve the same thing in Phalcon.
Bootstrapping:
https://docs.phalconphp.com/en/3.2/di
https://docs.phalconphp.com/en/3.2/loader
https://docs.phalconphp.com/en/3.2/dispatcher
Routing: https://docs.phalconphp.com/en/3.2/routing
Controllers: https://docs.phalconphp.com/en/3.2/controllers
More on Views (from registering to passing data to them, to templating and more): https://docs.phalconphp.com/en/3.2/views
And a simple tutorial to teach you some basic things: https://docs.phalconphp.com/en/3.2/tutorial-rest
The application begins with the routing stage. From there you grab the controller and action from the router, and feed it to the dispatcher. You set the view then call the execute the dispatcher so it access your controller's action. From there you create a new response object and set its contents equal to the view requests, and finally send the response to the client's browser -- both the content and the headers. It's a good idea to do this through Phalcon rather than echoing directly or using PHP's header(), so it's only done at the moment you call $response->send(); This is best practice because it allows you to create tests, such as in phpunit, so you can test for the existence of headers, or content, while moving off to the next response and header without actually sending anything so you can test stuff. Same idea with exit; in code, is best to avoid so you can write tests and move on to the next test without your tests aborting on the first test due to the existence of exit.
As far as how the Phalcon application works, and in what steps, it's much easier to follow the flow by looking at manual bootstrapping:
https://docs.phalconphp.com/en/3.2/application#manual-bootstrapping
At the heart of Phalcon is the DI, the Dependency Injection container. This allows you to create services, and store them on the DI so services can access each other. You can create your own services and store them under your own name on the DI, there's nothing special about the names used. However depending on the areas of Phalcon you used, certain services on the DI are assumed like "db" for interacting with your database. Note services can be set as either shared or not shared on the DI. Shared means it implements singleton and keeps the object alive for all calls afterwards. If you use getShared, it does a similar thing even if it wasn't initially a shared service. The getShared method is considered bad practice and the Phalcon team is talking about removing the method in future Phalcon versions. Please rely on setShared instead.
Regarding multiple views, you can start with $this->view->disable(); from within the controller. This allows you to disable a view so you don't get any content generated to begin with from within a controller so you can follow how views work from within controllers.
Phalcon assumes every controller has a matching view under /someController/someView followed by whatever extension you registered on the view, which defaults to .volt but can also be set to use .phtml or .php.
These two correspond to:
Phalcon\Mvc\View\Engine\Php and Phalcon\Mvc\View\Engine\Volt
Note that you DON'T specify the extension when looking for a template to render, Phalcon adds this for you
Phalcon also uses a root view template index.volt, if it exists, for all interactions with the view so you can use things like the same doctype for all responses, making your life easier.
Phalcon also offers you partials, so from within a view you can render a partial like breadcrumbs, or a header or footer which you'd otherwise be copy-pasting into each template. This allows you to manage all pages from the same template so you're not repeating yourself.
As far as which view class you use within Phalcon, there's two main choices:
Phalcon\Mvc\View and Phalcon\Mvc\View\Simple
While similar, Phalcon\Mvc\View gives you a multiple level hierarchy as described before with a main template, and a controller-action based template as well as some other fancy features. As far as Phalcon\Mvc\View\Simple, it's much more lightweight and is a single level.
You should be familiar with hierarchical rendering:
https://docs.phalconphp.com/en/3.2/views#hierarchical-rendering
The idea is with Phalcon\Mvc\View that you have a Main Layout (if this template exists) usually stored in /views/index.volt, which is used on every page so you can toss in your doctypes, the title (which you would set with a variable the view passed in), etc. You'd have a Controller Layout, which would be stored under /views/layouts.myController.volt and used for every action within a controller (if this template exists), finally you'd have the Action Layout which is used for the specific action of the controller in /views/myController/myAction.volt.
There are all types of ways you can break from Phalcon's default behavior. You can do the earlier stated $this->view->disable(); so you can do everything manually yourself so Phalcon doesn't assume anything about the view template. You can also use ->pick to pick which template to use if it's going to be different than the controller and action it's ran in.
You can also return a response object from within a controller and Phalcon will not try to render the templates and use the response object instead.
For example you might want to do:
return $this->response->redirect('index/index');
This would redirect the user's browser to said page. You could also do a forward instead which would be used internally within Phalcon to access a different controller and/or action.
You can config the directory the views are stored with setViewsDir. You can also do this from within the controller itself, or even within the view as late as you want, if you have some exceptions due to a goofy directory structure.
You can do things like use $this->view->setTemplateBefore('common') or $this->view->setTemplateAfter('common'); so you can have intermediate templates.
At the heart of the view hierarchy is <?php echo $this->getContent(); ?> or {{ content() }} if you're using Volt. Even if you're using Volt, it gets parsed by Phalcon and generates the PHP version with $this->getContent(), storing it in your /cache/ directory, before it is executed.
The idea with "template before" is that it's optional if you need another layer of hierarchy between your main template and your controller template. Same idea with "template after" etc. I would advise against using template before and after as they are confusing and partials are better suited for the task.
It all depends on how you want to organize your application structure.
Note you can also swap between your main template to another main template if you need to swap anything major. You could also just toss in an "if" statement into your main template to decide what to do based on some condition, etc.
With all that said, you should be able to read the documentation and make better sense of how to utilize it:
https://docs.phalconphp.com/en/3.2/api/Phalcon_Mvc_View

Phalcon / Volt dynamically build/render common template areas (partials)

I'm starting a project using Phalcon framework with Volt as a template engine. I have some experience with Symfony/Twig.
I read documentation and tried searching all over the internet but can't find a satisfying way to accomplish what I want (I find ugly the solution described here: How do I create common template with header and footer for phalcon projects with Volt Engine; it's not using Volt per se for the navigation.)
So the story is pretty easy: my base template consists of 4 parts: navigation, header, content and footer. I use partials to include the common areas in the base template like the navigation, header and the footer, works fine with "static data".
Now the question is: how do I get to dynamically generate the navigation menu with items coming from the database? The template will have common areas that have to come from DB also in the header, footer and a sidebar. Having to fetch that in all Controller actions sounds like overkill and not very DRY (maybe do it on the init part? but will have to be done in every controller. Maybe in an abstract controller, I dunno.)
What is the best way to accomplish this in Phalcon/Volt? In Symfony/Twig you can call from the view a controller action, so you can have like a LayoutController that renders partials from a page.
Thanks!
Here are few variants:
1) Your controllers can extend a BaseController and in it's initialize() method you can assign those variables to the view.
class BaseController extends \Phalcon\Mvc\Controller
{
public function initialize()
{
// Common Variables
$this->view->assetsSuffix = $this->config->debug ? '' : '.min';
}
2) Create a custom Volt function which loads the data.
// In your Volt service:
$compiler->addFunction('getMenu', function($resolvedArgs, $exprArgs){
return 'Helpers\CommonFunctions::getMenu(' . $resolvedArgs . ')';
})
// Helper file and function
public static function getMenu()
{
return \Models\Menu::find();
}
// Volt usage
{% set menuItems = getMenu() %}
{% for item in menuItems %}
{% endfor %}
3) Use the models to query the DB directly from the template. However this is not yet supported with Volt (not sure if it is added in latest version, have to confirm).
<?php $menuItems = \Models\Menu::find(); ?>
{% for item in menuItems %}
{% endfor %}
4) Ajax/Javascript, but this is really dependant on your application. Something like Angular approach, but I will not get into details with this varaiant.

PHALCON PHP - Different views based on user profile

I created Phalcon PHP app,
I have 3 different user profiles: (ID: 1) Administrators, (ID: 2) Accountants and (ID: 3) Warehouses.
I want my app able to render views based on those profiles, for example
controllerName/index.1.volt //for Administrators
controllerName/index.2.volt //for Accountants
controllerName/index.3.volt //for Warehouses
but when those files weren't found, my app will fallback to:
controllerName/index.volt
How do I accomplish that?
One approach, although messy, would be to use controlerName/index.volt as the "landing page" then from there check an if-statement to decide what the user's role is. Then from the if-statement use a partial like {{ partial("index.1.volt") }} but you'd need to hard-code this for every controller... yuck...
A good solution which I'd recommend, though, would be to make use of the View's exists method to check if the view exists from within your controller. The idea is you pass this method the string path to the view file you're looking for but omitting the extension. The reason you omit the extension is because Phalcon allows you to use multiple rendering engines so an application using a mixture of .volt and .phtml would work.
Assuming you were using user roles something like this:
define('GUEST_ROLE',0);
define('ADMIN_ROLE',1);
define('ACCOUNTANT_ROLE',2);
define('WAREHOUSE_ROLE',3);
(with the guest role with a value of 0) I would suggest having all your controllers extend a ControllerBase then define the following two methods in your ControllerBase:
public function getUserLevel()
{
if($this->session->has('userLevel'))
{
$userLevel=$this->session->get('userLevel');
return (int)$userLevel;
}else{
return 0;//default to guest
}
}
protected function initialize()
{
$controllerName=$this->dispatcher->getControllerName();
$actionName=$this->dispatcher->getActionName();
$userLevel=$this->getUserLevel();
if($this->view->exists("$controllerName/$actionName.$userLevel"))
{
$this->view->pick("$controllerName/$actionName.$userLevel");
}
//No reason to add an else, Phalcon defaults to "$controllerName/$actionName"
}
Just make sure, that if you ever need to define a custom "initialize" method for a specific controller which extends the ControllerBase, e.g. to add a title prefix to all pages related to the controller, that you call parent::initialize(); otherwise it won't get called. But that's only if you're going to be overriding the method.
Chances are you're already using a ControllerBase and doing similar logic already, if so, you'd need to edit your already existing "initialize" method and merge my code with yours.
Happy coding.

What's the most convenient way to globally set 2 different form themes for app back-end and front-end

So, theming a form in Symfony2 is easy. You create a custom theme file and you add it to your config.yml file to load it. Done.
However, I have 2 different form themes. One for the front-end of the application and one for the back-end of the application.
I went through the documentation (http://symfony.com/doc/2.3/cookbook/form/form_customization.html) but couldn't find a good and easy way to do this.
When I add the theme to the config.yml file, I have the same theme in both front-end and back-end. I could also include the form within each view like this
{% form_theme form 'form_table_layout.html.twig' %}
However, that means I have to do it within each view.
Is there somehow a way to create a separate config file for front-end and back-end? Can I somehow indicate in the base template file which form theme should be used?
Anything else I could do?
If you use the Symfony2 default directory structure, i.e. you have a single kernel for both the frontend and the backend, you can only (as you mentioned) either set the form theme in each template, or use the same template application-wide by setting it in the config.yml file.
The alternative solution you mentioned, that is creating two base templates, each one setting a "global" form_theme tag would theoretically work. Create a base front-end.html.twig template for all your frontend pages with the following tag:
{% form_theme form 'form-front-end.html.twig' %}
That would work, but you would be forced to have a form variable in each inherited template. You would also not be able to set the theme to multiple forms in the same page.
You could improve the solution by checking if the form variable is defined before styling it:
{% if form is defined %}
{% form_theme form 'form-front-end.html.twig' %}
{% endif %}
or even better, if you want to be able to passing multiple form to the same template, you could do it by using a forms array:
{% if forms is defined %}
{% for form in forms %}
{% form_theme form 'form-front-end.html.twig' %}
{% endfor %}
{% endif %}
The good thing is that this would not throw any exceptions, even if you don't pass the variable at all, but you have to remember to put any forms to be rendered into the forms array.
Obviously, you would do the same thing for the back-end base template.
There may be better solutions, but in the meanwhile I hope this helps!
I also faced this problem some times ago, found this post, and followed the way as Andrea Sprega suggested. Recently I came out of a way which can save some repetitive typings.
Warning: This is somewhat "hackish", is probably not the best way, and is not guarantee to work in next framework release. You'll see why when you read below.
Goal: We want to have different default form templates depending on the application "environment" (the most common examples would be "frontend" and "backend). But fundamentally Symfony isn't aware of such "environment". Although it is possible to split the project into different environments and load different configurations, this is definitely an overkill if what we want is just having different form templates.
I think the best way would be an ability to set/override the default form themes in the base template. So we can have form theme A in frontend base template, and form theme B in backend base template. This sounds the best solution to me because as form theme is a "view" stuff, it makes perfect sense to have it changed in the view. However, the problem seems to be that, when the twig (template) code is executed, the form view is already initialized. So it would be too late to change the default form theme there. (I could be wrong here, because I didn't dig deep enough to have 100% confidence)
So I decided to do it another way. It works like this:
First, I made an assumption that all frontend controllers will extend the same base class (e.g. "BaseFrontendController"). Similarly, all backend controllers will extend the same BaseBackendController class. Here's how we distinguish a frontend and backend environment. This is actually the case in my project.
The default twig templates will be added to these base controller classes. It can be done via a method, or an annotation. In this post I'll use a public method.
Before the controller is executed, overwrite the defined default twig templates.
When the form view is initialized, it will use the default twig template defined in the controller.
Here's how it's done:
Firstly, the default form themes defined in your config.yml will be passed to the constructor of \Symfony\Component\Form\AbstractRendererEngine, and is assigned to the local instance $defaultThemes. This field is protected so that it can be used by its derived classes, but there is no setter to change its value.
So, we need to roll our own Symfony\Bridge\Twig\Form\TwigRendererEngine:
namespace AppBundle\Form\Twig;
use Symfony\Bridge\Twig\Form\TwigRendererEngine as BaseTwigRendererEngine;
class TwigRendererEngine extends BaseTwigRendererEngine
{
/**
* #param array $defaultThemes
*/
public function addDefaultThemes($defaultThemes)
{
$this->defaultThemes = array_merge($this->defaultThemes, $defaultThemes);
}
}
This custom renderer engine is plain simple - it just add a new method to append the default themes to the existing one. And this is why I say it's "hackish" - it will no longer work when the internal is changed.
Secondly, define an interface TwigTemplateProvider which is going to be implemented by the base frontend/backend controller classes:
namespace AppBundle\Form\Twig;
interface TwigTemplateProvider
{
/**
* #return array|string The form template path
*/
public function getDefaultFormTwigTemplates();
}
Thirdly, we need a listener which will run when the controller is executed.
<?php
namespace AppBundle\EventListener;
use AppBundle\Form\Twig\TwigRendererEngine;
use AppBundle\Form\Twig\TwigTemplateProvider;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class PerControllerFormTemplateListener
{
/**
* #var \Twig_Environment
*/
private $twig;
public function __construct(\Twig_Environment $twig)
{
$this->twig = $twig;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TwigTemplateProvider) {
/** #var \Symfony\Bridge\Twig\Extension\FormExtension $formExtension */
$formExtension = $this->twig->getExtension('form');
$engine = $formExtension->renderer->getEngine();
if ($engine instanceof TwigRendererEngine) {
$templates = (array)$controller[0]->getDefaultFormTwigTemplates();
$engine->addDefaultThemes($templates);
}
}
}
}
The listener will get the form template name (in string or array) supplied by controllers implementing TwigTemplateProvider interface. Then it will add it to the default theme list and pass it to the form renderer engine.
Now, wire them together by adding the followings to your services.yml:
parameters:
twig.form.engine.class: AppBundle\Form\Twig\TwigRendererEngine
services:
app.form.per_controller_template_listener:
class: AppBundle\EventListener\PerControllerFormTemplateListener
arguments: ["#twig"]
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Here we set the %twig.form.engine.class% parameter to our own implementation, and added our event listener to the stack.
Using it is very simple. For example, in your base frontend controller, implement TwigTemplateProvider and add the following method:
public function getDefaultFormTwigTemplates()
{
return 'frontend/form_layout.html.twig';
}
Then this layout will be added to the form template stack when your front controller is executed.

Laravel - How to NOT use a layout

I'm using laravel with controllers layout. But there are some parts of my app where I don't want to use a layout (for example, when returning data to the payment gateway request, for wich I send XML data). I just want to pass data to my view and render it alone, with no need for a layout.
How can I do that? I've been trying some approaches but none worked for this. I can successfuly change what layout to render, but I can't set to render the view without a layout.
Thanks!
Edit: Let me explain it better
My default layout is set in Base_Controller. Then all my controllers extends it but in one of them I need no layout, as I told above. Maybe I need to unset the default layout or something like that, I'm not sure.
You can simply return something from your controller action to bypass the layout.
function get_xml($id) {
$user = User::find($id);
return View::make('user.xml', $user);
}
On your controller functions, you can simply return a string, which will be thrown back to the browser as-is. Alternatively, you can craft a Laravel\Response object, which will allow you to fine-tune your site's output a lot more than just returning a string.
The Response class has a few tricks up its sleeve that are not mentioned on the docs: default return, JSON, forced download.
You're more interested in the first one, which will allow you to correctly set the content-type of the response to application/xml. In addition to this, you can still use views for XML! Generate the view as you would with View::make, but instead of directly returning it, store it in a variable. To render it, call render() on it - it will return the output.
A simple way....
suppose there is a main layout
<body>
#yield('content')
</body>
This content will be where the view will be inserted.
Now,
if you want to use layout, Make the view page like this:
#layout('main')
#section('content')
blah blah your content
#endsection
If you don't want to use layout, omit the codes above.
In controller, the code will be same for both the files.
return View::make('index');

Categories