I have found out how to do a view override in Phalcon with the help of this: https://stackoverflow.com/a/19941928/74651
The problem is that it uses this method for the layout to, but if the directory does not exists on the original viewpath it does enter in this method.
Where does phalcon checks the directory for the layout and how to override it?
Here and kind of here.
So, you could do three things. First, change the layout dir, second, just set the layout, or third, change both :)
$view->setLayoutsDir('custom/layouts/');
$view->setLayout('outrageouslyCustomLayout');
See all methods in the documentation. If I remember correctly, paths must be relative to your views directory.
Standard view offers quite a few ways of controlling your rendering, it might be that you don't really need to override things – http://docs.phalconphp.com/en/latest/reference/views.html#control-rendering-levels
Checkout this framework: https://github.com/alanbarber111/cloud-phalcon-skeleton
Allows you to have design packages on a per website basis with the ability to setup a "Fall back" directory and an "override" directory. If nothing else, take a look at app/code/Core/Model/App/Design.php and app/code/core/Model/View* to see how we've completed this.
Use this in your controller... worked for me
public function initialize() {
parent::initialize();
$this->view->setTemplateAfter('yourtemplate');
}
Ok i just figured it out after some debugging.. cannot dump vars in view class which is a bit annoying ;)
My problem was indeed, that the layout needs to be relative to the view dir. When you override the view dir as on: https://stackoverflow.com/a/19941928/74651 it will look for the layout on the original view dir not the override.
It's possible to override this in _engineRender a bit annoying that they force the relative directory and only trigger and event if a file has been found not very flexible.
<?php
namespace Phapp\Mvc;
class View extends \Phalcon\Mvc\View {
/**
* #param array $engines
* #param string $viewPath
* #param boolean $silence
* #param boolean $mustClean
* #param \Phalcon\Cache\BackendInterface $cache
*/
protected function _engineRender($engines, $viewPath, $silence, $mustClean, $cache) {
// Layout override
if ($this->getCurrentRenderLevel() !== \Phalcon\Mvc\View::LEVEL_LAYOUT) {
return parent::_engineRender($engines, $viewPath, $silence, $mustClean, $cache);
}
foreach ($engines as $extension => $engine) {
if (!$engine instanceof View\Engine\ThemeInterface) {
continue;
}
$layout = $engine->getThemePath().$viewPath.$extension;
if (is_readable($layout)) {
$originalViewDir = $this->getViewsDir();
$this->setViewsDir($engine->getThemePath());
$content = parent::_engineRender($engines, $viewPath, $silence, $mustClean, $cache);
$this->setViewsDir($originalViewDir);
return $content;
}
}
}
}
Related
I'm working on a Symfony (2.7.4) website where some users have their own language resources for their own locales. For example, a user might have two locales (for example fr and en) and another user could also have one or all of these locales.
Each user has the ability to edit its own translations in a third party app, so translations are not shared between users.
I would like to be able to load the appropriate (YML or XLIFF) resources file when accessing a user's page, based on the locale (which is defined in the URL) and the user (could be its ID or anything that identifies it).
For example, when visiting user99.my-domain.ext/fr/ I'd like to add [base_directory]/user99/messages.fr.yml to the resources loaded by the Translator so it overrides the keys in the base messages.fr.yml.
I've tried to inject the Translator in my service, but I can only use it for reading translations, not adding any. What would be the best way to do that? Or is doing it in a service is too late? Maybe the Kernel is a better place?
Any help is appreciated!
Note: I'm using the YAML format in my examples, but any of the Symfony-known formats is eligible.
In order to "override" translations you should decorate the translator service. That way your own translator logic will be executed when trans() is called somewhere.
http://symfony.com/doc/current/components/dependency_injection/advanced.html#decorating-services
By decorating the service other bundles will also start using the logic you did describe above. You can inject the active user (eg. token_storage service) and some caching services (eg. https://github.com/doctrine/DoctrineCacheBundle) to make sure your user gets the right translations.
This isn't related to the request or hostname, your translation logic for the user should happen after the firewall / authorization logic was executed.
See Symfony's LoggingTranslator PR to find out how the decorator pattern was used to let the translator log missing translations: https://github.com/symfony/symfony/pull/10887/files
I chose to use a custom Twig filter, so I can decide when I want user specific translations and when I want generic ones.
Here's my extension (my users are in fact Domain instances):
<?php
// src/AppBundle/Twig/DomainTranslationExtension
namespace AppBundle\Twig;
use AppBundle\Document\Domain;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Translation\Loader\YamlFileLoader;
use Symfony\Component\Translation\Translator;
class DomainTranslationExtension extends \Twig_Extension {
/**
* #var ContainerInterface
*/
protected $container;
/**
* #var Translator
*/
protected $translator;
/**
* #var string
*/
protected $locale;
/**
* #var Domain
*/
protected $domain;
public function setContainer(ContainerInterface $container) {
$this->container = $container;
$this->locale = $this->container->get('request_stack')->getMasterRequest()->getLocale();
$domainService = $this->container->get('app.domain_service');
$this->domain = $domainService->getDomain();
// TODO: File loading error check
$this->translator = new Translator($this->locale);
$this->translator->addLoader('yaml', new YamlFileLoader());
$this->translator->addResource('yaml', 'path/to/domain-translations/' . $this->domain->getSlug() . '/messages.' . $this->locale . '.yml', $this->locale);
}
public function getFilters() {
return array(
new \Twig_SimpleFilter('transDomain', array($this, 'transDomain')),
);
}
public function transDomain($s) {
$trans = $this->translator->trans($s);
// Falling back to default translation if custom isn't available
if ($trans == $s) {
$trans = $this->container->get('translator')->trans($s);
}
return $trans;
}
public function getName() {
return 'app_translation_extension';
}
}
Declared like that in app/config/services.yml:
app.domain_service:
class: AppBundle\Services\DomainService
arguments: [ #request_stack, #doctrine_mongodb, #translation.loader ]
And used like that in a Twig file:
{{ 'my.translation.key'|transDomain }}
I hope this helps, thanks!
I have problem with path to twig file. I would like to shorten the path. Look at my example:
class DefaultController extends Controller
{
public function indexAction($name)
{
return $this->render('CatalogFrontendBundle:Default:index.html.twig', array('name' => $name));
}
}
Of course it will work, but look at the path to twig file it's so long.. I now that I could use annotation like this #Template() but I don't want to. Is there any other way to use default twig file to render my page?
Default twig file - I understand as the file whose name is the same like name of action method. So if name of action is indexAction the name of twig file should be index.html.twig.
If you'd like a Symfony experience that is more geared toward Rapid Application Development (RAD) take a look at the KNP RAD bundle. This gives you a lot of convention-based shortcuts to work with, including automatic template resolution. See here: http://rad.knplabs.com/#template
I can't found better solution to my problem. So I decide to use #Template, Mark have right it will be cached. And I think that better option is to use those annotation than write my own method or put all of the path in the render method.
So the answer is below:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class DefaultController extends Controller
{
/**
*
* #Template()
*/
public function indexAction($name)
{
return array('name' => $name);
}
}
Are there native codeigniter functions I can use to tell if a certain model has already been loaded? Can php's class_exists() be used to tell if a model has already been loaded?
I would be tempted to extend the CI_Loader core class. (See extending Core Class)
class MY_Loader extends CI_Loader {
function __construct()
{
parent::__construct();
}
/**
* Returns true if the model with the given name is loaded; false otherwise.
*
* #param string name for the model
* #return bool
*/
public function is_model_loaded($name)
{
return in_array($name, $this->_ci_models, TRUE);
}
}
You would be checking for a given model with the following:
$this->load->is_model_loaded('foobar');
That strategy is already being used by the CI_Loader class.
This solution supports the model naming feature of CI, where models can have a different name than the model class itself. The class_exists solution wouldn't support that feature, but should work fine if you aren't renaming models.
Note: If you changed your subclass_prefix configuration, it might not be MY_ anymore.
The simplest solution is to use PHP function class_exists
http://php.net/manual/en/function.class-exists.php
For example. if you want to check if Post_model has been defined or not.
$this->load->model('post_model');
/*
a lot of code
*/
if ( class_exists("Post_model") ) {
// yes
}
else {
// no
}
The simplest is the best..
Edited:
You can use the log_message() function.
Put this in your model’s constructor (parent::Model())
log_message ("debug", "model is loaded");
don’t forget to set the log config to debug mode in the config.php file
$config['log_threshold'] = 2;
And set the system/logs directory permission to writable (by default CI will create the log files here)
or set the logs directory in another dir
$config['log_path'] = 'another/directory/logs/';
CI will then create the log file in the directory. monitor the log files as you like. You can get the debug message to see if your model is already loaded or not in the log files.
Riffing off what Maxime Morin & Tomexsans have written, this is my solution:
<?php
class MY_Loader extends CI_Loader {
/**
* Model Loader
*
* Overwrites the default behaviour
*
* #param string the name of the class
* #param string name for the model
* #param bool database connection
* #return void
*/
function model ($model, $name = '', $db_conn = FALSE) {
if (is_array($model) || !class_exists($model)) {
parent::model($model, $name, $db_conn);
}
}
}
?>
This way, you don't ever need to (consciously) check whether a model's loaded or not :)
I do need to change Zend_Controller_Front and use My_Controller_Front, but I can't figure it out... I have made this:
At My_Controller_Front
/**
* Set singleton instance
*/
public static function setInstance($instance = null) {
self::$_instance = $instance;
}
And at my bootstrap
protected function _replaceZendController() {
Busca_Controller_Front::setInstance(Busca_Controller_Front::getInstance());
return $this;
}
From looking at the code I don't think its possible to have Zend_Application use anything other than Zend_Controller_Front.
When you run a Zend_Application, the following things happen:
Zend_Application::bootstrap() runs
The bootstrap process creates Zend_Application_Bootstrap_Bootstrap which sets up the resource loader and then loads the FrontController resource
The Frontcontroller resource is hardcoded to load Zend_Controller_Front (see Zend/Application/Resource/Frontcontroller::getFrontController())
The only way you may be able to change this behavior would be to register your own resource loader which could intercept the loading of the FrontController resource which would load your Front Controller instead of the Zend Front Controller. Of course you would have to make sure your Front Controller supports every option the Zend Framework one does.
So the question is, why do you need to replace Zend_Controller_Front with your own? Can't you set the appropriate options or create plugins to accomplish your goal?
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);
}
}