Giter VIP home page Giter VIP logo

plates's Introduction

Plates

Maintainer Source Code Latest Version Software License Build Status Quality Score Total Downloads

Plates is a native PHP template system that's fast, easy to use and easy to extend. It's inspired by the excellent Twig template engine and strives to bring modern template language functionality to native PHP templates. Plates is designed for developers who prefer to use native PHP templates over compiled template languages, such as Twig or Smarty.

Highlights

  • Native PHP templates, no new syntax to learn
  • Plates is a template system, not a template language
  • Plates encourages the use of existing PHP functions
  • Increase code reuse with template layouts and inheritance
  • Template folders for grouping templates into namespaces
  • Data sharing across templates
  • Preassign data to specific templates
  • Built-in escaping helpers
  • Easy to extend using functions and extensions
  • Framework-agnostic, will work with any project
  • Decoupled design makes templates easy to test
  • Composer ready and PSR-2 compliant

Installation

Plates is available via Composer:

composer require league/plates

Documentation

Full documentation can be found at platesphp.com.

Testing

composer test

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

plates's People

Contributors

anlutro avatar baileylo avatar carbontwelve avatar duncan3dc avatar elazar avatar electricjones avatar gomzyakov avatar grahamcampbell avatar harikt avatar jamesdb avatar jopacicdev avatar kafene avatar kozubsky avatar localheinz avatar magnus-eriksson avatar odahcam avatar pborreli avatar philsturgeon avatar ragboyjr avatar rayne avatar reinink avatar ricardofiorani avatar roxblnfk avatar sudo-barun avatar sxdx avatar visavi avatar vlakoff avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

plates's Issues

HHVM support

Could we take hhvm out of the allowed failures and see how well it does? Things are a bit more stable over there now so it would be good to see how this works.

Escape is safe?

Hi

Is safe use only http://platesphp.com/templates/escaping/ or should we consider to use third party lib like htmlpurifier?

I see here: http://htmlpurifier.org/comparison
all possible XSS attack.

Question, instaed of escape variable on template, why not do this on controller before passing to template?

Example on each controller, before render html, the variables from Request is escapated (and sanitized using htmlpurifier), and then passed to template clean and safe.

thanks!!

Loading jquery in section

Hi.

I'm using a section in my template:

start('custom_js') ?>
$(document).ready(function(){ alert("hi"); });
stop() ?>

My layout:

section('custom_js')?>

When i load it, it doesn't show the alert.

If I use a simple:

start('custom_js') ?>
alert("hi");
stop() ?>

It works.

How can I use jQuery in a section?

child themes

Hi
I would like to ask if possible create child themes like wordpress.

Example:

Into folder "views" there is another folder "default" which contain all view and template files.

A designer can create another folder into "views/custom" for override some view/template files.

The template engine should check firstly on active theme (custom), and if not found the file view, then load from views/default folder.

Thanks

Make template escape methods public

The Plates documentation has a section on making the template object available to extensions. The methods of that object used to escape output would be useful to extensions, but are presently protected, preventing them from being accessible in that scope. Not sure that it matters, but this happens under Plates 3.1.1 and PHP 5.6.10.

// template.php
<?php echo $this->foo(); ?>
// Extension.php
class Extension implements \League\Plates\Extension\ExtensionInterface
{
    public $template;

    public function register(\League\Plates\Engine $engine)
    {
        $engine->registerFunction('foo', [$this, 'foo']);
    }

    public function foo()
    {
        return $this->template->e('foo');
    }
}
// test.php
$engine = new \League\Plates\Engine;
$engine->addFolder('foo', __DIR__);
$engine->loadExtension(new Extension);
echo $engine->render('foo::template');

/*
Output:
PHP Fatal error:  Uncaught exception 'LogicException' with message 
'The template function "e" was not found.' in 
vendor/league/plates/src/Template/Functions.php:63
Stack trace:
#0 vendor/league/plates/src/Engine.php(196):
  League\Plates\Template\Functions->get('e')
#1 vendor/league/plates/src/Template/Template.php(70):
  League\Plates\Engine->getFunction('e')
#2 test.php(16):
  League\Plates\Template\Template->__call('e', Array)
#3 test.php(16):
  League\Plates\Template\Template->e('foo')
#4 [internal function]: Extension->foo()
#5 vendor/league/plates/src/Template/Func.php(105):
  call_user_func_array(Array, Array)
#6 vendor/league/plates/src/Template/Template.php(70):
  League\Plates\Template\Func->call(Object(League\Plates\Template\Template), Array)
#7 . in vendor/league/plates/src/Template/Functions.php on line 63
*/

Typo in docs

http://platesphp.com/templates/, Manually creating templates code block:

// Create new Plates instance
$templates = new League\Plates\Engine('/path/to/templates');

// Create a new template
$template = new League\Plates\Template\Template($templates, 'profile');

// Render the template
echo template->render(['name' => 'Jonathan']);

Last line is missing $ before template.

Auto-escaping by making all template variables objects

While discussing the proposed new compiling stage for Plates, @baileylo had the very interesting idea to proxy template variables into objects to achieve auto-escaping in Plates. I realize now that this is also what @stefankleff was trying to explain to me here and here.

