How to get current template name in a TWIG function - php

let's say I have created a custom twig function: templateName.
$twig = new Twig_Environment($loader);
$twig->addFunction('templateName', new Twig_Function_Function('twig_template_name', array('needs_environment' => true)));
Is there a way to get the name of the current template(s) in php. I imagine something like this:
function twig_template_name(Twig_Environment $env, $values = null) {
return $env->getCurrentTemplateName();
}
Thanks in advance.

For everyone who needs an answer to the initial question, I found a solution that twig itself is using in the Twig_Error class.
protected function guessTemplateInfo()
{
$template = null;
foreach (debug_backtrace() as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) {
$template = $trace['object'];
}
}
// update template filename
if (null !== $template && null === $this->filename) {
$this->filename = $template->getTemplateName();
}
/* ... */
best regards!

The solution proposed by Mario A did not work for me, but using debug_backtrace() is a great idea and a little modification made it work with a recent Twig version:
private function getTwigTemplateName()
{
foreach (debug_backtrace() as $trace) {
if (isset($trace['object'])
&& (strpos($trace['class'], 'TwigTemplate') !== false)
&& 'Twig_Template' !== get_class($trace['object'])
) {
return $trace['object']->getTemplateName() . "({$trace['line']})";
}
}
return '';
}

$twig = new Twig_Environment($loader);
$twig->addFunction(new Twig_SimpleFunction('twig_template_name', function() use ($twig) {
$caller_template_name = $twig->getCompiler()->getFilename();
echo "This function was called from {$caller_template_name}";
}));
UPDATE: as mentioned by Leto, this method will NOT work from within cached (compiled) templates. If, however, you use shared memory caching (APC, Memcache) instead of Twig's caching or run this functionality in app that runs in an environment that doesn't have high traffic (think of back-end app for staff or branch of app that is only used to collect information about app's codebase) you can make it work by disabling Twig's caching (e.g. $twig = new Twig_Environment($loader, array('cache' => false));). Make sure to carefully examine your use case though before disabling Twig's cache and using this method and see if you can solve that problem using different approach.

Related

Does ApiGen 5 support multiple file extensions?

I am using ApiGen 5.0.0-RC3, and I cannot figure out how to get it to search for .class files and .inc files as well as .php files.
My question is twofold: firstly, is it possible to get ApiGen to recognize .class files, and secondly, if it is possible, how would one go about it?
I found a way... but it's pretty hacky. I'm posting it here in the hopes that it might be able to help someone else.
The solution is not actually in ApiGen, but in roave/better-reflection. Specifically, in the file src/SourceLocator/Type/FileIteratorSourceLocator.php, in the method getAggregatedSourceLocator, in an anonymous function.
Replace:
private function getAggregatedSourceLocator() : AggregateSourceLocator
{
return $this->aggregateSourceLocator ?: new AggregateSourceLocator(array_values(array_filter(array_map(
function (\SplFileInfo $item) : ?SingleFileSourceLocator {
- if (! ($item->isFile() && pathinfo($item->getRealPath(), \PATHINFO_EXTENSION) === 'php')) {
return null;
}
return new SingleFileSourceLocator($item->getRealPath());
},
iterator_to_array($this->fileSystemIterator)
))));
}
With:
private function getAggregatedSourceLocator() : AggregateSourceLocator
{
return $this->aggregateSourceLocator ?: new AggregateSourceLocator(array_values(array_filter(array_map(
function (\SplFileInfo $item) : ?SingleFileSourceLocator {
+ $flag = in_array(pathinfo($item->getRealPath(), \PATHINFO_EXTENSION), ['php', 'class']);
+ if (! ($item->isFile() && $flag)) {
return null;
}
return new SingleFileSourceLocator($item->getRealPath());
},
iterator_to_array($this->fileSystemIterator)
))));
}
Works as of commit 47b76f7, version something

How does codeigniter threats libraries loaded more than once?

Could someone explain me how does Codeigniters handle the load of a library previously loaded?
Are the library loaded once again?
Does it simply jumps into the next function call?
This is something that might happen accidentally. Recently, while working on a project, I created an hook for post_controller_constructor and inside of it i start loading a class to enhance the functionality of my website.
With the hook on place i forgot to remove the old load library call from my controllers.
Curiously, nothing of wrong happened.
I was expecting an exception telling me that the library was already loaded or something like that.
CI checks if library is not set.
As you can see in this code :
public function library($library = '', $params = NULL, $object_name = NULL)
{
if (is_array($library))
{
foreach ($library as $class)
{
$this->library($class, $params);
}
return;
}
if ($library == '' OR isset($this->_base_classes[$library]))
{
return FALSE;
}
if ( ! is_null($params) && ! is_array($params))
{
$params = NULL;
}
$this->_ci_load_class($library, $params, $object_name);
}
system\code\Loader.php

Phalcon backup view path

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

how to turn off loading the layout in ZfcAdmin module for ZF2

i'm trying to turn off the layout for some controllers in zfc-admin. Unfortunately all the methods i've found does the exact opposite. turns off the views and loads the layout.
Eg.
$viewModel = new ViewModel();
$viewModel->setTerminal(true);
return $viewModel;
Is something in the configuration of ZfcAdmin that disturbs the usual functionality of setTerminal() method ?
As another temporary solution, you can edit ZfcAdmin\Module.php to fix this bug. Change like this:
public function selectLayoutBasedOnRoute(MvcEvent $e)
{
$app = $e->getParam('application');
$sm = $app->getServiceManager();
$config = $sm->get('config');
if (false === $config['zfcadmin']['use_admin_layout']) {
return;
}
$match = $e->getRouteMatch();
if (!$match instanceof RouteMatch || 0 !== strpos($match->getMatchedRouteName(), 'zfcadmin')) {
return;
}
$layout = $config['zfcadmin']['admin_layout_template'];
$controller = $e->getTarget();
if( ! $controller->getEvent()->getResult()->terminate() ) // Add by Vinicius Garcia, to fix ->setTerminal() bug (https://github.com/ZF-Commons/ZfcAdmin/issues/8)
$controller->layout($layout);
}
Just add the if( ! $controller->getEvent()->getResult()->terminate() ) before set the layout will solve the problem.
of course, It's a bad practice change code of a third-party module, but I guess it's better than include extra code in all your views that need this...
When ZF-Commons fix the bug you can just override the module, using their solution.
The code you provide in your question disables layout rendering and only outputs the view of the action.
Can you clarify your question..?
As a temporary solution, following Jurian Sluiman comment, when you want to disable layout you can simply add a get parameter to the ( /?disableLayout=true) when you call a needed action, and in the layout
if (isset($_GET['disableLayout']) && $_GET['disableLayout'] == 'true') die();
or something similar (adjust to your needs)

Is it a good idea and reliable to branch out on the php version number?

I'm working on some new components in my framework for a gettext implementation. It needs to support the dead php4 and php5+. I'm using Zend_Translate and I will implement a gettext based class for php4.
I'm wondering if it's ok to rely on the phpversion function to branch out on which class to implement. Something like...
$version = phpversion();
define('VERSION', grabVersion($version) ); // grab major version
if ( VERSION >= 5 ) {
$local = new Zend_Translate();
} else {
$local = new Gettext();
}
Both classes have a _ function to do the translations, so the rest should be the same.
<label for="first_name"><?php echo $local->_("First Name");?></label>
Is it common to do so in any of the major php frameworks, would you do something similar if you were forced to support the dead PHP4?
If I was going to implement this across PHP versions, I would rely on the version number too. So what you're proposing seems perfectly sane and acceptable.
It's not a terrible idea to branch on a php version number, but I would prefer something like this:
if(class_exists("Zend_Translate")) {
$local = new Zend_Translate();
} else if(class_exists("Gettext")) {
$local = new Gettext();
} else {
throw new Exception("No supported translation helper found");
}
If you wanted to, you could make it more dynamic:
$translators = array(
"Zend_Translate",
"Gettext",
"FooTranslate",
);
$local = null;
foreach($translators as $t) {
if(class_exists($t)) {
$local = new $t();
break;
}
}
if($local === null) {
throw new Exception("No supported translation helper found");
}
A better place to branch based on version, is error handling - above I use exceptions, which are not available in PHP4 :)
I think getting the version number is a good idea, but it should be implicit so it's not called out of the blue.
$local = TranslateFactory::create();
$local->_("translate me");
class TranslateFactory {
private static $_translator;
private static function _getTranslator() {
if(empty(self::$_translator)) {
// get php version
// if-case here
self::$_translator = // result your if-case
}
return self::$_translator;
}
public static function create() {
return _getTranslator();
}
}
That provides a per-request cache at least. The _getTranslator() could even fetch object from a longer lasting cache if you'd need it, just by putting the logic in that one spot fetching your real object.

Categories