I have an old project I'm working on using Slim version 2. I can not upgrade to 3.
I'm trying to integrate twig into slim 2 while also keeping the old default slim2 renderer.
Currently I have this.
class TwigView extends \Slim\View
{
public function rendertwig($template,$data = array()){
global $twig;
$twigResults = $twig->render($template,array('test' => '1'));
$data = array_merge($this->data->all(), $data);
return $this->render($twigResults, $data);
}
}
$view = new TwigView();
$config['view'] = $view; //#JA - This command overides the default render method.
//#JA - Intialize Slim
$app = new \Slim\Slim($config);
The idea is that I would call this saying $app->view->rendertwig('file.twig') when I need to render the twig templates and use $app->render('template.php') for all the other templates that use the default slim2 method of templating.
However, I get an error because in my rendertwig function $this->render() function requires a template name for the first parameter. Is there a way I can render directly the results from twig into the slim engine without needing a template file?
I'm aware this is bad form to have two templating engines but eventually I will switch everything to Twig but I need this as a temporary solution till I can patch everything over.
When I inspected slim's view object it has this defined as its render method which will explain the issue.
protected function render($template, $data = null)
{
$templatePathname = $this->getTemplatePathname($template);
if (!is_file($templatePathname)) {
throw new \RuntimeException("View cannot render `$template` because the template does not exist");
}
$data = array_merge($this->data->all(), (array) $data);
extract($data);
ob_start();
require $templatePathname;
return ob_get_clean();
}
I don't know if this is bad form but I did this as a temporary solution.
class TwigView extends \Slim\View
{
public function rendertwig($template,$data = array()){
global $twig;
$twigResults = $twig->render($template,array('test' => '1'));
echo $twigResults;
}
}
I saw that all the render method did was just require the template so I figured its safe to just echo the results from the twig templating engine? This seemed to work from my test.
Related
Using Twig I render a particular view. I need this view to be translated into a language I choose. I display the view using:
return $this->setup->twig->display($view, $params);
Where $view is the name of the *.html.twig template and $params is an array with the parameters I need to pass.
However, if I want to translate the template before displaying it, how I have to do it?
Currently I have included .yml files for different languages and I have also replaced the text inside the views with the appropriate corresponding values from the yml file.
Apart from everything else, I have also loaded the Twig translator in a file separate from the rest of the project. It has the following code:
require dirname(__DIR__) . '/vendor/autoload.php';
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\ArrayLoader;
class Translation
{
public $translator;
public function translator()
{
$this->translator = new Translator('fr_FR');
$this->translator->addLoader('array', new ArrayLoader());
$this->translator->addResource('array', array(
'Symfony is great!' => 'J\'aime Symfony!',
), 'fr_FR');
var_dump($this->translator->trans('Symfony is great!'));
}
}
$show = new Translation;
$show->translator();
And it really displays the translation.
Still, I have no idea how to connect everything together....
Did you try to set the locale before rendering your twig view?
public function exampleAction(Request $request) {
$locale = 'de'; // Set the language
$request->setLocale($locale);
$content = $this->renderView($view, $params);
// Maybe return to default locale....
}
We are using the ZF2 forms as a standalone plugin inside another PHP application. This is working fine, except I'm not able to use some custom view helpers. The ServiceManager (?) doesn't know where my helpers are located and crashes with a fatal error.
Is there a way to register some custom view helpers without a modules.config.php? I haven't found a way to pass an array/config to the HelperConfig below. As a ZF2 rookie I'm a bit lost here.
$form = new \My\Custom\Form\ContactForm();
$renderer = new \Zend\View\Renderer\PhpRenderer();
$config = new \Zend\Form\View\HelperConfig();
$config->configureServiceManager($renderer->getHelperPluginManager());
if(isset($_POST['submit'])) {
$form->setData($_POST);
if($form->isValid()) {
$data = (OBJECT) $form->getData();
// ...
} else {
return $form->render($renderer); // Inside the render() method we use the form-view-helpers to render the form.
}
} else {
return $form->render($renderer);
}
I have also tried to use the HelperPluginManager (Btw. whats the difference between these two?). But there isn't a method to pass a config-array either.
$renderer = new \Zend\View\Renderer\PhpRenderer();
$plugins = $renderer->getHelperPluginManager();
$renderer->setHelperPluginManager($plugins);
Is there any way to pass through a secondary path to the views dir in phalcon?
in zend framework I think the syntax is
$this->view->addScriptPath('/backup/path');
$this->view->addScriptPath('/preferred/path');
so if there is a file in the preferred path it will use it, if not it will fallback through the chain.
I use this, for example, for mobile versions when most of the pages are the same, but some have to be significantly different and I don't want to have to duplicate all the views just for 2 or 3 variants
In phalcon I have tried sending an array to the view, but that just results in neither working
$di->set('view', function() use ($config) {
$view = new \Phalcon\Mvc\View();
$view->setViewsDir( array('/preferred/path/', '/backup/path/') );
return $view;
});
I've got this working by extending the Phalcon\Mvc\View\Engine\Volt
In the render($template_path, $params, $must_clean = null) method I set the alternative path, check if file is available and if so I switch the $template_path given with the alternative path. Then it's just a case of calling:
return parent::render($template_path, $params, $must_clean);
where $template_path contains the new (alternative) path.
If your alternative path might change on a per project basis and you need to set it in bootstrap, then rather than doing it when getting a "view" from di you would do it when getting volt.
Just remember that all views are rendered with that method so you will have to account for layout and partial views as well - depending on your implementation.
Example: (this has not been tested, it's based on a similar set up I have in my own code)
<?php
class Volt extends Phalcon\Mvc\View\Engine\Volt
{
private $skin_path;
public function render($template_path, $params, $must_clean = null)
{
$skin_template = str_replace(
$this->di->getView()->getViewsDir(),
$this->getSkinPath(),
$template_path
);
if (is_readable($skin_template)) {
$template_path = $skin_template;
}
return parent::render($template_path, $params, $must_clean);
}
public function setSkinPath($data)
{
$this->skin_path = $data;
}
public function getSkinPath()
{
return $this->skin_path;
}
}
In your bootstrap:
$di->setShared('volt', function($view, $di) {
$volt = new Volt($view, $di);
$volt->setSkinPath('my/alternative/dir/');
return $volt;
});
Many thanks to nickolasgregory#github who pointed me in the right direction.
Method proposed by #strayobject helps me also, but I've found that using extend or other statements inside volt templates dosn't work.
Here's refined solution that works with extend and include:
use Phalcon\Mvc\View\Engine\Volt;
class VoltExtension extends Volt
{
// Override default Volt getCompiler method
public function getCompiler()
{
if (!$this->_compiler) {
$this->_compiler = new VoltCompilerExtension($this->getView());
$this->_compiler->setOptions($this->getOptions());
$this->_compiler->setDI($this->getDI());
}
return $this->_compiler;
}
}
And
use Phalcon\Mvc\View\Engine\Volt;
class VoltCompilerExtension extends Volt\Compiler
{
public function compileFile($path, $compiledPath, $extendsMode = null)
{
$skinPath = $this->getOption('skinPath');
if ($skinPath) {
$skinTemplate = str_replace(
$this->getDI()->getView()->getViewsDir(),
$skinPath,
$path
);
if (is_readable($skinTemplate)) {
$path = $skinTemplate;
}
}
return parent::compileFile($path, $compiledPath, $extendsMode);
}
}
Usage:
$volt = new VoltExtension($view, $di);
$volt->setOptions(
array(
'compiledPath' => $config->application->cacheDir,
'compiledSeparator' => '_',
'compileAlways' => false,
'skinPath' => $config->application->skinPath
)
);
Please take a look at this phalcon framework update. It provides support for multiple view packages per website (you can have multiple websites). Users of the magento framework will find it easy to use:
https://github.com/alanbarber111/cloud-phalcon-skeleton
I have to display different views for mobile devices and I want to provide a simple JSON-API.
I wrote a little module for the Kohana Framework which loads different views depending on some circumstances, which should help me in this case: https://github.com/ClaudioAlbertin/Kohana-View-Factory
However, I'm not very happy with this solution because I can't set different assets for different device-types. Also, when I'd output JSON with a JSON-view, it's still wrapped in all the HTML-templates.
Now, I'm looking for a better solution. How do you handle different output formats or device-types in your MVC-applications?
I had an idea: just split the controller into two controllers: a data-controller and an output-controller.
The data-controller gets and sets data with help of the models, does
all the validating etc. It gets the data from the models and write it to a data-object
which is later passed to the view.
The output-controller loads the views and give them the data-object from the data-controller. There is an output-controller for each format or device-type: an output-controller for mobile-devices could load the mobile-views and add all the mobile-versions of stylesheets and scripts. A JSON-output-controller could load a view without all the html-template stuff and convert the data into JSON.
A little example:
<?php
class Controller_Data_User extends Controller_Data // Controller_Data defines a data-object $this->data
{
public function action_index()
{
$this->request->redirect('user/list');
}
public function action_list()
{
$this->data->users = ORM::factory('user')->find_all();
}
public function action_show($id)
{
$user = new Model_User((int) $id);
if (!$user->loaded()) {
throw new HTTP_Exception_404('User not found.');
}
$this->data->user = $user;
}
}
class Controller_Output_Desktop extends Controller_Output_HTML // Controller_Output_HTML loads a HTML-template
{
public function action_list($data)
{
$view = new View('user/list.desktop');
$view->set($data->as_array());
$this->template->body = $view;
}
public function action_show($data)
{
$view = new View('user/show.desktop');
$view->set($data->as_array());
$this->template->body = $view;
}
}
class Controller_Output_JSON extends Controller_Output // Controller_Output doesn't load a template
{
public function action_list($data)
{
$view = new View('user/list.json');
$view->users = json_encode($data->users->as_array());
$this->template = $view;
}
public function action_show($data)
{
$view = new View('user/show.json');
$view->user = json_encode($data->user);
$this->template = $view;
}
}
What do you think?
Hmm... From the 1st view it loooks strange, and somehow like fractal -- we are breaking on MVC one of our MVC -- C.
But why is this app returns so different results, based on point-of-entry (or device)?
The task of the controller is only to get the data and choose the view -- why do we need standalone logic for choosing something based on point-of-entry (device)?
I think these questions should be answered first. Somewhere could be some problem.
Also the cotroller should select only one view ideally, and dont' do "encode" or else with data, based on current output. I think all this should be in some kind of "layouts" or else. As data always the same and even different views should be the same -- only some aspects changes.
I'm using PHP 5.3's class_alias to help process my Symfony 1.4 (Doctrine) forms. I use a single action to process multiple form pages but using a switch statement to choose a Form Class to use.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
class_alias('MyFormPage1Form', 'FormAlias');
break;
...
}
$this->form = new FormAlias($obj);
}
This works brilliantly when browsing the website, but fails my functional tests, because when a page is loaded more than once, like so:
$browser->info('1 - Edit Form Page 1')->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end()->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end();
I get a 500 response to the second request, with the following error:
last request threw an uncaught exception RuntimeException: PHP sent a warning error at /.../apps/frontend/modules/.../actions/actions.class.php line 225 (Cannot redeclare class FormAlias)
This makes it very hard to test form submissions (which typically post back to themselves).
Presumably this is because Symfony's tester hasn't cleared the throughput in the same way.
Is there a way to 'unalias' or otherwise allow this sort of redeclaration?
As an alternate solution you can assign the name of the class to instantiate to a variable and new that:
public function executeEdit(sfWebRequest $request) {
$formType;
switch($request->getParameter('page')) {
case 'page-1':
$formType = 'MyFormPage1Form';
break;
...
}
$this->form = new $formType();
}
This doesn't use class_alias but keeps the instantiation in a single spot.
I do not know for sure if it is possible, but judging from the Manual, I'd say no. Once the class is aliased, there is no way to reset it or redeclare it with a different name. But then again, why do use the alias at all?
From your code I assume you are doing the aliasing in each additional case block. But if so, you can just as well simply instantiate the form in those blocks, e.g.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
$form = new MyFormPage1Form($obj);
break;
...
}
$this->form = $form;
}
You are hardcoding the class names into the switch/case block anyway when using class_alias. There is no advantage in using it. If you wanted to do it dynamically, you could create an array mapping from 'page' to 'className' and then simply lookup the appropriate class.
public function executeEdit(sfWebRequest $request) {
$mapping = array(
'page-1' => 'MyFormPage1Form',
// more mappings
);
$form = NULL;
$id = $request->getParameter('page');
if(array_key_exists($id, $mapping)) {
$className = $mapping[$id];
$form = new $className($obj);
}
$this->form = $form;
}
This way, you could also put the entire mapping in a config file. Or you could create FormFactory.
public function executeEdit(sfWebRequest $request) {
$this->form = FormFactory::create($request->getParameter('page'), $obj);
}
If you are using the Symfony Components DI Container, you could also get rid of the hard coded factory dependency and just use the service container to get the form. That would be the cleanest approach IMO. Basically, using class_alias just feels inappropriate here to me.
function class_alias_once($class, $alias) {
if (!class_exists($alias)) {
class_alias($class, $alias);
}
}
This doesn't solve the problem itself, but by using this function it is ensured that you don't get the error. Maybe this will suffice for your purpose.