Jump to content
porl

Twig

Recommended Posts

I know, I know....

I really like the way the Twig templating engine works. I tried to hack up a module (messy, with hard coded paths etc. just to test) but I'm somewhat out of my depth.

I tried to make a module that had a before-render hook with this:

require_once '/path/to/lib/Twig/Autoloader.php';

Twig_Autoloader::register();

$loader = new Twig_Loader_Filesystem('/path/to/templates');

(with paths adjusted of course)

and a property hook (Page::twig) that returns the twig object below

$twig = new Twig_Environment($loader, array(

'cache' => '/path/to/compilation_cache',

));

This is all taken from http://twig.sensiolabs.org/doc/api.html

I started to get odd errors from within the Twig files themselves and got lost trying to debug.

Has anyone succeeded in getting a module to load Twig? What I would like to do is have the .php files in the templates directory initialise variables etc required and then twig load (from a 'views' folder) the twig template associated with the PW template. I know this goes against the PW idea of using PHP as a templating engine in itself, but I would really love to be able to use Twig for an upcoming project and I know others have asked about similar functionalities with Twig and other templating engines. Perhaps someone more knowledgeable than myself can point me in the right direction to get such a monstrosity of crossbreeding working?

Slightly off topic: I tried to do the right thing and use the $config->urls... variables, but kept getting errors loading the Twig php files (even though the errors showed the correct paths). This is why I just started to use things like the __DIR__ variables for first tests, but even then I was having troubles. Perhaps some sort of NameSpace conflict?

Share this post


Link to post
Share on other sites

Woo! Progress!!

I fixed the main bug and it is now working.

A couple of limitations so far.

No easy way to clear the cache, which is in a bad spot to begin with. Is there any way to integrate Twig's cache with PW's? At the moment I have just made a cache folder under my site/views folder. As the cache needs to be regenerated every time the template.php or twig view files are edited are there any suggestions for making this a simple process? I thought maybe have some url/page/?cache=clear get function? Would this be easy to do?

Still have the __DIR__ paths in the module itself. I found out the problem with the $config->urls variables was to do with my .htaccess rewritebase having a ~porl at the beginning. I couldn't find an easy way around it (changing it to /home/porl broke other things so I don't know how else to do this setting. Any ideas?).

Apart from that it seems to run brilliantly.

  • Like 3

Share this post


Link to post
Share on other sites

Still have the __DIR__ paths in the module itself. I found out the problem with the $config->urls variables was to do with my .htaccess rewritebase having a ~porl at the beginning. I couldn't find an easy way around it (changing it to /home/porl broke other things so I don't know how else to do this setting. Any ideas?).

See this: https://github.com/ryancramerdesign/ProcessWire/blob/master/htaccess.txt#L71

So it might be as easy fix as editing and uncommenting one line on .htaccess file.

Cannot really help you with other questions. Cache problem is probable easily solvable. but I don't know anything about twig so cannot help you there. Great that you got it working!

Share this post


Link to post
Share on other sites

Thanks for the input :)

I think the problem I was having with the htaccess is more to do with the way php executes require_once. I looked at the Comments module to see how php files were included in there and it is the same way I was doing (more or less) so I think I am ok.

As for the caching it was a simple matter of passing Twig and auto_reload=true setting to the array when initialising it. This way Twig will regenerate a cache file if the source files are updated. It works perfectly.

Would there be any interest here in me attempting to tidy the module up a bit? It still has some hard-coded assumptions which I don't like but since I am new to ProcessWire I'm not sure how to correctly approach them.

  • Like 1

Share this post


Link to post
Share on other sites

I am sure many people find that interesting. Maybe you should share the code in Github so others can see and contribute to it?

Share this post


Link to post
Share on other sites
Would there be any interest here in me attempting to tidy the module up a bit? It still has some hard-coded assumptions which I don't like but since I am new to ProcessWire I'm not sure how to correctly approach them.

Definitely interested--keep it up!

