Skip to content

Commit

Permalink
Merge 93c4836 into 22bc577
Browse files Browse the repository at this point in the history
  • Loading branch information
ragboyjr committed Jan 31, 2018
2 parents 22bc577 + 93c4836 commit 4ff9c2a
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 24 deletions.
128 changes: 127 additions & 1 deletion doc/engine/extensions.md
Expand Up @@ -7,4 +7,130 @@ title: Extensions
Extensions
==========

Todo - discuss how to create a custom extension, the power of extensions and the IoC container, engine methods, stacks and composers.
Extensions in Plates are first class citizens. In fact, all of the functionality of plates is implemented via extensions. This enables very powerful customizations and features that are completely opt-in.

Every extension implements the `League\Plates\Extension` interface which is defined as:

<div class="filename">src/Extension.php</div>
```php
<?php

namespace League\Plates;

interface Extension {
public function register(Engine $plates);
}
```

Extensions can do any/all of the following:

1. Configure the engine
2. Define services in the IoC container
3. Define engine methods

Extensions use a combination of these techniques to add features to plates.

## Case Study

Probably the easiest way to learn about the features of extensions is by a case study looking at how we would implement certain features into plates using extensions. These examples are taken from the actual Plates source code and already exist in documented extensions, but these will provide a great understanding to how the system works so that you can build your own great extensions!

### Changing the Escape Charset

Let's look at an extension that will simply use the `ISO-8859-1` charset when escaping data with the `$v()` func.

<div class="filename">src/Docs/ChangeEscapeCharset/ChangeEscapeCharsetExtension.php</div>
```php

<?php

namespace League\Plates\Docs\ChangeEscapeCharset;

use League\Plates;

final class ChangeEscapeCharsetExtension implements Plates\Extension
{
public function register(Plates\Engine $plates) {
$plates->addConfig([
'escape_encoding' => 'ISO-8859-1' // defined in the RenderContextExtension
]);
}
}
```

Luckily, the RenderContextExtension exposes a configuration flag for setting the charset.

### Adding Global Data

In this example, we want to create an extension that allows us to add global values into our templates.

We want to add a func like:

```
$plates->addGlobal('varName', 'value');
```

So that in any of our templates, we can access `$varName`.