How it works

As template variables are assigned to a template (League\Plates\Template) they are automatically converted into variable objects (League\Plates\Variable). Then, when the variable is outputted in a template, the __toString() magic method is called, and the variable is automatically escaped.

This approach doesn't only work for strings, but also multidimensional objects and arrays. Using overloading, ArrayAccess and Iterator we can automatically convert nested arrays, objects and even method calls into the new League\Plates\Variable object at the point that they are accessed.

This approach also accommodates other escaping strategies via object methods:

// Simple:
Hello, <?=$name?> // automatically escaped using HTML strategy
Hello, <?=$name->raw()?>
Hello, <?=$name->css()?>
Hello, <?=$name->js()?>
Hello, <?=$name->attr()?>
// Complex:
Hello, <?=$user->name?>
Hello, <?=$user->name()?>
Hello, <?=$user->name()->raw()?> 
Hello, <?=$user['name']?> 
Hello, <?=$users[0]->name?> 
Hello, <?=$users[0]['name']->css()?> 

It may even be possible to allow template extensions to add methods to these variable objects. Using the existing batch extension as an example:

// Current:
<p>Welcome <?=$this->batch($this->name, 'strip_tags|strtoupper')?></p>
// Updated:
<p>Welcome <?=$name->batch('strip_tags|strtoupper')?></p>

Summary of benefits

  • Templates are safer without any extra work required by the developer.
  • This works with strings.
  • This works with arrays, including dereferencing (ie. $users[0]) and loops.
  • This works with objects, methods ($user->name()) and parameters ($user->name).
  • This works with multi-dimensional arrays, objects in arrays and arrays in objects.
  • Variables are only ever escaped on output.
  • This could be enabled by default, or setup as an option: $engine->enableAutoEscaping().
  • By default variables would be escaped using the HTML strategy, but the other strategies could be made available as methods: $name->js(), $name->css(), $name->attr().
  • Outputting raw data could also be done via a method: $name->raw().
  • Template extensions can be updated to always return an instance of League\Plates\Variable, making this functionality available there as well.
  • Template extensions can potentially even add methods to the variable objects themselves, allowing for cleaner templates.

Possible issues

  • Double escaping will probably be the most annoying issue with this approach. Every time two strings are concatenated, escaping would occur. Combine this with template functions, and it could be a bit of a mess. Going to have to think this through carefully.
  • This will not work with static variables nor static object methods (Str::uppercase()). Manually escaping would be required at this point.
  • All template variable will be objects, which could annoy some developers. For example, type checks would not be possible: if (is_string($name)).
  • Any variables created within the template will not be escaped. Not sure why they would need to be though, since escaping is really an issue with user supplied input. Further, these variables could still be manually escaped.
  • No way to reference a string character using array syntax (ie. <?=$name[1]?>) without it being escaped first. As of PHP 5.4, function array dereferencing is possible, so this would be possible like this: <?=$name->raw()[1]?>

Draft

Here is the rough draft of a League\Plates\Variable implementation:

<?php

namespace League\Plates;

class Variable implements \ArrayAccess, \Iterator
{
    protected $variable;

    public function __construct($variable)
    {
        $this->variable = $variable;
    }

    // Objects

    public function __isset($name)
    {
        return isset($this->variable->$name);
    }

    public function __unset($name)
    {
        unset($this->variable->$name);
    }

    public function __set($name, $value)
    {
        $this->variable->$name = $value;
    }

    public function __get($attribute)
    {
        return new self($this->variable->$attribute);
    }

    public function __call($name, $arguments)
    {
        return new self(call_user_func_array([$this->variable, $name], $arguments));
    }

    // ArrayAccess

    public function offsetSet($offset, $value)
    {
        if (is_null($offset)) {
            $this->variable[] = $value;
        } else {
            $this->variable[$offset] = $value;
        }
    }

    public function offsetExists($offset)
    {
        return isset($this->variable[$offset]);
    }

    public function offsetUnset($offset)
    {
        unset($this->variable[$offset]);
    }

    public function offsetGet($offset)
    {
        return isset($this->variable[$offset]) ? new self($this->variable[$offset]) : null;
    }

    // Iterator

    public function rewind()
    {
        reset($this->variable);
    }

    public function current()
    {
        return new self(current($this->variable));
    }

    public function key()
    {
        return new self(key($this->variable));
    }

    public function next()
    {
        return new self(next($this->variable));
    }

    public function valid()
    {
        $key = key($this->variable);
        return ($key !== null && $key !== false);
    }

    // Output