Regarding cache, I'm assuming it creates compiled templates and it's the PHP code that it's caching, not the content? If that's the case, there's not much use in trying to hook into PW's cache timing because it's all for output caching. You'd need something that can tell when a template file or template settings have changed and clear the cache then. PW has the ability to store dynamic info with each template, and I think we'd need to keep track of the file modification time for each template file, as well as the template modification time. Then when it sees that the file (or template) modification time is newer than the one saved with the template, it would clear the cache. Not sure how to do it just yet, but think we can figure it out. Let me know if this sounds consistent with what would be needed to you?

Share this post


Link to post
Share on other sites

Twig seems to be doing the caching pretty well now that I set the options correctly. Is it considered good form to create a 'twig' directory in assets/cache or should I keep it out of there?

As an exercise I ported the site files from the default install (not all of them but enough for basic-page.php pages to work). It wasn't too difficult, although there were a couple of small 'gotchas' there. It is working well enough that I am seriously considering using it for a real project.

I'll tidy things up a bit and look into posting on github or something as apeisa suggested.

Share this post


Link to post
Share on other sites

That's great if Twig is keeping track of knowing when to clear cache files. I see no problem with putting them in /assets/cache/twig -- that seems like the best place. I look forward to checking out your implementation!

Share this post


Link to post
Share on other sites

Here's my first attempt: https://github.com/p...ProcessWireTwig

Sorry about the manual steps to get it going. I haven't had a chance to test things from scratch so let me know if I have left anything out of the README or if I have missed a file/setting.

The example templates should essentially copy the basic-page.php template from the example site. It isn't pretty (especially the outer wrapper layout.html) as I just did a straight copy (rather than designing a site with Twig in mind) and haven't done any formatting tidy-up but should hopefully get someone started.

Let me know what you think!

Edit:

Should I add code to the ___install() and ___uninstall() functions to create the views and cache folders (and delete the cache folder on uninstall)? Can the uninstall routine give a checkbox to choose to delete the twig views folder?

  • Like 1

Share this post


Link to post
Share on other sites

Porl, tested out here and seems to work great! Thanks for your work with this.

I did run into an error with the paths during installation, though not sure others will as it might have been user error on my part. However, I recommend updating the way reference paths. Don't bother with creating $this->dirname. Instead, do this:

// replace this
require_once $this->dirname . '/Twig/Autoloader.php';
// with this: 
require_once $this->config->paths->PageTwig . 'Twig/Autoloader.php';

// replace this
$loader = new Twig_Loader_Filesystem($this->dirname . '/../../views/');
// with this
$loader = new Twig_Loader_Filesystem($this->config->paths->templates . 'views/');

