Jump to content

Template Data Providers


marco
 Share

Recommended Posts

Hi all,
 
I just released my first module on github called Template Data Providers.
 
This module lets you create simple data provider classes for templates and chunks (a.k.a. partials, blocks, includes, ...) to gather and prepare data for templates and/or handling form data and other actions outside of the templates ("separation of concerns").
 
But even if you've installed the module the usage of the new functionality is merely optional. You may define your simple templates and chunks as usuals, adding custom classes (PageDataProvider for pages/templates, ChunkDataProvider for chunks) for more complex data handling on demand.
 
I provided detailed instructions in the README.md and encourage you to read it for further information.

Just some sample code here:

Defining a data provider for a home template:

class HomePage extends \nw\DataProviders\PageDataProvider {
    
    public function populate() {

        $this->foo = 'bar';         // definevariable $foo to use within the page's template
        $this->page->foo = 'baz';   // provides page member $page->foo to use within the page's template
    }
}

Calling a chunk from within a template:

$page->renderChunk('path/to/primary-navigation.php'); // relative to site/templates/

Calling a chunk providing contextual data:

$news = $pages->get('template=news');
foreach ($news as $newsItem) {
    $page->renderChunk('path/to/news-item.php', $newsItem); 
    // additional arguments provided will be avaiable within the chunk in a closed scope
}

Defining an example chunk data provider:

class ExampleChunk extends \nw\DataProviders\ChunkDataProvider {
   
    public function setContext(array $context = array()) {

        // store first context argument in $this->foo
        $this->foo = isset($context[0]) ? $context[0] : null;

        // store second context argument in $this->bar if instance of \Page
        $this->bar = null;
        if (isset($context[1]) && $context[1] instanceof \Page) {
            $this->bar = $context[1];
        }
    }
    
    public function populate() {

        $this->foo = 'bar'; // provides variable $foo to use within the chunk
    }
}

Please leave your questions, remarks, error reports here in the forum or at github and I'll try to answer as soon as possible.

This module is a preparation for another module coming soon. This will be an alternative Twig template engine module that will interact with Template Data Providers or could be used as stonde-alone module.

Regards from Hanover/Germany,

Marco

  • Like 3
Link to comment
Share on other sites

The post seems to have been deleted. I will restore it.

edit: even better, the post was removed because the content was exactly the same. I moved this one to the forum "modules/plugins" instead of restoring the other.

Marco I edited your post to remove the link.

Link to comment
Share on other sites

  • 6 months later...

Hi marco,

unfortunately the example for a home page template, mentioned above, doesn't work in my case.

I installed the module, left the default setting dataproviders, created this very folder in site/templates/ (checked its permissions), placed a HomePage.php with your example code in it, opened home.php and tried to echo $foo. With no result. Am I misunderstanding the concept, or did I forget a neccessary step to make this work?

Thanks in advance!

marcus

PS: PW 2.4, Module version 1.0.2

  • Like 1
Link to comment
Share on other sites

Hey Marcus,

as Marco is currently not available i'll try to answer your questions. Normally what you did should be correct.

Your structure should now look like this:

templates

--- | dataproviders

--- | ---HomePage.php

home.php

If this is the case you can try to debug if the dataprovider is loaded correctly. If you just place a "die('test')" inside it's populate method this should be the only output that is displayed on the homepage as the populat method is executed before rendering the actual template.

If the template renders correctly instead of just displaying the text "test" there is a problem with loading the dataprovider. This (from my experience) is mostly the case if either the classname of the dataprovider is misspelled or (as stated above) there is a problem with a case sensitive filename.

If this doesn't help just contact me here or via skype and we'll debug your problem together. 

Best Regards

Felix

Link to comment
Share on other sites

Hi Felix,

greetings to Hannover and thanks for your answer. die(test) works and proves the dataprovider is loaded at all. But the value of $foo still isn't provided to home. See my code below in both files:

home.php: 

<?php 

/**
 * Home template
 *
 */

echo "This should be the value of foo: " . $foo;