    public function __toString()
    {
        return htmlspecialchars($this->variable, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }

    public function raw()
    {
        return $this->variable;
    }

    public function css()
    {
        // add
    }

    public function js()
    {
        // add
    }

    public function attr()
    {
        // add
    }
}

Extensions Repository

Hello,

I suggest to add a Extension.md file to index the open source extensions developped by communauty.

I can suggest a pull-request if the idea seems pertinent.

Good week.

Define sections inside inserted templates

I don't now if this is related with #47
I have partials to reuse some templates. For example:

<?php
//define the main layout
$this->layout('main');

//use some partials
if ($post) {
    $this->insert('post', ['post' => $post]);
}

//define some sections
$this->start('title');
echo 'This is a title';
$this->stop();
?>

In the "post" partial, I have something like this:

<article class="post">
    <h1><?= $post->title ?></h1>
    <?= $post->body ?>
</article>

<?php $this->start('extra-head') ?>
<style>
    .post {
        background: blue;
    }
</style>
<?php $this->stop(); ?>

And now, in the main template, the first section is printed but the one defined in the partial doesn't:

<html>
    <head>
        <title><?= $this->section('title') ?></title>
        <?= $this->section('extra-head') ?>
    </head>
    <body>
        <?= $this->section('content') ?>
    </body>
</html>

The section name "content" is reserved

I copied and pasted an example from your this link http://platesphp.com/templates/inheritance/ and got following exception:

'The section name "content" is reserved.'

In Template.php file on line 171 you have reserved content name. Can you please update your docs to reflect this change?

I have two questions as well:

  1. What's the difference between fetch and insert function?
  2. Let's say I setup a folder admin. Now I want to insert template blocks from a folder that resides in the admin directory i.e. admin/partials. Is it possible to use $this->fetch('partials/navigation') instead of $this->fetch('admin::partials/navigation')

addData to all templates in a directory

This is just a nice-to-have request... But it would be useful for me to be able to addData() to all templates in a defined directory... something like

$templates->addFolder('admin', '/path/to/admin/templates');
$templates->addData(['admin_value'=>$admin_value], 'admin::*');

So $admin_value would be available to any template in that directory, but not others.

Add "default section content" example to docs

Currently the docs don't explicitly show how to setup default section content. Add the following example to the sections and/or inheritance page.

<div id="sidebar">
    <?php if ($this->section('sidebar')): ?>
        <?=$this->section('sidebar')?>
    <?php else ?>
        <?=$this->fetch('default-sidebar')?>
    <?php endif ?>
</div>

Less verbose section override?

Currently, to define multiline default section content one needs an if / else block.

From your docs:

<?php if ($this->section('sidebar')) : ?>
        <?= $this->section('sidebar') ?>
<?php else: ?>
        <ul>
            <li><a href="/link">Example Link</a></li>
            <li><a href="/link">Example Link</a></li>
        </ul>
<?php endif ?>

In my fork, with this commit I added hasSection() template method that makes same thing doable using only an if statement, for less verbose and more readable templates.

Example:

<?php if ( ! $this->hasSection('sidebar')) : ?>
        <ul>
            <li><a href="/link">Example Link</a></li>
            <li><a href="/link">Example Link</a></li>
        </ul>
<?php endif ?>

If you like it I can write unit test and submit a PR.

Accessing template functions without $this

The idea

I have investigated the possibility of making template functions available without using the $this operator. For example, instead of calling $this->layout('template') you could use layout('template'). The purpose of doing this is to try reduce the amount of unnecessary syntax within the templates. Below are my findings.

The implementation

There is no way to dynamically create user-defined functions in PHP, which means all template functions, including extension functions, need to explicitly defined. Here is an example of this using the layout() function:

// functions.php

use \League\Plates\Template;

if (!function_exists('layout')) {
    function layout()
    {
        return call_user_func_array(array(Template::getInstance(), __FUNCTION__), func_get_args());
    }
}

The template (and extension) functions require an instance of the template object, however, in global scope the template is not available. The workaround here is to add a public static $instance parameter to the template object which is temporarily assigned during rendering. This requires the following updates to the template object:

// Template.php

namespace League\Plates;

class Template
{
    public static $instance;

    public function render($name, Array $data = null)
    {
        ob_start();

        $this->data($data);

        include_once 'functions.php';

        self::$instance = $this;

        include($this->_internal['engine']->resolvePath($name);

        if (isset($this->_internal['layout'])) {

            $this->_internal['child'] = ob_get_contents();

            ob_clean();

            include($this->_internal['engine']->resolvePath($this->_internal['layout']));
        }

        self::$instance = null;

        return ob_get_clean();
    }

    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            throw new \LogicException('Cannot use template functions outside of templates.');
        }

        return self::$instance;
    }
}

Enable/disable this feature

A new engine option could even be made available to enable this feature, such as $engine->useGlobalFunctions(boolean). Setting this option to true would automatically include the template functions. This could be implemented within the template's render() method like this:

if ($this->_internal['engine']->useGlobalFunctions()) {

    include_once 'functions.php';

    self::$instance = $this;
}

The issues

While the above implementation works, there are a number of problems:

Too many global functions

The most obvious issue is that the new global template functions could conflict with other libraries and application code. For example, is it really that smart to have a global insert() function just for the sake of this feature?

Issues around autoloading

As far as I can see, functions must be included in a separate functions.php file, as opposed to just adding them to the bottom of the existing class files. For some reason when using class autoloading, functions defined at the bottom of a class file are not available at runtime. However, adding them to a separate file and then including them seems to work fine. For the main template functions (ie. layout, start, stop, child, insert, etc.) this isn't a big deal as it could be worked into the library, but it certainly adds a layer of complexity when creating extensions.

Consistency with variable access

