Jump to content

Best place for urlSegments logic


Zeka
 Share

Recommended Posts

Hi.

I'm using delayed output strategy where in "_init.php" (prepended template) file I define my default regions /fields / etc. 

For some part of pages, I have to use URL segments and the logic of handling urlSegments is placed in templates files. 

	$p = pages()->findOne("template=knowledge-base-category|knowledge-base-item, name={$pagename}");
	if ($p->id) {
		$p->render();
	} else {
		throw new Wire404Exception();
	}

In this way when $p-> id statement is true, all circle of _init.php> template file> _main.php repeats. So it takes twice as much time to render one page because a lot of heavy things happen inside _init.php which is called twice. 

There are two ways that I see how to deal with that:

1. Don't use urlSegments. ))) 

2. Move heavy logic from _init.php under segments code, but in this way, I get a lot of duplicated code in my template. 

How do you handle this cases? 

Link to comment
Share on other sites

During boot, inside /site/config.php $page is not yet determined, so I am limited in determining what to prepend/append. For this reason I disable $config->prependTemplateFile (& append) and use a 'controller' hook like this.

wire()->addHookBefore('TemplateFile::render', function (HookEvent $event) {
    // skip if AJAX
    if ($event->config->ajax) return;

    /** @var TemplateFile $templateFile */
    $templateFile = $event->object;

    // Skip admin pages
    if (strpos($event->input->url, $event->config->urls->admin) === 0) return;

    // check if this is a partial template, if so, stop
    $fileDir = pathinfo($templateFile->get('filename'), PATHINFO_DIRNAME);
    $templatesPath = $event->config->paths->templates;
    if (realpath($fileDir) !== realpath($templatesPath)) return;

    $templateName = $templateFile->page->template->name;

    $prepends = [
        'routes' => $templatesPath . "{$templateName}.routes.php",
        'before' => $templatesPath . "_before.php" // like your _init.php
    ];
    
    // then comes the actual template file
    
    $appends = [
        'view' => $templatesPath . "views/{$templateName}.php",
        'after' => $templatesPath . "_after.php",
        'layout' => $templatesPath . "layouts/main.php"
    ];

    foreach ($prepends as $name => $file) {
        if (file_exists($file)) $templateFile->setPrependFilename($file);
    }

    foreach ($appends as $name => $file) {
        if (file_exists($file)) $templateFile->setAppendFilename($file);
    }
});

So to adapt this to your setup, you can define your urlSegments inside template.routes.php, and stop further appends with return $this->halt(). This way only template.routes.php is loaded and remaining appends are skipped while still allowing PW to complete its shutdown.

 

  • Like 3
Link to comment
Share on other sites

2 hours ago, Zeka said:

In this way when $p-> id statement is true, all circle of _init.php> template file> _main.php repeats.

How about using a counter or a flag to signal that the page has already been rendered? If you store the rendered output in the first iteration then you can just output it later on without calling render again. Or am I misunderstanding something?

  • Like 1
Link to comment
Share on other sites

@abdus Thank for your example.

I have tried to test. It looks like I don't fully understand this approach, would be nice to get clarification. 

Here is my setup

In site/init.php

wire()->addHookBefore('TemplateFile::render', function (HookEvent $event) {
	// skip if AJAX
	if ($event->config->ajax) return;

	/** @var TemplateFile $templateFile */
	$templateFile = $event->object;

	// Skip admin pages
	if (strpos($event->input->url, $event->config->urls->admin) === 0) return;

	// check if this is a partial template, if so, stop
	$fileDir = pathinfo($templateFile->get('filename'), PATHINFO_DIRNAME);
	$templatesPath = $event->config->paths->templates;
	if (realpath($fileDir) !== realpath($templatesPath)) return;

	$templateName = $templateFile->page->template->name;

	bd($templateName);

	$prepends = [
		'routes' => $templatesPath . "{$templateName}.routes.php",
		'before' => $templatesPath . "_init.php" // like your _init.php
	];

	// then comes the actual template file

	$appends = [
		'after' => $templatesPath . "_main.php",
	];

	foreach ($prepends as $name => $file) {
		if (file_exists($file)) $templateFile->setPrependFilename($file);
	}

	foreach ($appends as $name => $file) {
		if (file_exists($file)) $templateFile->setAppendFilename($file);
	}
});

In templates/categories.routes.php

// we are only using 1 URL segment, so send a 404 if there's more than 1
if($input->urlSegment2) throw new Wire404Exception();

// we have 1 URL segment in page URL
if ($input->urlSegment(1)) {
	$pagename = $input->urlSegment(1);
	$p = pages()->findOne("template=category, name={$pagename}");
	if ($p->id) {
		$p->render();
	} else {
		throw new Wire404Exception();
	}
}

In templates/_init.php

// Include helper methods for templates render
require_once("./helpers/convenience-methods.php");
require_once("./helpers/functions.php");


region("header", renderGlobal("header"));
region("masthead", renderGlobal("masthead", $viewData));
region("footer", renderGlobal("footer"));
region("scripts", renderGlobal("scripts"));

In templates/categories.php

$viewData["categories"] = pages("template=category");
region("content", renderView("categories", $viewData));

