Jump to content
Adam Kiss

Any implications of having custom classes instead of Page?

Recommended Posts

Hey all (and Ryan, mostly ;) )

During me playing with PW, I noticed that you can set different PHP Object for pages, instead Page. SO what I do now: I have custom Autoloader added (in the end of config), have custom classes for all templates (extending Page class), and all templates are redirected to index.php, which only calls some default function $page->render();

Why? This allows me now do stuff like this: '$page->children()->renderListItem();', which I find superclean.

I also teamed up with Steve (netcarver) to see whether there is some performance hit. There is, but minor one - application went from 24 rqs/s down to 21 rqs/s, which I am pretty much okay with.

But (there is always one), I am curious whether there are any other implications? This possiblity is hidden in the advanced settings, which you recommend only to module makers – but I would need to turn this on for each page I'll be working on, so... I am asking. :)

Are there any?

Share this post


Link to post
Share on other sites

I don't understand why and what exactly you did. But for having $page->children()->renderListItem(), you could just simply create a module to add that method to page array objects. I've done similar things in a project.

Share this post


Link to post
Share on other sites

TL;DR: I basically wrap the original Page object (the one that is returned from PW when you call $page or PageArray->eq(0), for instance) into my own PHP class, so I can add my own code to different template classes.

This is makes it easier (for me, at least) to separate the logic part (when deciding stuff based on input or urlSegments, for isntance) from the HTML code generation itself – it's very "MVC-ish" :)

Share this post


Link to post
Share on other sites

I use that kind of stuff in every project. Here is one of my "MarkupRenderHelper" modules (stripped some code to make it easier to follow - might throw few errors because of that, but should give the idea):

<?php

class MarkupRenderHelper extends WireData implements Module {
public static function getModuleInfo() {
 return array(
  'title' => 'Render Helper',
  'version' => 100,
  'summary' => 'Simple module that adds few methods to Pagearray and Page object. -Antti',
  'singular' => true,
  'autoload' => true
  );
}
public function init() {
 $this->addHook('PageArray::renderArticleList', $this, 'renderArticleList');
 $this->addHook('Page::renderListItem', $this, 'renderListItem');
 $this->addHook('Page::renderTags', $this, 'renderTags');
 $this->addHook('Page::renderMeta', $this, 'renderMeta');
 $this->addHook('Page::firstImage', $this, 'firstImage');
}
public function renderArticleList($event) {

 $articles = $event->data['object'];

 $out = "<ul class='acticles-list'>";
 foreach($articles as $p) {
  $out .= $p->renderListItem();
 }
 $out .= "</ul>";
 $event->return = $out;
}
public function renderListItem($event) {
 $p = $event->data['object'];
 $parent = $p->parent();
 $out = "";
 $img = $p->firstImage();

 if ($p->author) $author = $p->author . ",";

 $out .= "<li class='$parent->name'>";
 if ($img) {
  $img = $img->width(130);
  $out .= "<img src='$img->url' width='$img->width' height='$img->height' alt='$img->description' />";
 }
 $out .= "<div class='text-holder'>";
 $out .= $p->renderMeta();
 $out .= "<h2><a href='$p->url'>$p->title</a></h2>";
 $out .= "<p>$p->summary</p>";
 $out .= "</div>";
 $out .= "</li>";
 $event->return = $out;
}
public function renderMeta($event) {
 $p = $event->data['object'];
 $parent = $p->parent();
 $author = ($p->author) ? $p->author . "," : "";
 $out = "<span class='meta'><a href='$parent->url'>$parent->title</a> $author $p->publish_date </span>";
 $event->return = $out;
}
public function renderTags($event) {
 $p = $event->data['object'];
 $out = '';
 foreach($p->tags as $tag) {
  $out .= "<a href='$tag->url'>$tag->title</a>";
  if ($tag !== $p->tags->last()) $out .= ", ";
 }
 $event->return = $out;
}
public function firstImage($event) {
 $p = $event->data['object'];
 if (count($p->images) > 0 ) {
  $img = $p->images->first();
 } else {
  $img = false;
 }

 $event->return = $img;
}
}

Sometimes I add "widgets" for $pages object too. Then I can just do <?= $pages->renderInfobox() ?> or something like that.

  • Like 3

Share this post


Link to post
Share on other sites

Antti: but do you do this for your templates too?

Edit: Oh, I see what you did there; You are adding a custom hooks to fake class methods, right?

Share this post


Link to post
Share on other sites

apeisa, yes that's what I did. I thought why so complicated if there's a nice method to add those in PW. Sorry to not explain further.

Share this post


Link to post
Share on other sites

When it comes to markup render stuff that is needed throughout the site, I also use that hook/module method mentioned above, adding new hook methods as needed to Page and PageArray.