In my mind this feature would only make sense if the same approach could be taken with variable access as well, essentially making it possible to access both functions and variables without the $this operator. However, this is very unlikely, as there is no way to make ALL template variables accessible in local scope, given how Plates has been designed.

This is quite easy to do with variables assigned outside of a template (ie. in a controller). These can be converted to the local scope easily within the template's render() method like so:

foreach (get_object_vars($this) as $var => $value) {

    if ($var !== '_internal') {
        ${$name} = $value;
    }
}

However, what makes Plates so powerful is its ability to assigned variables within the templates themselves. For example, you may assign your page title at the top of your template like this: <?php $this->title = 'About Us' ?>.

Since these variables are assigned to the template object during the rendering process, there is no way to convert them to variables in the local scope. Sure, you could assign them immediately to the local scope (ie. <?php $title = 'About Us' ?>), but then they would not be available to other included templates (ie. layout and nested). Not good.

The problem worsens for helper functions, like the inheritance start('sidebar') and stop() functions. The content generated between these two functions is saved as a parameter of the template object itself, making them available to all included templates (ie. <?=$this->sidebar?>). Again, in this situation there is no way to convert this new object parameter into a local scope variable (ie. <?=$sidebar?>), as the local scope is actually within the render() method.

So, unless template variables can be made entirely available without the $this operator, it would only add confusion to have some variables available this way, and others not.

Closing thoughts

One "last ditch" idea I had was to make all functions within local scope accessible using closures, which are actually aliases to the underlying object methods. So, for example, $this->layout('template') could become $layout('template'). Closures wouldn't suffer from the scope issues that variables suffer from, as all functions are known before the rendering begins. They can also be dynamically generated, and therefore don't suffer from the autoloading issues. Honestly, if variable access was possible this way (which is isn't , see above), I'd seriously consider this.

Finally, a simple option would be to just assign the $this operator to a shorter variable name, such as $t. I'm personally not a fan of this given that 1) it could conflict with local scope variables, 2) it makes it less clear that the template is essentially an object, and 3) it only saves 3 characters.

Because of the above issues, this idea will not be added to Plates at this time. Accessing all template variables and functions using the $this operator keeps things very "neat and tidy". It also makes it very obvious that it's a template specific variable or function.

Accessing template variables without $this

This request keeps coming up:

Can we access template variables without the $this pseudo-variable?

As discussed in #9, this is not possible with template functions, and therefore it seemed unwise to do this with template variables (as it would create inconsistency between how these two are accessed).

Further, prior to the changes made to nesting and layouts, accessing variables without $this wasn't even possible. However, because nested templates now have their own variable scope, this may actually be be possible...with some caveats to sections. Consider this example:

<?php $this->start('content') ?>

    <h1>Welcome!</h1>
    <p>Hello <?=$this->e($this->name)?></p>

<?php $this->end() ?>

The contents of this section will be placed into the variable <?=$this->content?>. Generally, sections are used with inheritance, in which case there shouldn't be an issue. The section variables can be extracted before the layout is rendered, making the content available in the layout's local scope ($content).

However, if you want to use this variable in the same template, this will not be possible. For example:

<?php $this->start('content') ?>

    <h1>Welcome!</h1>
    <p>Hello <?=$this->e($this->name)?></p>

<?php $this->end() ?>

<!-- This will not work: -->
<?=$content?>

There are two ways around this. You could access this variable the old way (<?=$this->content?>), but that now creates an odd and very inconsistent approach to accessing variables. Alternatively, section variables could be accessed using a new template function, such as: <?=$this->section('content')?>. I'd be inclined to say, for consistency's sake, that all section variables should be accessed using this new section() function. For example:

<html>
<head>
    <title><?=$this->title?></title>
</head>

<body>

<div id="content">
    <?=$this->section('content')?>
</div>

<?php if ($this->section('sidebar')): ?>
    <div id="sidebar">
        <?=$this->section('sidebar')?>
    </div>
<?php endif ?>

</body>
</html>

With the sections issue out of the way, does it now make sense to encourage (force) everyone to access variables without the $this pseudo-variable? We could theoretically allow both approaches, and developers can choose which one they prefer. Personally, think that is a bad idea since it creates inconsistency in the templates. I'd prefer to simply choose which approach is better, and force that. This would clearly be a breaking change, meaning a version bump (3.0).

So, is everyone okay with having template functions accessed differently than template variables?

<h1>Hello, <?=$this->e($name)?></h1>

Construct accept string, why not mixed array or have addFolders

So there are some further thoughts on the templates directory. I don't think your idea is only to have single place for templates.

I may be creating different bundles, and the templates may be residing inside the bundle.

Assume each bundle which can be installed via composer and the templates directory of a bundle is something like this.

|-src
|-templates

I find the usage of a DI container very useful when you want to add more values to an array.

This is how we can use via Aura.Di

$di->params['League\Plates\Template']['directory'] = array();

// or use the setter 

$di->setter['League\Plates\Template']['addFolders'] = array(
    'namespace' => 'some-name-space'
    'directory' => 'some-directory'
);

Though we have add folder, we could not make use of an array to add folders. One of the value will be replaced when loading the configurations of different bundles.

You may ask me why not addFolder in some controller / action. It is also possible but the way I am trying to design the system is to have multiple engines, where you can set the engine before you render in a bundle. That way you could integrate any bundle with any template engine.

I am interested to hear your thoughts on this.

Asset extension not returning correct path for files

I am not entirely sure whether it's just me not being familiar to how Plate works or if this is actually a bug so please do bear with me.

This issue occurs where I have a file structure like below:
/home/user/domain/public_html/ (web root)
-> assets/
---> css/
-----> stylesheet.css
---> js/
-----> script.js

So in this case the extension would be loaded as follows:

$engine->loadExtension(new League\Plates\Extension\Asset('/home/user/domain/public_html/assets'));

And when we are trying to load say stylesheet.css in a template using this code:

$this->assets('css/stylesheet.css');

the code would return css/stylesheet.css?v=(something) instead of assets/css/stylesheet.css?v=(something).

Anyone have any idea if there's something I've done that's wrong or if this is indeed a bug?

A bug in $template->render('folderName/templateFile'); ?

I notice something very weird:

assuming:
The calling block is something like:

    $template = new \League\Plates\Engine($container->get('/path/to/view'));
    $template->render('front/index');

and the folder structure is:

path/to/view
|__ Front
|     |__ index.tpl.php
|__ anotherTemplate.tpl.php

On my local LAMP stack, if I call the folder name 'Front' with lowercase as in $template->render('front/index');
It works fine!

BUT, on a production server like an AWS LAMP stack, unless I put the folder name 'as-is' with the first Cap letter as in $template->render('Front/index');
it will not work. (which does make sense)

QU: Any ideas why on my local machine it does work although not making sense?

Delayed rendering feature

Here is an idea, how to avoid default section value overhead for that cases:

// somewhere in template
print $this->section('name', $this->fetch('default-template-name', [/* params maybe */]));

The fetch() call calls unnecessary rendering procedure immediately.

What if pass an array or callback as default value (instead of direct fetch()), and later, on section() call process possible section values like following (my own override from real project):

protected function section($name, $default = null)
    {
        if (isset($this->sections[$name]) && !is_string($this->sections[$name])) {
            // not rendered yet
            $section = $this->sections[$name];
            if ($section instanceof \Closure) {
                // we may even give hook for inject context params at sub-implementations, if needed :)
                $this->sections[$name] = (string)$section();
                return $this->sections[$name];
            } elseif (is_array($section)) {
                list($template, $data) = $section;
                $this->sections[$name] = $this->render($template, (array)$data);
                return $this->sections[$name];
            }
        }
        // if rendered, give ready string back
        return parent::section($name, $default);
    }

Stacked Layouts - variables not passing to main page layout?

Hi,

First of all, thanks for the great project.

I am encountering an issue when trying out Stacked Layouts (as shown on this page). Following the example, the $title variable should be set from blog-article.php as below:

$this->layout('blog', ['title' => $article->title])

While I was expecting the variable $title to be passed through blog.php (Blog layout) then into template.php (Main site layout), however an error Undefined variable: title is shown when executing this line from template.php:

<title><?=$this->e($title)?></title>

And quite strangely, when I update the first line of blog.php like this, the page will show the article title properly:

$this->layout('template', ['title' => $title])

Is it an expected result, Or am I doing anything wrong here?

Insert template relative to current

Hi.
I use directories to organize my templates and I miss an option to insert a template using a relative path. Something like this:

<?php $this->insert('./my-template') ?>

This should be useful to avoid repetitions and to allow move templates without break them. What do you think?

nested inheritance

is this possible / intented, i am trying right now and get a blank page without erros )- :