HomePage.php:

<?php

class HomePage extends \nw\DataProviders\PageDataProvider {

	public function populate() {

		$this->foo = 'bar';
		$this->page->foo = 'baz';
		//die('test');
	} 
}

But the output is just "This should be the value of foo:", without any 'bar' added.

Overall, its just testing a different way to populate/architect my templates, so no show stopper at all - but thanks for the offer via Skype!

Marcus

Link to comment
Share on other sites

Hi marcus,

greetings to berlin! ;)

Well: This is sort of strange. Your examples seem to be absolutely correct.

To debug let me ask you some questions regarding your setup:

  • Which PHP Version are you using?
  • Do you have any modules installed that might conflict with TemplateDataProviders (something that installs hooks before rendering templates)?
  • Have you tried setting some different variables (i.e. clearing the "foo" example and setting something else)? 

Felix 

Link to comment
Share on other sites

  • The testing environment is an out-of-the-box MAMP 3 virtual host, meaning PHP 5.5.10 - but I could try to downgrade, since it will really take some time until this version hits my customers web hosters ;)
  • Just MarkupSimpleNavigation, ManageFiles, FieldtypeCropImage -  just installed, none of them initiated in any form - apart from that the installation is totally empty / unconfigured
  • Yeah, different variable name, integer instead of string, but same effect

edit: 5.4.25, 5.3.28 - no difference

Edited by marcus
Link to comment
Share on other sites

Hi Marcus,

PHP 5.5 shouldn't be a problem. I'll hack together a working site-profile that you can download and check against your code today. I'm pretty sure both of us are just missing/overseeing something important. We're using this module in pretty large projects and didn't find any bugs yet. :)

Link to comment
Share on other sites

I tested it with a fresh setup and got the same result you did. We're always using this module combined with TemplateTwigReplace so I wasn't aware that this happens when using it with "plain php" :). If you'd install TemplateTwigReplace and echo the variable using {{ bar }} everything would work as expected. But I'm sure not everyone likes using twig as much as we do. Also this shouldn't be a dependency. 

I'll ask Marco about this tomorrow and keep you updated. 

Link to comment
Share on other sites

Hey marcus. It seems like there really is an issue which (most propably) is caused by a changed behaviour when doing object overloading since the 2.4 update (at least that's what marco said - I'm not that much of a "PHP Pro" :) ).

We didn't noticed this yet due to the reasons already stated. There will presumably be a fix for this next week.

Link to comment
Share on other sites

Hi felix, thanks for the updates on the issue! I haven't found time in the last days to move any further in this project - which is just a learning installation anyway - but will look into TemplateTwigReplace on the weekend. I better be learning twig sooner or later, if not for PW then for Drupal 8 ;)

Link to comment
Share on other sites

  • 6 months later...

For me this Module is the best approach for organizing logic and views without being too much in your face with restrictions.

I tested it yesterday, and like it very much and discovered the same problem as @marcus without knowing it has been posted here.

I created an issue: https://github.com/marcostoll/processwire-template-data-providers/issues/2

What's the status of this module? Will it be maintained further?

  • Like 1
Link to comment
Share on other sites