// replace this
array('cache' => $this->dirname . '/../../assets/cache/twig/',
// with this
array('cache' => $this->config->paths->cache . 'twig/',

Also some questions/comments about this:

/**
* initialises each field of $page. this is overkill but can be used if you don't want to keep
* adding individual fields to the controllers.
*/
function twig_init_fields() {
 foreach ($pages->fields as $field) {
   $twig_vars['page']->$field = $page->$field;
 }
}

I don't think that your current twig_init_fields() function will work, because the $pages variable is out of scope. Besides, I think you wanted that to be $page instead? If so, you'd want to use wire('page') since $page would also be out of scope in that function. But even if that's the case, I'm confused because when you set $twig_vars['page'] = $page; shouldn't you then be able to already access all the properties of $page? For the same reason, are these lines in twig-test.php even necessary?

$twig_vars['page']->body = $page->body;
$twig_vars['page']->title = $page->title;

It doesn't seem like they should be. But then I tried commenting out the first one for $page->body; and got an error about Page::body() being a non existing function. So it looks like Twig is doing an isset($page['body']); and if it returns false it's trying to call $page->body() as a function. Now it all makes sense. I think I will update the Page::__isset() function to return true when a valid field for the page is requested. That would solve this and prevent you from having to initialize any variables in this manner. And make PW more template-engine friendly, as I'm assuming others may work like this too. Just tried updating Page::__isset() and it does seem to resolve it.

Btw, the reason that you wouldn't want to do something like twig_init_fields() is that it would cause all of the page's data to be loaded, regardless of whether it would be used. This would cancel the benefit of PW's on-demand/selective data loading.

Your module has me wondering if it wouldn't be a lot simpler for people to use if the Twig parsing happened automatically on the template fields without them having to delegate views, setup $twig_vars, include twigdefaults.php, or call $page->twig->render manually. What if your /site/templates/*.php could just be your Twig views, ready to go? I'm thinking it may be possible with something like this in an autoload module:

public function init() {
 $this->addHookBefore('TemplateFile::render', $this, 'hookRender');
}

public function hookBeforeRender($event) {

 $templateFile = $event->object; 
 $filename = $templateFile->filename; 

 // use some condition to know which templates should use Twig
 // here we just use filenames that start with "twig_", but might be more elegant to configure which 
 // templates use twig in the module configuration or something
 if(strpos($filename, 'twig_') !== 0) return;

 // init twig

 require_once $this->config->paths->PageTwig . 'Twig/Autoloader.php';
 Twig_Autoloader::register();
 $loader = new Twig_Loader_Filesystem($this->config->paths->templates); 
 $twig = new Twig_Environment($loader, array('cache' => $this->config->paths->cache . 'twig/', 'auto_reload' => true));

 // substitute a pre-defined placeholder file to replace the original template file
 // so that TemplateFile::render() is actually rendering our placeholder instead: 
 $templateFile->setFilename($this->config->paths->PageTwig . 'placeholder.php'); 

 $templateFile->set('twig_filename', $filename); // original template filename 
 $templateFile->set('twig', $twig); // twig instance

}

Then the contents of the placeholder template: /site/modules/PageTwig/placeholder.php

<?php
$twig_vars = Wire::getAllFuel()->getArray(); 
echo $twig->render($twig_filename, $twig_vars); 

I haven't tried this yet, but I think it may work. If I get a little more time here in the next day or two to try this stuff rather than just write about it, I'll definitely give it a try unless you get to it first.

  • Like 1

Share this post


Link to post
Share on other sites

Porl, tested out here and seems to work great! Thanks for your work with this.

I did run into an error with the paths during installation, though not sure others will as it might have been user error on my part. However, I recommend updating the way reference paths. Don't bother with creating $this->dirname. Instead, do this:

// replace this
require_once $this->dirname . '/Twig/Autoloader.php';
// with this:
require_once $this->config->paths->PageTwig . 'Twig/Autoloader.php';

// replace this
$loader = new Twig_Loader_Filesystem($this->dirname . '/../../views/');
// with this
$loader = new Twig_Loader_Filesystem($this->config->paths->templates . 'views/');

// replace this
array('cache' => $this->dirname . '/../../assets/cache/twig/',
// with this
array('cache' => $this->config->paths->cache . 'twig/',

I tried to change the values as you suggested but I still run into errors on my test site. As I stated in a previous post I believe this is because I am using a .htaccess RewriteBase rule that starts with /~porl/. I think that php doesn't like require_once with paths like this (as the config variables seem to output the rule with the ~...). I tried changing it to /home/porl/... and the require_once started to work, but the site itself stopped working. If this is considered an edge case then I can just move my site out of the ~porl folder and do away with that rewrite rule, but I wanted to get the code working if someone else was in the same situation as myself if possible. If there is a better way to do this I'm happy to change it.

Also some questions/comments about this:

/**
* initialises each field of $page. this is overkill but can be used if you don't want to keep
* adding individual fields to the controllers.
*/
function twig_init_fields() {
 foreach ($pages->fields as $field) {
$twig_vars['page']->$field = $page->$field;
 }
}

I don't think that your current twig_init_fields() function will work, because the $pages variable is out of scope. Besides, I think you wanted that to be $page instead? If so, you'd want to use wire('page') since $page would also be out of scope in that function. But even if that's the case, I'm confused because when you set $twig_vars['page'] = $page; shouldn't you then be able to already access all the properties of $page? For the same reason, are these lines in twig-test.php even necessary?

$twig_vars['page']->body = $page->body;
$twig_vars['page']->title = $page->title;

It doesn't seem like they should be. But then I tried commenting out the first one for $page->body; and got an error about Page::body() being a non existing function. So it looks like Twig is doing an isset($page['body']); and if it returns false it's trying to call $page->body() as a function. Now it all makes sense. I think I will update the Page::__isset() function to return true when a valid field for the page is requested. That would solve this and prevent you from having to initialize any variables in this manner. And make PW more template-engine friendly, as I'm assuming others may work like this too. Just tried updating Page::__isset() and it does seem to resolve it.

Btw, the reason that you wouldn't want to do something like twig_init_fields() is that it would cause all of the page's data to be loaded, regardless of whether it would be used. This would cancel the benefit of PW's on-demand/selective data loading.

I agree completely with the overkill of loading all fields (and you were right about the $pages typo). As you discovered the reason I had to call each field separately (or make the horrible full-loading thing) was because the way twig checked the fields made it give undefined errors if i didn't. If it is possible to fix the __isset() property to remove the need for this it would make things much cleaner. I have removed the field 'autoloader' in the meantime as I didn't want it there to begin with (hence the fact that I didn't call it from anywhere).

Your module has me wondering if it wouldn't be a lot simpler for people to use if the Twig parsing happened automatically on the template fields without them having to delegate views, setup $twig_vars, include twigdefaults.php, or call $page->twig->render manually. What if your /site/templates/*.php could just be your Twig views, ready to go? I'm thinking it may be possible with something like this in an autoload module:

public function init() {
 $this->addHookBefore('TemplateFile::render', $this, 'hookRender');
}

public function hookBeforeRender($event) {

 $templateFile = $event->object;
 $filename = $templateFile->filename;

 // use some condition to know which templates should use Twig
 // here we just use filenames that start with "twig_", but might be more elegant to configure which
 // templates use twig in the module configuration or something
 if(strpos($filename, 'twig_') !== 0) return;

 // init twig

 require_once $this->config->paths->PageTwig . 'Twig/Autoloader.php';
 Twig_Autoloader::register();
 $loader = new Twig_Loader_Filesystem($this->config->paths->templates);
 $twig = new Twig_Environment($loader, array('cache' => $this->config->paths->cache . 'twig/', 'auto_reload' => true));

 // substitute a pre-defined placeholder file to replace the original template file
 // so that TemplateFile::render() is actually rendering our placeholder instead:
 $templateFile->setFilename($this->config->paths->PageTwig . 'placeholder.php');

 $templateFile->set('twig_filename', $filename); // original template filename
 $templateFile->set('twig', $twig); // twig instance

}

Then the contents of the placeholder template: /site/modules/PageTwig/placeholder.php

<?php
$twig_vars = Wire::getAllFuel()->getArray();
echo $twig->render($twig_filename, $twig_vars);

I haven't tried this yet, but I think it may work. If I get a little more time here in the next day or two to try this stuff rather than just write about it, I'll definitely give it a try unless you get to it first.

I think this looks interesting, but I will have to admit it is a little out of my depth (especially with regards to my knowledge of ProcessWire workings so far). If it helps, the twig templates are now named with the following convention: [templatename].[templatetype].twig

eg: basic-page.html.twig or user-list.json.twig etc. This might make it easier to detect a twig template (of any type, not just html output).

On another note, I cleaned up the templates a little and made them a little more 'twiggy' (still not perfect but much better). I also added a template for the sitemap page, search page and the home page (this is just a copy of basic-page) to demonstrate replacing the entire front site with twig. I didn't touch the admin template of course but I don't think I'm going to rebuild the entire backend section just yet :). Let me know what you think.

Share this post


Link to post
Share on other sites
I tried to change the values as you suggested but I still run into errors on my test site. As I stated in a previous post I believe this is because I am using a .htaccess RewriteBase rule that starts with /~porl/. I think that php doesn't like require_once with paths like this (as the config variables seem to output the rule with the ~...). I tried changing it to /home/porl/... and the require_once started to work, but the site itself stopped working.

When you tried it before, are you sure that you were using $config->paths (not $config->urls)? The $config->paths shouldn't have the "~" in it, whereas the $config->urls should. The reason is that "~" is a virtual directory symbol that is used and interpreted by Apache. But it's not something that actually exists in the file structure, or at least it's not supposed to.

Though if you actually have a "~" symbol in your directory path name, then all bets are off as I'm guessing this would confuse not just ProcessWire but Apache too. :)

Based on what Apache uses "~" for, I have a feeling you might have tried to use $config->urls rather than $config->paths. After all, the rest of ProcessWire and all it's modules are already using the $config->paths and $config->urls variables where appropriate. So if there were a problem with using them then I don't think you'd be able to run ProcessWire at all.

If it is possible to fix the __isset() property to remove the need for this it would make things much cleaner.

I have made the change on my system, but need to do some more testing to make sure it doesn't break anything. What we're doing is asking the Page::__isset() function to behave like the Page::has() function, so it's not really the correct behavior for isset to return true just because the field is valid for the page. So I'm thinking of providing a $config var that can be used to override the behavior, rather than just making the change permanent. That way a template engine module like this could customize the behavior towards the need.

On another note, I cleaned up the templates a little and made them a little more 'twiggy' (still not perfect but much better). I also added a template for the sitemap page, search page and the home page (this is just a copy of basic-page) to demonstrate replacing the entire front site with twig.

Thanks for the update! I look forward to taking a closer look.

Share this post


Link to post
Share on other sites

I got the config variable working, but I had to add $this->config = wire('config'); to the init() function. I'm not sure this is a good idea but it seems to work. If I don't add this then print_r($this->config) or print_r($config) returns nothing. After adding it print_r($this->config) returns the config correctly. Am I missing something in the inheritance or is this normal?

Also I noticed that the variables you have set assume the views folder lies underneath the templates folder (as opposed to next to it in the tree). This may have been the error you had initially. I think it makes more sense now to have it how you have it set so I have moved it there anyway.

Edit: one other thing. Should I keep the module named as is or is there a better naming convention I could use (maybe RenderTwig)?

Share this post


Link to post
Share on other sites
I got the config variable working, but I had to add $this->config = wire('config'); to the init() function. I'm not sure this is a good idea but it seems to work. If I don't add this then print_r($this->config) or print_r($config) returns nothing. After adding it print_r($this->config) returns the config correctly. Am I missing something in the inheritance or is this normal?

It's perfectly fine to do what you are doing, but I'm not sure why $this->config; wouldn't work when wire('config'); would. They both pull it from the same place. But $this->config can stop working if you override WireData's get() or __get() methods with one that doesn't continue to call the original. However, it doesn't look to me like your module is doing that, so I'm not sure why $this->config isn't working. Just stick with wire('config') if it works, because the two are equivalent.

Also I noticed that the variables you have set assume the views folder lies underneath the templates folder (as opposed to next to it in the tree). This may have been the error you had initially. I think it makes more sense now to have it how you have it set so I have moved it there anyway.

Well I was thinking that ultimately we could make /site/templates/ the views folder for Twig, but until then I was intending to just stick with what you were doing before with /site/templates/views/. Anything else was a mistake on my part :)

Edit: one other thing. Should I keep the module named as is or is there a better naming convention I could use (maybe RenderTwig)?

That's up to you, but I suppose that RenderTwig or even TemplateTwig or something might make more sense.

Share this post


Link to post
Share on other sites

I think TemplateTwig is a great idea so I've renamed the module to this.

If the __isset() method gets changed (or some other way of getting Twig to play nice here. I have no idea how difficult or useful it would be to 'override' Twig to use the has() function instead of isset(). Any suggestions?) then I think the module is essentially 'done' other than perhaps some install/uninstall routines. Is there anything that anyone thinks should go in them (create the views directory and optionally remove on uninstall...)?

Share this post


Link to post
Share on other sites

Sounds good. I should be able to get this isset() update into the core early next week. I'm going to initially keep it as a config option, which your module can set dynamically. That way we aren't taking much risk just in case changing the isset() behavior is a problem elsewhere.

Share this post


Link to post
Share on other sites

sounds great :)

I am looking forward to seeing how it works together. Thank you for all your help and input.

Share this post


Link to post
Share on other sites

Porl, yesterday I put in a commit that enables you to modify the behavior of the Page::__isset() function. For the PageTwig module, you are going to want to put this in your __construct() or init() method:

Page::$issetHas = true;

More info in the commit notes:

https://github.com/ryancramerdesign/ProcessWire/commit/40ccfe2a05772f51a97c8a58e2c8721dfc0b51dd

Thanks,

Ryan

Share this post


Link to post
Share on other sites

It is working well, thanks.

I am still having some issues where I can't access object properties directly (say page.sidebar) and have to use the get method (page.get("sidebar")) or it fails to execute.

I'm not sure how to diagnose this, but I believe it is usually when the field is present but empty.

As an example:

$page->summary is a valid but empty field. twig gets this passed to it as page.summary

{% if page.summary is defined %} {# this returns as true #}
{%if page.summary is not empty %} {# triggers an error as below #}
...

Error:

Error Exception: An exception has been thrown during the rendering of a template ("Method Page::summary does not exist or is not callable in this context")

As stated above, I can use page.get("summary") in these circumstances, but sometimes it is not clear which to use (an example is the layout.html.twig template which is extended by others - it is sometimes hard to know if page.summary will work on all pages).

Any ideas?

Thanks again for your time.

Share this post


Link to post
Share on other sites

Are you sure you've got the latest commit? The Page::$issetHas = true; is what is designed to fix this exact issue. Make sure that you are setting Page::$issetHas = true; (exactly like that) before you initialize Twig.

But I'm assuming you've done all this. As a result, the only reason I can think of why you'd get this error is if the page that was checked didn't actually have a 'summary' field in it's template. The error you are getting is from ProcessWire and it's throwing the error because Twig is trying to call a function called $page->summary(); and that function does not exist. So on the surface, it would look to me like Twig tries to call a $page->summary(); function if it finds that $page->summary; is blank. It seems like an unusual behavior... Twig doesn't accept something being blank and instead starts making arbitrary function calls using the property name. Maybe there is a way to turn off that behavior in Twig's config?

Share this post


Link to post
Share on other sites

Just a couple of days ago I read about TWIG's tag processing logic here. It goes like this:

For convenience sake foo.bar does the following things on the PHP layer:

  • check if foo is an array and bar a valid element;
  • if not, and if foo is an object, check that bar is a valid property;
  • if not, and if foo is an object, check that bar is a valid method (even if bar is the constructor - use __construct() instead);
  • if not, and if foo is an object, check that getBar is a valid method;
  • if not, and if foo is an object, check that isBar is a valid method;
  • if not, return a null value.

foo['bar'] on the other hand only works with PHP arrays:

  • check if foo is an array and bar a valid element;
  • if not, return a null value.

Hope it helps.

Share this post


Link to post
Share on other sites

Are you sure you've got the latest commit? The Page::$issetHas = true; is what is designed to fix this exact issue. Make sure that you are setting Page::$issetHas = true; (exactly like that) before you initialize Twig.

But I'm assuming you've done all this. As a result, the only reason I can think of why you'd get this error is if the page that was checked didn't actually have a 'summary' field in it's template. The error you are getting is from ProcessWire and it's throwing the error because Twig is trying to call a function called $page->summary(); and that function does not exist. So on the surface, it would look to me like Twig tries to call a $page->summary(); function if it finds that $page->summary; is blank. It seems like an unusual behavior... Twig doesn't accept something being blank and instead starts making arbitrary function calls using the property name. Maybe there is a way to turn off that behavior in Twig's config?

I did a little more digging after reading slkwrm's post and found the following:

The case that I am getting this on is on the search page. This doesn't have a summary field (as you correctly guessed). the page.get("summary") I assume just returns null (may be wrong) and twig just treats it as 'empty'. When calling page.summary I get the error "Method Page::summary does not exist or is not callable in this context".

I assume that the way Twig attempts the check for the summary property and summary() method is somehow triggering ProcessWire to give the error (and so Twig does not get to the point where it returns a null value). Is there any way to stop this from happening (not sure if it is ProcessWire itself catching the error or just formatting a PHP error) and allowing Twig to test these 'non existent' methods and properties?

Edit: I just had the thought that since any overriding of the above behaviour is 'messy' then somehow making the Twig test of 'is defined' work correctly is probably better (sorry I forgot about this). By this I mean that the code block as follows executes the inside of the statement as if the 'is defined' has returned true:

{% if page.summary is defined %}
 This text is rendered even though page.summary should not be defined in this case.
 any use of page.summary in here (instead of page.get("summary") will trigger an error
 but the 'if page.summary is defined' statement executes and evaluates as true.
{% endif %}

If the first statement returns false (as I believe it should in this case, as the search template on the default site has no summary field) then hopefully the code will not trigger an error as the contents inside will not execute. I think this is actually a larger bug than I thought (where the bug is though I have no idea, ie. Twig or ProcessWire or even my incorrect assumption of what 'is defined' should be).

Share this post


Link to post
Share on other sites

Ok after further investigating I found that the lines in Wire.php as below


public function __call($method, $arguments) {
$result = $this->runHooks($method, $arguments);
if(!$result['methodExists'] && !$result['numHooksRun'])
throw new WireException("Method " . $this->className() . "::$method does not exist or is not callable in this context");
return $result['return'];
}

are causing the error. Obviously this is intentional behaviour (and makes sense for 99% of cases) but is there a way to change this behaviour (similar to the isset() change) that can be enabled when using templating engines like twig that do their own tests for methods etc? Is this a bad idea?

I am still not sure why the 'is defined' check appears to return true even when the property/method does not exist. The API reference page is here if it is of any help.

OOH I just discovered something...

In the 'compiled' template (the output php file in the twig cache) the line {% if page.summary is defined %} gets translated to the following:

if (isset($context["page"])) { $_page_ = $context["page"]; } else { $_page_ = null; }

As you can see it is only taking the 'page' part of the variable into account (ignoring the '.summary' part). I assume that Twig only does basic tests for variables and not their properties. This seems odd too me but I'm sure there are reasons that are beyond my knowledge :D

With this in mind, is there any way to stop the exception being triggered (apart from removing or commenting out those lines) when Twig does it's tests?

Thanks

Edit: I just adjusted the templates to be more 'Twig-like' with regards to the page.summary field. I am using a block override in the pages that have one and just the homepage's summary by default. This gets around the issue in the case that I have. I'm not sure if the problem is still a valid issue for other use cases or not.

Share this post


Link to post
Share on other sites
Obviously this is intentional behaviour (and makes sense for 99% of cases) but is there a way to change this behaviour (similar to the isset() change) that can be enabled when using templating engines like twig that do their own tests for methods etc? Is this a bad idea

I think we may be able to make this behavior configurable like we did with isset. I don't necessarily think it's a great thing to do, because it could add some really difficult to debug situations in site development. There's a reason why PHP throws an error when you call a non-existant method, and ProcessWire is trying to be consistent with that behavior of PHP. But just disabling the exception when Twig is active might not be a problem. I know you'd mentioned you found a workaround, but let me know if you still find this to be worthwhile.

  • Like 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...