<!-- index -->
<?= $this->content ?>
<!-- AAA -->
<?php $this->layout('index') ?>
<?php $this->start('content') ?>
<h1>CONTENT</h1>
<?= $this->subcontent ?>
<?php $this->end();
<!-- BBB -->
<?php $this->layout('AAA') ?>
<?php $this->start('subcontent') ?>
<h2>SUBCONTENT</h2>
<?php $this->end(); ?>

expected output

<!-- index -->
<h1>CONTENT</h1>
<h2>SUBCONTENT</h2>

addData for layout?

Using v3.0.2, I'm trying to use addData() to assign a value for a layout template to use, but that variable isn't defined inside the template:

bootstrap.php:

$plates = new League\Plates\Engine('/path/to/templates');
$plates->addFolder('layout', '/path/to/layouts');
$plates->addData(['layout::main'], ['title' => 'Page Title']); // first param must be array?

index.php

echo $plates->render('home');

/path/to/templates/home.php

<? $this->layout('layout::main') ?>
blah

/path/to/layouts/main.php

<html><title><?= $title ?></title>...

I was expecting it to output <html><title>Page Title</title>.... but that variable isn't defined. Am I misunderstanding how that should work or doing something wrong? I know I could pass the title into the render() call, then into the layout() call... but that could get burdensome in this setup.

It does look like if I just do addData with the array (without specifying a template), then it works fine... I guess that will do if this won't work.

Proposal: new batch magic function

I just come to see the issue about batch and escape.
I imagined an solution using magic function, for exemple instead of :