Hey owzim. I'm glad you like the module. We're still using it in all of our projects (but as I've already mentioned combined with TemplateTwigReplace) so it has to be maintained further even if it's only for our own projects.

I'll look into this issue and try to do my best in the next days. Sadly marco (who is a great software engineer - a lot better than me) isn't working for neuwaerts anymore but if I can't resolve this issue by myself I'm sure he'll help us fix it.

  • Like 3
Link to comment
Share on other sites

Actually beforePageRender doesn't change any output or replaces vars. It just adds data to the page object. So imo there should be no need to replace the default render method.

I tracked the problem down to the Core-File "PageRender.module" (line 305) where output = $page->output(true); gets called. $page->output(true) returns a new, uncached TemplateFile object which is filled with a fresh "Page" Object via $fuel = self::getAllFuel(). So if there would be a way to permanently store the variables set inside the hook (I've tried using setFuel('page',$page) but that didn't work) this would be fixed. Any Ideas?

Link to comment
Share on other sites

I don't but any of the PW hackers here should.

What irritated me, is that with the Twig replace module everything works as expected, for which a reason should exist, so I looked into the code, and not only is the Twig replace module hooking into PageRender::renderPage instead of Page::render (as suggested in my GitHub issue) but also the vars are explicitly collected and passed, see https://github.com/marcostoll/processwire-template-twig-replace/blob/master/TemplateTwigReplace.module#L216

$twigVars = $this->collectVariables($page->output);
$output = $this->getTwig()->render($page->template->name . '.' . wire('config')->templateExtension, $twigVars);

So perhaps the right path would be to actually replace the render method with the same hook as above and load the respective template file and the TemplateFile class. The downside of it (and I think it's the same with Twig replace module) that the render method must be held in sync with the core version (appending, and prepending files like _init.php and such, and also the new template specific append/prepend functionality since 2.5).

I am not too much into the topic so I might just be guessing here. Dear PW hackers come one, come all.

PS/OT: this forum should support MarkDown, post authoring/editing in here is a bloody mess. >:(

  • Like 2
Link to comment
Share on other sites

Hey owzim,

this is exactly the reason why i don't want to completely replace the render method (plus i honestly don't know what would happen if the method is replaced not only by one but several modules).

I'll start a thread asking if it's possible to make the variables persistent in the module development board.

  • Like 1
Link to comment
Share on other sites

  • 5 weeks later...

I just revisited the issue and now I understand what you mean with the output thingy, found that too.
 
This makes it work, but setting data directly to the fuel leaves a bad taste in my mouth.

/**
 * Provides direct reference access to variables in $this->page->output
 *
 * Overwrites \WireData::__set
 *
 * @param string $key
 * @param mixed $value
 */
public function __set($key, $value) {
    $this->wire($key, $value);
    $this->page->output->$key = $value;
}

Just browsed through the core code to find info about the fuel, and found that in the docs of setFuel:
 

Fuel is an internal-only keyword. Unless static needed, use $this->wire($name, $value) instead.

The data sharing via output is great because all fuel data gets merged into the output, so a potential overwrite of fuel data is not possible.

setting data to the fuel directly entails the risk of overwriting stuff, which can lead to very unwanted behavior, e.g. modules that hook into after page render (or something similar) might get a messed up fuel.

Am I right? I might not be ... :rolleyes:

Sadly one cannot hook into that specific part of the page render process. Only via replacing the whole method via hook, which might lead to a crap fest as well :mellow:

Edit: I just tested it, you cannot set most of the fuel vars, like session, fields, or templates, they are locked and throw an Exception, great stuff.

You can however overwrite say, page or db (I guess they are supposed to be dynamic, because they are set by the system itself and change).

So the fuel way could work after all?

 

I created a pull request: https://github.com/marcostoll/processwire-template-data-providers/pull/6

Link to comment
Share on other sites

First of all: Thanks for your help!

I've just merged the pull request and altered some stuff. Basically I removed the part where you overwrite the fuel every time something is set. If it would have stayed like this you wouldn't have been able to overwrite fuel variables once they are set (no matter if they are proteced or not).

Now all "system" keys are stored during module initialization and the setter method checks against those. Furthermore I replaced the isset() check within the getter with is_null as isset always returned false.

  • Like 1
Link to comment
Share on other sites

  • 2 years later...

Tything to get the processwire version 3 of the module working correctly.

Admin:
Data Providers Name Space :  "pwire\DataProviders "
Data Provider Base Path : "site/templates/dataproviders/"

site/template/home.php (template)
site/template/dataproviders/pwire/HomePageDataProvider.php (data provider)

namespace pwire;
class HomePage extends \nw\DataProviders\PageDataProvider {
    public function populate() {
		echo "you made it here "; die();
    }
}


Do I also need to any something to my composer file to get the class to autoload, or just put it in the correct directory?

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...