In templates/_main.php

				<?=region("header");?>
				<?=region("masthead");?>
				<?=region("content");?>
				<?=region("footer");?>

With this setup with any URL segment1, I got render "categories " page, not "category" from $p->render();

@szabesz Sorry for not being clear, with code examples it would be more clear what I meant.

Link to comment
Share on other sites

28 minutes ago, Zeka said:

With this setup with any URL segment1, I got render "categories " page, not "category" from $p->render();

 

I'm not getting that behavior. I tired to replicate your setup. This is what I have ended up with (I use jobs instead of categories but it shouldnt matter)

<?php namespace ProcessWire;
// categories.routes.php

if (input()->urlSegment2) throw new Wire404Exception();
if (input()->urlSegment1) {
    /** @var Pages $pages */
    $name = sanitizer()->pageName(input()->urlSegment1);
    $match = $pages->findOne("template=profession, name=$name");

    if (!$match->id) throw new Wire404Exception();
    echo $match->render();
    return $this->halt();
}

This is my _init.php. It gets included to every template (after its routes, if exists)

<?php namespace ProcessWire;

require_once '_func.php';

$testPages = $pages->find('template=profession, limit=5');

And I use it inside actual page template that renders `category` page

<?php namespace ProcessWire; ?>
This is: <?= $page->title ?>

<p>Other Pages:</p>
<?php foreach ($testPages as $p): ?>
    <?= $p->title ?><br>
<?php endforeach; ?>

And both pages render correctly

59b1afbc726e3_2017-09-0723_44_11-pw.dev_categories_teacher.png.b98a12e9bbf22e5bd73ed6afc908618c.png59b1afbe1e81f_2017-09-0723_44_22-pw.dev_categories_nurse.png.a343e79ac2e69423726faf0f3e05aa90.png

 

So, to me the only thing that seems missing is echo statement before $p->render() and return $this->halt(); after that

 

EDIT: This is the backend structure, I am guessing you have a similar one. You get categories from another parent

59b1b09f9dee3_2017-09-0723_47_44-PagesProcessWirepw_dev.png.1556416e0668e0fed01fb1c9789bb768.png

  • Like 1
Link to comment
Share on other sites

Thanks, @abdus for so quick response. 

You are right, I don't get "category" page because of missed "echo";

And this return $this->halt();  prevents render of "categories" page. 

 

From your code:

$name = sanitizer()->pageName(input()->urlSegment1);

https://processwire.com/api/ref/input/url-segment/

Docs say that urlSegments are already sanitized as page names.

Thanks once again!

 

  • Like 1
Link to comment
Share on other sites

You're welcome.
I'm glad your problem got solved.

1 hour ago, Zeka said:

Docs say that urlSegments are already sanitized as page names.

 

You're right, thanks for pointing that. I was reading the source for urlSegments just today.
Gotta love @ryan's humor

// ProcessPageView.module
/**
 * Get the requested page and populate it with identified urlSegments or page numbers
 *
 * @return Page|null
 *
 */
protected function getPage() {

    $config = $this->wire('config');
    $shit = isset($_GET['it']) ? trim($_GET['it']) : "/"; // dirty
    $it = preg_replace('{[^-_./a-zA-Z0-9]}', '', $shit); // clean
    unset($_GET['it']);

    if($shit !== $it && $config->pageNameCharset == 'UTF8') {
        $it = $this->wire('sanitizer')->pagePathNameUTF8($shit);
    }

    // ...

    while((!$page || !$page->id) && $cnt < $maxSegments) {
        $it = rtrim($it, '/');
        $pos = strrpos($it, '/')+1;
        $urlSegment = substr($it, $pos);
        $urlSegments[$cnt] = $urlSegment;
        $it = substr($it, 0, $pos); // $it no longer includes the urlSegment
        $selector = "path=" . $this->wire('sanitizer')->selectorValue($it, 2048) . ", status<" . Page::statusMax;
        $page = $this->pages->get($selector);
        $cnt++;
    }
    // ... 
}

 

  • Like 3
Link to comment
Share on other sites

I've found a cleaner way to modify prepend/append files depending on the current page.

wire()->addHookBefore('PageRender::renderPage', function (HookEvent $e) {
    /** @var Page $page */
    /** @var HookEvent $event */
    /** @var Template $template */

    $event = $e->arguments(0);
    $options = $event->arguments(0);
    $page = $event->object;
    $template = $page->template;
    $options['prependFiles'] = [
        "{$template}.routes.php",
        "_common.php",
    ];
    $options['appendFiles'] = [
        "views/{$template}.php",
        "_after.php",
        "layouts/main.php",
    ];
    $event->setArgument(0, $options);
});

The older way felt too much like a hack to me.

To early exit and skip including remaining files during render, use `return $this->halt();` anywhere in your templates.

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

2 hours ago, abdus said:

I've found a cleaner way to modify prepend/append files depending on the current page.

I haven't followed this discussion closely so sorry if I'm missing the point here, but I believe you can override the append/prepend files directly when you render a page with $page->render().

echo $p->render( ['prependFile' => null, 'appendFile' => null] );

 

  • Like 2
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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...