$this->escape($foo, 'uppercase');

We can write :

$this->escapeAndUppercase($foo);

or

$this->escape_uppercase($foo);

And batch as many function we want.

"Deep" template inheritance

Currently, template files can override layout sections only one level deep: included templates (partials) can define sections, but they are not overridable from "main" templates.

Assuming this layout:

<?= $this->fetch('header.php') ?>

<body>
...
</body>

<?= $this->fetch('footer.php') ?>

And following header.php

<html>
<head>

<?php if ( $this->section('styles') ) : ?>
    <?= $this->section('styles') ?>
<?php else: ?>
    <style src="path/to/default/css.css">
<?php endif ?>

</head>

The section "styles" can't be overridden from a template that uses the layout, because when layout includes the partial it creates another template object that is not aware of parent template sections.

In my fork, I added this commit, that, by passing parent template sections to included template objects, allows "deep" inheritance (i.e. inheritance for partials) as seen in Twig and other non-php engines.

If you like it I can write unit test and submit a PR.

Edit
The linked commit has a bug, because template render() method doesn't take name. Fixed here.

Add batch functionality to escape functions

It's almost always necessary to escape variables when outputting, and so it would be helpful to add batch functionality to the escaping functions, where the actual escape call would occur last. Consider the following example:

Currently:

<h1>Hello <?=$this->e($this->uppercase($name))</h1>
<h1>Hello <?=$this->batch($name, 'uppercase|e')</h1>

Proposed:

<h1>Hello <?=$this->e($name, 'uppercase')</h1>

A small improvement to make templates slightly more legible.

Does Plates extension assets work in sub directories?

Does Plates extension assets work in sub directories?
ie c:/xampp/htdocs/plates1
url http://localhost/plates1
dev-master branch installed using composer
mod rewite is working
Additional info below.
Thanks in advance.
Mark

htaccess

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]

index.php

opcache_reset();
require_once 'vendor/autoload.php';
$templates = new League\Plates\Engine('templates');
$templates->loadExtension(new League\Plates\Extension\Asset(getcwd().'/assets/',
        true));
echo '<pre>';
// Render a template
$templates->render('profile', ['name' => 'Jonathan']);
print_r($templates);

templates\template.php

<!DOCTYPE html>
<html>
    <head>
        <title><?=$this->escape($title)?></title>
        <link rel="stylesheet" href="<?=$this->asset('css/skel.css')?>" />
        <link rel="stylesheet" href="<?=$this->asset('css/style.css')?>" />
        <link rel="stylesheet" href="<?=$this->asset('css/style-xlarge.css')?>" />
    </head>
    <body>
        <?=$this->section('content')?>
    </body>
</html>

templates/profiles.php

<?php $this->layout('template', ['title' => 'User Profile'])?>
<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

resulting page

League\Plates\Engine Object
(
    [directory:protected] => League\Plates\Template\Directory Object
        (
            [path:protected] => templates
        )

    [fileExtension:protected] => League\Plates\Template\FileExtension Object
        (
            [fileExtension:protected] => php
        )

    [folders:protected] => League\Plates\Template\Folders Object
        (
            [folders:protected] => Array
                (
                )

        )

    [functions:protected] => League\Plates\Template\Functions Object
        (
            [functions:protected] => Array
                (
                    [asset] => League\Plates\Template\Func Object
                        (
                            [name:protected] => asset
                            [callback:protected] => Array
                                (
                                    [0] => League\Plates\Extension\Asset Object
                                        (
                                            [template] => League\Plates\Template\Template Object
                                                (
                                                    [engine:protected] => League\Plates\Engine Object
 *RECURSION*
                                                    [name:protected] => League\Plates\Template\Name Object
                                                        (
                                                            [engine:protected] => League\Plates\Engine Object
 *RECURSION*
                                                            [name:protected] => template
                                                            [folder:protected] => 
                                                            [file:protected] => template.php
                                                        )

                                                    [data:protected] => Array
                                                        (
                                                            [title] => User Profile
                                                        )

                                                    [sections:protected] => Array
                                                        (
                                                            [content] => 
User Profile

Hello, Jonathan

                                                        )

                                                    [layoutName:protected] => 
                                                    [layoutData:protected] => 
                                                )

                                            [path] => C:\xampp\htdocs\plates1/assets
                                            [filenameMethod] => 1
                                        )

                                    [1] => cachedAssetUrl
                                )

                        )

                )

        )

    [data:protected] => League\Plates\Template\Data Object
        (
            [sharedVariables:protected] => Array
                (
                )

            [templateVariables:protected] => Array
                (
                )

        )

)

resulting HTML without print_r and adding echo $templates->render('profile', ['name' => 'Jonathan']);

If you try to select css link you get file not found server error

<!DOCTYPE html>
<html>
    <head>
        <title>User Profile</title>
        <link rel="stylesheet" href="css/skel.1421211896.css" />
        <link rel="stylesheet" href="css/style.1421206785.css" />
        <link rel="stylesheet" href="css/style-xlarge.1421206432.css" />

    </head>
    <body>
        <h1>User Profile</h1>
<p>Hello, Jonathan</p>
    </body>
