Jump to content

Question: Understanding templates–Advanced templating


Adam Kiss
 Share

Recommended Posts

Hello all advanced users (Ryan, Antti, ?) :)

Introduction:

I am curious about how the template files get included, called, how they are run and such. Last week or so, I tried to create little MVC (or rather, V(C+M) :) ) directory skeleton, where templates itself would be classes generating data for 'views', which would be located in one of the directories in '/site/templates/'.

However, when I set up Base class, set few of my templates to use this, added View object (which in theory loads file and dumps data in it), prepared some place for common functions, and prepared autorendering in snippets (creating renderXyz function would give the template object variable called $Xyz with generated object/array of objects).

However, upon doing all of this, system felt really slow-ish, having generation time 0,5s-1s for localhost and simple, 3 or 4 field pages, which felt really unnatural - and I suspect my noobish object model :)

So this brings me to my question:

I noticed that there are TemplateFile objects, with render() function, and such, whole ProcessWire is 100% OOPhp, is there any recommended way I can use ProcessWire as any MVC framework, having autoloading, so I don't have to use require(); with objects for instance?

If this doesn't make any sense for you, I'll try rephrase later, but I hope it does make sense. :)

Link to comment
Share on other sites

Adam, that's a pretty slow page render time you had there. I don't think that can be attributed to taking an MVC approach, so am guessing there is some other factor at play that caused the bottleneck.

If you want your current template file to be a controller pulling in other views, then use the TemplateFile class and place your view files in a directory like /site/templates/views/

$t = new TemplateFile('./views/list-news.php');
$t->set('items', $pages->find('template=news, featured=1, sort=-date, limit=3'));
$out = $t->render();

Your /views/list-news.php file would look like this:

<ul>
<?php foreach($items as $item): ?>
<li><a href="<?=$item->url?>"><?=$item->title?></a> <?=$item->date?></li>
<?php endforeach; ?>
</ul>

If you are using this approach, it is far more efficient and straightforward to keep the foreach() in the view rather than calling up a new view for each item. It also gives the view the opportunity to handle an empty condition (if you want it to), and it prevents separation of the container from the items (i.e. you aren't splitting a <ul> and an <li> in separate files).

Another approach (and the one I like to use) is to use a module as a view. I'll create an autoload module that adds a 'renderNews' (or another name as applicable) to the PageArray class. Then I get the output of that view like this:

$out = $pages->find('...')->renderNews(); 

It's almost as easy with a normal, non-autoload module:

$m = $modules->get("MarkupRenderNews");
$out = $m->render($pages->find("..."));

Of course, the same approach can be done just by including a set of functions as views and calling upon them as needed, though I prefer the structure of modules for this stuff. But the end result is just as simple:

$out = render_news($pages->find('...'));
  • Like 3
Link to comment
Share on other sites

  • 3 weeks later...

I'm interested in these ideas too.

I like the CMS back-end of PW but I'm not so keen on the way it essentially drills down to a file that just spits out content.

I would prefer an architecture thay is more like a pyramid whereby the index file dispatches based on the URL, fetches the data and returns the content and header in an object back up to the starting point (the index.php again) where it is handled appropriately.

It seems like this might be possible if I can just have a rewrite rule that redirects anything non-cms to my own secondary index file where I can use the API to handle it in the way I would like e.g. much more like a modern MVC framework such as Symfony2. I'm using TWIG for templating but this is just the start.

Link to comment
Share on other sites

I like the CMS back-end of PW but I'm not so keen on the way it essentially drills down to a file that just spits out content.

I'm not sure this describes what it actually does, or I might be misunderstanding what you are trying to say. But what actually happens is that the template file assigned to the accessed page gets executed, as a PHP file. ProcessWire spits out no content. You have to specifically code your template file to spit out content. ProcessWire does not participate in this aspect, only you do.

That template file is handed a copy of $page, which represents the page that was requested by the URL. Whether you choose to do anything with that $page is up to you.

I would prefer an architecture thay is more like a pyramid whereby the index file dispatches based on the URL, fetches the data and returns the content and header in an object back up to the starting point (the index.php again) where it is handled appropriately.

This is an architecture that ProcessWire supports. Make all your pages use the same template file and this is what you will have. This doesn't mean all your pages have to use the same 'template' (unless you want them to), just the same 'template file'. To achieve that, edit each template, click 'Advanced' and set the 'Alternate Template Filename' to be the one you want to be the top of your pyramid (i.e. main.php). Another way to do it is to maintain separate template files that just include("./main.php")). Either way, all valid requests will be routed to your main.php with $page being the only thing different from request to request.

If you want requests routed to your template file that don't map to URLs in ProcessWire, then edit the template settings and turn on URL Segments in the URLs tab. Now all URLs below the page(s) using that template file will be handed any URLs below them that don't map to pages. You can then map $input->urlSegment(n) to whatever you want, whether a function/class method, another $pages->get/find call, another application, etc. This is a lot of fun to be had in here. :)

It seems like this might be possible if I can just have a rewrite rule that redirects anything non-cms to my own secondary index file where I can use the API to handle it in the way I would like e.g. much more like a modern MVC framework such as Symfony2.