The best way to implement something like this would be via a [Template Composer](#).

<div class="filename">src/Docs/GlobalData/GlobalDataExtension.php</div>
```php
<?php

namespace League\Plates\Docs\GlobalData;

use League\Plates;

final class GlobalDataExtension implements Plates\Extension
{
public function register(Plates\Engine $plates) {
$c = $plates->getContainer(); // 1. access the ioc container
$c->add('globalData.globals', []); // 2. define an empty array in the container to store globals

// 3. define a method for adding globals
$plates->addMethods([
'addGlobal' => function(Plates\Engine $e, $name, $value) {
// 4. merge the new array with the globalData.globals definition
$e->getContainer()->merge('globalData.globals', [$name => $value]);
},
]);

// 5. push a new composer onto the stack (will execute first)
$plates->pushComposers(function($c) {
return [
// 6. create the new composer and pass in the globals array
'globalData.assignGlobals' => assignGlobalsComposer($c->get('globalData.globals'))
];
});
}
}
```

<div class="filename">src/Docs/GlobalData/global-data.php</div>
```php
<?php

namespace League\Plates\Docs\GlobalData;

use League\Plates\Template;

function assignGlobalsComposer(array $globals) {
return function(Template $t) use ($globals) {
// 7. merge the globals with the original template data so that any template defined vars overwrite globals if conflict
return $t->withData(array_merge(
$globals,
$t->data
));
};
}
```

Now this introduced quite a few more concepts than the last example, so let's break down by point:

1. The IoC Container is just a simple service container similar to Laravel Service Container or Pimple. It stores service definitions lazily so that you can configure services and later retrieve them with minimal hassle.
2. We want want to store a simple value in the container. This will be stored as is and no processing will be done to it. It's just a simple array.
3. We are adding a new engine onto the method which will give users the ability to call `$plates->addGlobal('varName', 'value')`.
4. `$container->merge` is just a simple alias for retrieving the array from the container and then calling array_merge and then updating the said container.
5. The Plates Standard Extension defines composers which are just functions that transform the `League\Plates\Template` instance. They receive one argument of a Template and return a Template always.
6. We return a keyed array because this allows us to easily override other composers if we use the same name. The naming convention with values in the container are `extensionName.value`.
7. Here are implementing the composer which merges in the global data with the templates current data.
106 changes: 103 additions & 3 deletions doc/engine/index.md
Expand Up @@ -22,12 +22,112 @@ $plates->addConfig([
// optionally call an extension defined method to further configure the engine and the extensions
$plates->addGlobal('siteName', 'Acme');

// render any custom extensions
// register any custom extensions
$plates->register(new MyCustomExtension());

echo $plates->render('home', ['title' => 'Home Page']);
```

## Creating Engines
## Creating the Engine

Todo - show examples of the several different constructor methods
The Engine comes with several constructors that offer varying degrees of customization.

### create($baseDir, $ext = null)

```php
$plates = League\Plates\Engine::create('/path/to/templates', $ext = 'phtml');
```

Creates the plates Engine sets the base dir and extension and loads all of the standard extensions.

### createWithConfig(array $config = [])

```php
$plates = League\Plates\Engine::createWithConfig([
'base_dir' => '/path/to/templates',
'ext' => 'phtml',
'escape_encoding' => 'UTF-8'
]);
```

This creates the engine, loads the standard extensions and then calls `addConfig` on the given config.

### \_\_construct(League\Plates\Util\Container $container = null)

```php
$plates = new League\Plates\Engine();
```

Creates an empty instance of the plates engine without any extensions or configuration loaded. Optionally, you can pass in an instance of the `League\Plates\Util\Container`.

**Note** This is for advanced usage only, typically you'll want to use the other static constructors.

## Configuring the Engine

Once you've created an engine, you can then configure it by loading extensions and calling extension methods.

### Registering Extensions

You can register extensions via:

```php
$plates->register(new MyAwesomeExtension());
```

### Config

You can define config values via `addConfig`.

```php
$plates->addConfig([
'config_item' => 'val'
]);
```

This merges your defined config with the existing configuration defintions. Extensions define a default set of config values which can configured to customize each extension. You'll want to read through each extension documentation to see the configuration values they support.

### Engine Methods

Extensions can also define methods on the Engine which can be used for configuration. You can define methods with `addMethods`:

```php
$plates->addMethods([
'acmeFn' => function(Engine $plates, $arg1, $arg2 = null) {
// do some stuff to the engine
}
])
```

You can then call the `acmeFn` function on the Engine. It shares the same signature as the method definition, but without the Engine method as the first argument.

```php
$plates->acmeFn('arg1'); // arg2 is optional in this instance.
```

Here are a few examples of Engine Methods which are used for configuring.

```php
// addFolder is defined by the FolderExtension
$plates->addFolder('folderName', 'folderPathPrefix');
// addGlobals is defined by the DataExtension
$plates->addGlobals([
'isLoggedIn' => true,
'siteName' => 'Acme Site'
]);
```

In this example, we've defined a folder and two globals for our Engine.

You can check each extensions' documentation to see the different engine methods they define.

## Rendering Templates

The engine provides the `render(string $name, array $data, array $attributes = []): string` for rendering a template into a string.

This is just a convenience function over:

```
$plates->getContainer()->get('renderTemplate')->render(new Template($name, $data, $attributes));
```

The `RenderTemplate` interface is what is actually responsible for rendering template instances into strings.
2 changes: 1 addition & 1 deletion doc/simple-example.md
Expand Up @@ -23,7 +23,7 @@ Here is a simple example of how to use Plates. We will assume the following dire
// Create new Plates instance
$templates = League\Plates\Engine::create('/path/to/templates');

// Render a template
// Render a template with the given data
echo $templates->render('profile', ['name' => 'Jonathan']);
~~~

Expand Down
4 changes: 4 additions & 0 deletions doc/upgrading-from-v3.md
Expand Up @@ -55,6 +55,10 @@ However, this is exactly what the Engine render method does, so you might as wel

The new interface is located here: `League\Plates\Extension`. The contents of the interface are exactly the same. We've kept the `League\Plates\Extension\ExtensionInterface` around as an alias to the former, but it is deprecated and will be removed in v4.1.

## Accessing the engine and template in Extensions

This paradigm no longer exists with v4 due to how template functions work now. Functions in v4 now accept the `League\Plates\Extension\RenderContext\FuncArgs` as the only argument which has access to the RenderTemplate and curren template instance. Since v4 also utilizes an IoC container, any dependencies needed can be injected into the function constructor. So instead of a func having access to the entire engine, they can just receive the deps they need.

## BC Extension

To do - reference the [BCExtension](https://github.com/thephpleague/plates/issues/214) once it gets built.
Expand Down
6 changes: 1 addition & 5 deletions src/Engine.php
Expand Up @@ -36,7 +36,7 @@ public static function createWithConfig(array $config = []) {
}

/** @return string */
public function render($template_name, array $data = [], array $attributes = []) {
public function render(string $template_name, array $data = [], array $attributes = []): string {
return $this->container->get('renderTemplate')->renderTemplate(new Template(
$template_name,
$data,
Expand All @@ -56,10 +56,6 @@ public function __call($method, array $args) {
throw new \BadMethodCallException("No method {$method} found for engine.");
}

/** @deprecated kept for bc */
public function loadExtension(Extension $extension) {
$this->register($extension);
}
public function register(Extension $extension) {
$extension->register($this);
}
Expand Down
4 changes: 4 additions & 0 deletions src/Extension/Path/PathExtension.php
Expand Up @@ -32,6 +32,10 @@ public function register(Plates\Engine $plates) {
'path.relative' => relativeResolvePath(),
]);
});
$plates->defineConfig([
'ext' => 'phtml',
'base_dir' => null,
]);
$plates->pushComposers(function($c) {
return [
'path.normalizeName' => normalizeNameCompose($c->get('path.normalizeName')),
Expand Down
22 changes: 14 additions & 8 deletions src/Extension/RenderContext/RenderContextExtension.php
Expand Up @@ -44,14 +44,6 @@ public function register(Plates\Engine $plates) {
'end' => [endFunc()]
];
});
$plates->pushComposers(function($c) {
return [
'renderContext.renderContext' => renderContextCompose(
$c->get('renderContext.factory'),
$c->get('config')['render_context_var_name']
)
];
});
$c->add('include.bind', function($c) {
return renderContextBind($c->get('config')['render_context_var_name']);
});
Expand All @@ -62,6 +54,20 @@ function() use ($c) { return $c->get('renderTemplate'); },
);
});

$plates->defineConfig([
'render_context_var_name' => 'v',
'escape_encoding' => null,
'escape_flags' => null,
]);
$plates->pushComposers(function($c) {
return [
'renderContext.renderContext' => renderContextCompose(
$c->get('renderContext.factory'),
$c->get('config')['render_context_var_name']
)
];
});

$plates->addMethods([
'registerFunction' => function(Plates\Engine $e, $name, callable $func, callable $assert_args = null, $simple = true) {
$c = $e->getContainer();
Expand Down
5 changes: 0 additions & 5 deletions src/PlatesExtension.php
Expand Up @@ -8,11 +8,6 @@ public function register(Engine $plates) {
$c = $plates->getContainer();

$c->add('config', [
'render_context_var_name' => 'v',
'ext' => 'phtml',
'base_dir' => null,
'escape_encoding' => null,
'escape_flags' => null,
'validate_paths' => true,
'php_extensions' => ['php', 'phtml'],
'image_extensions' => ['png', 'jpg'],
Expand Down
2 changes: 1 addition & 1 deletion test/integration/path.spec.php
Expand Up @@ -6,7 +6,7 @@
it('will normalize any path type names', function() {
$plates = Plates\Engine::create(__DIR__ . '/fixtures/normalize-name');
$plates->addGlobals(['name' => 'Bar']);
$plates->assignTemplateData('main', ['name' => 'Foo']);
$plates->addData(['name' => 'Foo'], 'main');

$expected = "name: Foo";
expect($plates->render(__DIR__ . '/fixtures/normalize-name/main'))->equal($expected);
Expand Down

0 comments on commit 4ff9c2a

Please sign in to comment.