</html>

Question: Iterating top-level array

If I have an array that looks like this:
<?php $arr = array(array("foo" => "bar"), array("bar" => "foo")) ?>
And then use it in a template like this:
<?php echo $templates->render('my_template', $arr) ?>

How can I iterate over the top array inside the template?

CSS Themes

Hi

as we have now themes folders, is possible to do the same for CSS?

Example we have all css in this structure:

assets/default/css/main.css
assets/default/css/reset.css
...

"default" is the name of theme.

If we set theme name "custom" then the Asset extension will search firstly into assets/custom/css/* folder and then into assets/default/css/* if not found.

Usage sample:

// The third params not exist, but can be similar like folders third params for search into multiple folders
$engine->loadExtension(new League\Plates\Extension\Asset('/path/to/public/assets/', true, true));

// /css/main.css is the only thing wich we pass, then is autogenerated path by checking active theme

asset('/css/main.css')?>

is already possible this scenario?

Thanks

out of default dir allocation in Template::render (feature or bug?)

my default template dir is

new \League\Plates\Engine('path/to/templates');

and my template structure looks like

`-- path
    `-- to
        `-- templates
            |-- index.php
            |-- login
            |   |-- index.php
            |   `-- partial.form.php
            `-- registration
                `-- index.php

as i cant imagine that using the "addFolder()" function is intented here
i am doing this in my ctrl

$view->render('login/index')
// or
$view->render('registration/index')
$view->render('registration/success')

but the possibility to reach for nested folders like this is not documented. So am i using it as intented or should the render function restrict me from reaching deeper into the path

thx for your hard work

Question: setting the layout on object level

Hey there, I've just started using Plates and find it to be an awesome project.

However I was wondering about something, namelyI find it to be a slight hassle having to define the parent layout in every template file, manually.

Wouldn't it be easier to also have the ability to define a master layout on object level, making it easier to change it if needed and not having to hard-code it in every template file.

I want to stress I'm new to plates, so if there's any functionality like this is already in place would you mind pointing me in the right direction?

Thanks ^^

Tests fail on windows 7 due to Directory Separator

I pulled a fresh copy of the repo and ran the tests, but got seven failed tests right off the bat.
Six of the failures were from the same root problem. Directory Separators are different in windows.

1) League\Plates\EngineTest::testGetTemplatePath
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'vfs://templates\template.php'
+'vfs://templates/template.php'

3) League\Plates\Template\NameTest::testGetPath
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'vfs://templates\template.php'
+'vfs://templates/template.php'

4) League\Plates\Template\NameTest::testGetPathWithFolder
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'vfs://templates/folder\template.php'
+'vfs://templates/folder/template.php'

5) League\Plates\Template\NameTest::testGetPathWithFolderFallback
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'vfs://templates\fallback.php'
+'vfs://templates/fallback.php'

6) League\Plates\Template\TemplateTest::testGetPath
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'vfs://templates\template.php'
+'vfs://templates/template.php'

7) League\Plates\Template\TemplateTest::testRenderDoesNotExist
Failed asserting that exception message 'The template "template" could not be found at "vfs://templates\template.php".' contains 'The template "template" could not be found at "vfs://templates/template.php".'.

Sharing data across all views

I think it would be helpful if you could share data across all templates and layouts. For example: user information shown in a template. This data would be saved to the Engine object, and then assigned to any Templates that are created.

$engine->share('username', 'Jonathan');

Naturally, if a template uses the same variable key, the shared variable will be overwritten for that particular template.

Also, as found in Laravel, composers are a way to take this even one step further. Instead of sharing a variable with all templates, it may be possible to use a callback which would allow variable assignment only when a specific template is used.

$engine->share(['template'], function($template) {
    $template->username = 'Jonathan';
});

These callbacks would occur at the time a template is rendered.

Update docs to note that folders are allowed in template names

It's possible to include folders in a template name, but the docs currently don't really say or show this. Ie:

$templates->render('partials/header');

It would be good to add this to the template's overview page, and maybe also update the nesting examples to illustrate this:

<?php $this->insert('partials/header') ?>

<p>Your content.</p>

<?php $this->insert('partials/footer') ?>

testCachedAssetUrl fails out of the box, windows 7

A fresh clone of the repo throws 7 failed tests. Six of them are discussed in #71.

The seventh test is not due to directory separators.

Here is my failure.

2) League\Plates\Extension\AssetTest::testCachedAssetUrl
Failed asserting that false is true.

C:\wamp\www\chrismichaels84\plates\tests\Extension\AssetTest.php:40

Get engine from Template

Hi,
I was just playing with some of my experiments. In that I have a feeling it is good to have a getEngine in the Template.

Cleaner templates via new compiling stage

I'm seriously considering introducing a compiling stage to Plates, with two main goals:

  1. To finally allow proper auto-escaping
  2. To cleanup the template code

I know what you're thinking: "What, a parsing stage for native PHP templates? Isn't that exactly what we're trying to avoid?" It's a fair question. But, I think a bigger question needs to be asked first: Why do developers prefer native PHP templates? I believe it's because they don't want to learn a new syntax. They are comfortable working with PHP, they know the functions available to them, and they can get the job done quick. The lack of compiling stage is just an added benefit, although given how compiled template engines handle caching, this is really a moot point.

Generally compiled template engines have a unique, custom syntax. But what if Plates templates remained 100% pure, native PHP, and the compiling stage simply helped make these templates safer (auto-escaping) and nicer to read? I think that could be pretty awesome!

Proposed compiling stage

  • Automatically insert an escape function inside all PHP short tags:
    • <?=$name?> becomes <?php echo $this->escape($name) ?>
    • To output "raw" variables, simply use regular tags: <? echo $name ?>
    • Auto-escaping can be enabled/disabled
  • Automatically add $this-> to all template functions:
    • <? layout('template') ?> becomes <?php $this->layout('template') ?>
    • Completely removes the need to ever use $this in templates
    • Still has all the benefits that an object-based template system provides
  • Automatically convert short open tags to regular tags:
    • <? becomes <?php
    • Short open tags make templates cleaner/more legible, except they're often disabled
    • This allows them to be used all the time, even when they're disabled!
    • Again, no new syntax—these are documented tags

The result

  • 100% native PHP templates
  • Proper auto-escaping (only on outputted variables)
  • No more $this pseudo-variable
  • The ability to use short open tags

Example

A new Plates 3.0 template:

<? layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$name?></p>

<? start('sidebar') ?>
     <ul>
         <li><a href="/link">Link</a></li>
         <li><a href="/link">Link</a></li>
         <li><a href="/link">Link</a></li>
     </ul>
<? stop() ?>

A compiled Plates 3.0 template:

<?php $this->layout('layouts/page', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?php echo $this->escape($name); ?></p>

<?php $this->start('sidebar') ?>
     <ul>
         <li><a href="/link">Link</a></li>
         <li><a href="/link">Link</a></li>
         <li><a href="/link">Link</a></li>
     </ul>
<?php $this->stop() ?>

What do you think?

how to avoid undefined variable?

I've create an extension of mine to automatically handle missing variables and replacing them with a default value, i.e.

$this->plate->registerFunction('default', [$this, 'extension_default']);

    function extension_default($var,$def='') {
        if (isset($var)) return $var;
        return $def;
    }

however when using the code in the template i still getting an error message abount the variable being undefined:

<html lang="<?=$this->default($page_lang,'en')?>">

avoidable with @:

<html lang="<?=@$this->default($page_lang,'en')?>">

Is there a better alternative way to fill up my page with all of those @$this->default() calls?

Extract variables

Hello,

Is there a reason why the variables are not extracted to be available in the template without using $this ?
I find it more convenient to use than var ?>

I believe it would require just this line before each include
extract(get_object_vars($this));

docs improvement

hi!

I had some trouble to get going with plates. As i think this is something you dont want to have, as plates is designed to be super simple and fast to implement.

If you would add a simple directory tree to the docs it would help much. My biggest issue was to understand that $this->layout('template') is refering to a specific file

a simple directory tree like

`-- path
    `-- to
        `-- templates
            |-- template.php
            |-- login
            |   |-- index.php
            |   `-- partial.form.php
            `-- registration

with the example

<!-- templates/template.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title><?=$this->title?></title>
</head>

<body>

<div id="content">
    <?=$this->content?>
</div>

<?php if (isset($this->sidebar)): ?>
    <div id="sidebar">
        <?=$this->sidebar?>
    </div>
<?php endif ?>

</body>
</html>
<!-- templates/login/index.php -->
<?php $this->layout('template') ?>

<?php $this->title = 'User Profile' ?>

<?php $this->start('content') ?>
    <h1>Welcome!</h1>
    <p>Hello <?=$this->e($this->name)?></p>
<?php $this->end() ?>

<?php $this->start('sidebar') ?>
    <ul>
        <li><a href="/link">Example Link</a></li>
        <li><a href="/link">Example Link</a></li>
        <li><a href="/link">Example Link</a></li>
        <li><a href="/link">Example Link</a></li>
        <li><a href="/link">Example Link</a></li>
    </ul>
<?php $this->end() ?>

that would at least helped me alot! maybe others had / have the same issues. Also you could maybe add migration guides like "using plates in symfony2" oder something similar for poeple who are stuck in a framework but want to use plates.

thx for your hard work. I hope this is the rigth way to give feedback because i couldnt find any other

Nested templates with their own variables

Hey,

Wouldn't it be great if we could have nested templates with their own variables, not shared with the parent template?

I quickly hacked something that works for my case but I'm not sure it's okay so I didn't go much further:

class Template
{
    public function insert($name, Array $data = null)
    {
        //save current template data
        $templateData = $this->getPublicVars();

        //show the nested template with its own data
        $this->data($data);
        include $this->_internal['engine']->resolvePath($name);

        //delete nested template data not existing in the parent template
        $toDelete = array_diff(array_keys($data), array_keys($templateData));
        foreach ($toDelete as $name) {
            unset($this->$name);
        }

        //put back parent template data for the rest of the page
        $this->data($templateData);
    }

    public function getPublicVars() {
        return call_user_func('get_object_vars', $this);
    }

What do you think? :)

Cheers,
Leimi

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.