$events = $pages->find("template=event, sort=-date, limit=3"); 
echo $events->renderEvents();

Either that, or I'll just put them in a separate file and call them up with functions:

include("./tools.inc"); 
echo renderEvents($events); 

If i'm making a lot of repetitive $pages->find() calls, I might also add a new hook method to $pages to serve as a filter so that I could do this:

$events = $pages->events("limit=3"); 

I think that inheritance isn't ideal if all you need is to add some new methods to a class. While it works, it's adding another level of complexity, so I prefer to plug-in to things where possible, rather than extend them through inheritance. But what works for one doesn't necessarily work for all, and people have different preferences and ways of achieving things, so I would stick with whatever makes the most sense for your project.

The inheritance of Page that you see in PW's core (like User, Permission, etc.) is more about having a separate type for typecasting purposes, than it is for extending functionality of Page. Though in PW's case, it does both. But if I didn't need separate typecasting (for argument type hints and such) then I might have utilized hooks for these instead. It was also a way to keep the system consistent with PW 2.0, which had User, Permission, Role classes that weren't Page objects. By making the new classes extend Page, it was a way to avoid potentially breaking older code that expected those types.

As for implications, stuff that is in 'Advanced' mode is there because I don't really know the full implications. :) So if you find everything works, I think you are good. But the only implication I would be worried about is just what happens during upgrades and whether they are more likely to affect the approach you are using. I can't say for certain, as I don't totally understand the approach, but if it works now, it's more likely to continue working than not. That being said, the further you go in using a different approach from others, the more difficult it may be to troubleshoot when something doesn't work.

Using an MVC approach with PW also doesn't need to be complex. If you think of your template files as PHP controllers, your $page(s) as your model, and use the TemplateFile class to render your views, you have an easy to use and implement MVC setup. There are applications where I like to use this approach. But for most web sites I find it more convenient and maintainable to use templates, modules and includes in a more traditional way. There are times when layers on top of markup makes things better, and there are times when it makes it worse.

  • Like 2

Share this post


Link to post
Share on other sites

I've been using Ryan's method here of adding hooks instead of extending the Page class, but as my applications grow larger I'm wishing that I had some of the benefits of organizing my code into subclasses and not having to write all of my methods as hooks, which can be a bit of an added pain sometimes (having to pull the object, parameters and check for the page template each time).

Ryan- If one were to extend the Page class instead of hooking to it, would you have a recommended way of doing so?

Share this post


Link to post
Share on other sites

@thetuningspoon:

There is only one way to extend the Page -class, e.g.

<?php

class MyPage extends Page
{
  public function myFunction()
  {
    return "I have been created for great endeavours"
  }
}

?>

After you have made sure the class exists (preferrably through autoloading), you just enter the name of the class into the 'System' -tab of the template in question (under "Page Class Name").

  • Like 5

Share this post


Link to post
Share on other sites

Thanks sforsman. From reading Adam's original post I thought there might be more to it than that. That is awesome. So everywhere that a page is instantiated in PW, it will use the alternate class for that page? I'll have to try it out.

Share this post


Link to post
Share on other sites

So everywhere that a page is instantiated in PW, it will use the alternate class for that page?

Yeah, exactly :) Well to be precise, it will use the alternate class for every page that is using the template for which you have changed the Page Class. The ProcessWire users are implemented exactly like this, so it's definitely a core feature. Also since the classes extend Page, the objects will be valid to any methods that expect Page-objects.

Share this post


Link to post
Share on other sites

If methods are really meant to be used only by a single PageType, isn't it better to go the inheritance route? The thing that always bugged me about the hooking way is, that a added methods are available to all Pages, which is almost never what I want it to be. Maybe the conditional hooking will solve this. It does solve the issue.

Edited by owzim
Added link to the blog post about conditional hooks

Share this post


Link to post
Share on other sites

Thanks sforsman. From reading Adam's original post I thought there might be more to it than that. 

My original post is also three years old  :-[

Share this post


Link to post
Share on other sites

If methods are really meant to be used only by a single PageType, isn't it better to go the inheritance route?

For some cases, it's a matter of preference. What's also cool about the inheritance route, is that you can use type hinting in your methods and functions. It makes the code more readable and also protects from errors. Also there is always some overhead (even though very small) when you are executing hooked methods.

The thing that always bugged me about the hooking way is, that a added methods are available to all Pages, which is almost never what I want it to be.

As you already noticed, you can use conditional hooks. And you can of course check the template of the Page in question inside the hook as well.

Anyway, I would go with inheritance in your case. First and foremost because you don't need any autoloading modules, and you never have to wonder 'where the heck did I put that hook'. In my opinion it makes your application more manageable.

  • Like 2

Share this post


Link to post
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

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...