You can do this, but your htaccess will have to identify the paths that get directed to your own script by looking for them with a rewrite rule. Or you'd have to put your script in a directory that is the same as the URL used to access it (or at least, the first url segment is the same). The htaccess file does not know which requests will result in 404s. It doesn't know what is "non-cms" unless the request is to a real file or directory. ProcessWire is what ultimately generates the 404.

  • Like 2
Link to comment
Share on other sites

I think I may have got the tone of that last post a bit wrong, I didn't mean to sound negative in any way.

It appears that "spitting out content" is how the tutorials presented the CMS as working, but as you say there is no necessity to do that.

I suppose what I'd really like to do (and what I've often wanted to do with CMS' I've used) is to essentially leave the back-end intact to manage the data in/out and build my own dispatcher because that way I can get as creative as I like and maintain a lot of control.

I think perhaps if I dig into the API a bit more deeply I may be able to essentially separate the whole thing in two. I can perhaps run the CMS as a subdomain and then have the principle domain for my own dispatcher, pull the page data etc myself and do with it as I please. Judging by the brief exposure I've had to the API, it appears to be very well-built and geared towards flexibility and modular design so I think it's a good candidate for this sort of thing.

Link to comment
Share on other sites

It appears that "spitting out content" is how the tutorials presented the CMS as working, but as you say there is no necessity to do that.

This is because we don't necessarily know the needs and programming level of the audience, so always try to present stuff in the most basic way possible. You already know your way around PHP, so some examples will seem simplistic. But my hope is that as people learn more, they uncover more layers and ways of doing things. For instance, the basic site profile uses a simple header/footer include system for the main markup, and each template outputs what's in between. This isn't the way I usually build sites. But I learned early on that the way I build sites doesn't connect with people until they know their way around the software. So the header/footer system is a way to relate back to the methods used by more entry-level systems like WordPress and Expression Engine. I'm also hopeful that we'll get more site profiles available for download that get into more advanced techniques that'll be better examples for the PHP coders in our audience. CMS architecture is one of my favorite topics, and I hope I didn't suggest you were being negative--you always ask good questions and I enjoy the discussion.

I think perhaps if I dig into the API a bit more deeply I may be able to essentially separate the whole thing in two. I can perhaps run the CMS as a subdomain and then have the principle domain for my own dispatcher, pull the page data etc myself and do with it as I please. Judging by the brief exposure I've had to the API, it appears to be very well-built and geared towards flexibility and modular design so I think it's a good candidate for this sort of thing.

What WillyC was referring to above is how you can bootstrap ProcessWire from another PHP application or command line script. If I understand what you are talking about correctly, this may be in line with what you want to do because you could create your own index.php and include() ProcessWire's index.php. From that point forward, you have a wire('api var') function that gives you access to the entire PW API, leaving you in control of everything else. Here's another really basic example: if you wanted to grab your /sitemap/ page and render it:

test.php:

<?php
include("/path/to/processwire/index.php");
$page = wire('pages')->get('/sitemap/');
echo $page->render();
Link to comment
Share on other sites

I'm having a play with bootstrapping and using alternate tools for request/response and templating.

I'm a huge fan of the Symfony2 framework and it's constituent components. If you haven't had a look I'd recommend it, the developers seem to have a very strong grasp on clean, modern, best-practise architecture. In terms of PHP frameworks and code quality I think it's the best I've ever seen, Fabien Potencier seems to be very forward-thinking.

My ultimate aim is to have an "admin.mysite.com" domain for the back-end and then write my own dispatcher that will live at "mysite.com". At this stage it seems entirely plausible, but I can't work out how to make the admin subdomain/site ONLY deal with CMS-realted page requests. In fact it'd be great to be able to filter out the "front" page requests and also get rid of any "/processwire" part of the URL for CMS requests so it looks cleaner.

All the above are non-essential though, it's more my own curiosity than anything!

Link to comment
Share on other sites

I'm a huge fan of the Symfony2 framework and it's constituent components.

I agree with all your points, it's a great framework. Admittedly it's rare that I use a traditional framework as most of the work I do better fits in line with what ProcessWire does. But if I found myself in a project that called for a more traditional framework, Symfony2 would be one of the first I'd consider.

My ultimate aim is to have an "admin.mysite.com" domain for the back-end and then write my own dispatcher that will live at "mysite.com". At this stage it seems entirely plausible, but I can't work out how to make the admin subdomain/site ONLY deal with CMS-realted page requests. In fact it'd be great to be able to filter out the "front" page requests and also get rid of any "/processwire" part of the URL for CMS requests so it looks cleaner.

I've not considered this need before, but you should be able to add a snippet to the top of your /site/templates/admin.php that prevents it from being accessed by your main site:

if($config->httpHost != 'admin.mysite.com') throw new Wire404Exception(); 

As for making the admin run at the root path, I don't immediately know of a way to do that while still having it power a site. Though you can certainly rename the admin page to something more transparent like "a" rather than "processwire".

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...