1[![PHPUnit](https://github.com/salesforce/handlebars-php/actions/workflows/ci.yml/badge.svg)](https://github.com/salesforce/handlebars-php/actions/workflows/ci.yml)
2
3---
4
5#handlebars-php
6
7---
8
9#### A simple, logic-less, yet powerful templating engine for PHP
10
11---
12
13Name: **handlebars-php**
14
15License: MIT
16
17Requirements: PHP >= 5.4
18
19---
20
21
22## About Handlebars
23
24Handlebars provides the power necessary to let you build semantic templates effectively with no frustration,
25that keep the view and the code separated like we all know they should be.
26
27
28Fork of: [Handlebars.php by XaminProject](https://github.com/mardix/Handlebars)
29
30Handlebars, is the PHP port of [Handlebars.js](http://handlebarsjs.com/)
31
32---
33
34## Install Handlebars
35
36
37You can just download Handlebars.php as is, or with Composer.
38
39To install with composer, add the following in the require key in your **composer.json** file
40
41`"salesforce/handlebars-php": "1.*"`
42
43composer.json
44
45```json
46{
47    "name": "myapp/name",
48    "description": "My awesome app name",
49    "require": {
50        "salesforce/handlebars-php": "1.*"
51    }
52}
53```
54
55-----
56
57## Getting Started
58
59At the minimum, we are required to have an array model and a template string. Alternatively we can have a file containing handlebars (or html, text, etc) expression.
60
61
62
63#### Template
64
65Handlebars templates look like regular HTML, with embedded handlebars expressions.
66
67Handlebars HTML-escapes values returned by a {{expression}}.
68
69```html
70<div class="entry">
71  <h1>{{title}}</h1>
72  <div class="body">
73    Hello, my name is {{name}}
74  </div>
75</div>
76```
77
78The string above can be used as is in your PHP file, or be put in a file (ie: */templates/main.tpl*), to be called upon rendering.
79
80#### PHP file
81
82Now the we've created our template file, in a php file (index.php) we'll create the data to passed to the model. The model is a key/value array.
83
84Below we are going to create the Handlebars object, set the partials loader, and put some data in the model.
85
86**/index.php**
87
88```php
89<?php
90
91# With composer we can autoload the Handlebars package
92require_once ("./vendor/autoload.php");
93
94# If not using composer, you can still load it manually.
95# require 'src/Handlebars/Autoloader.php';
96# Handlebars\Autoloader::register();
97
98use Handlebars\Handlebars;
99use Handlebars\Loader\FilesystemLoader;
100
101# Set the partials files
102$partialsDir = __DIR__."/templates";
103$partialsLoader = new FilesystemLoader($partialsDir,
104    [
105        "extension" => "html"
106    ]
107);
108
109# We'll use $handlebars throughout this the examples, assuming the will be all set this way
110$handlebars = new Handlebars([
111    "loader" => $partialsLoader,
112    "partials_loader" => $partialsLoader
113]);
114
115# Will render the model to the templates/main.tpl template
116$model = [...];
117echo $handlebars->render("main", $model);
118```
119
120#### Assign Data
121
122The simplest way to assign data is to create an Array model. The model will contain all the data that will be passed to the template.
123```php
124<?php
125
126$model = [
127    "name" => "Yolo Baggins",
128    "title" => "I'm Title",
129    "permalink" => "blog/",
130    "foo" => "bar",
131    "article" => [
132        "title" => "My Article Title"
133    ],
134    "posts" => [
135        [
136            "title" => "Post #1",
137            "id" => 1,
138            "content" => "Content"
139        ],
140        [
141            "title" => "Post 2",
142            "id" => 2,
143            "content" => "Content"
144        ]
145    ]
146];
147```
148
149#### Render Template
150
151Use the method `Handlebars\Handlebars::render($template, $model)` to render you template once everything is created.
152
153***$template*** : Template can be the name of the file or a string containing the handlebars/html.
154
155***$model*** : Is the array that we will pass into the template
156
157The code below will render the model to the *templates/main.tpl* template
158
159```php
160echo $handlebars->render("main", $model);
161```
162
163
164Alternatively you can use $handlebars itself without invoking the render method
165
166```php
167echo $handlebars("main", $model);
168```
169
170---
171
172## Expressions
173
174Let's use this simple model for the following examples, assuming everything is already set like above.
175
176```php
177<?php
178
179$model = [
180    "title" => "I'm Title",
181    "permalink" => "/blog/",
182    "foo" => "bar",
183    "article" => [
184        "title" => "My Article Title"
185    ],
186    "posts" => [
187        [
188            "title" => "Post #1",
189            "id" => 1,
190            "content" => "Content"
191        ],
192        [
193            "title" => "Post 2",
194            "id" => 2,
195            "content" => "Content"
196        ]
197    ]
198];
199```
200
201Let's work with the template.
202
203Handlebars expressions are the basic unit of a Handlebars template. You can use them alone in a {{mustache}}, pass them to a Handlebars helper, or use them as values in hash arguments.
204
205
206The simplest Handlebars expression is a simple identifier:
207
208```html
209{{title}}
210
211-> I'm Title
212```
213
214Handlebars nested expressions which are dot-separated paths.
215
216```html
217{{article.title}}
218
219-> My Article Title
220```
221
222Handlebars nested expressions in an array.
223
224```html
225{{posts.0.title}}
226
227-> Post #1
228```
229
230Handlebars also allows for name conflict resolution between helpers and data fields via a this reference:
231
232```html
233{{./name}} or {{this/name}} or {{this.name}}
234```
235
236Handlebars expressions with a helper. In this case we're using the upper helper
237
238```html
239{{#upper title}}
240
241-> I'M TITLE
242```
243
244Nested handlebars paths can also include ../ segments, which evaluate their paths against a parent context.
245
246```html
247{{#each posts}}
248    <a href="/posts/{{../permalink}}/{{id}}">{{title}}</a>
249    {{content}}
250{{/each}}
251```
252
253Handlebars HTML-escapes values returned by a {{expression}}. If you don't want Handlebars to escape a value, use the "triple-stash", {{{ }}}
254
255```html
256{{{foo}}}
257```
258
259---
260
261
262## Control Structures
263
264`if/else` and `unless` control structures are implemented as regular Handlebars helpers
265
266### IF/ELSE
267
268You can use the if helper to conditionally render a block. If its argument returns false, null, "" or [] (a "falsy" value), Handlebars will not render the block.
269
270**Example**
271
272```html
273{{#if isActive}}
274    This part will be shown if it is active
275{{#else if isValid}}
276    This part will be shown if it is valid
277{{else}}
278    This part will be shown if isActive and isValid are both "falsy" values
279{{/if}}
280```
281
282```php
283<?php
284
285$model = [
286    "isActive" => true,
287    "isValid" => false
288];
289
290echo $handlebars->render($template, $model);
291```
292
293### UNLESS
294
295You can use the unless helper as the inverse of the if helper. Its block will be rendered if the expression returns a falsy value.
296
297```html
298{{#unless isActive}}
299    This part will not show if isActive is true
300{{/unless}}
301```
302
303---
304##Iterators: EACH
305
306You can iterate over a list using the built-in each helper. Inside the block, you can use {{this}} or {{.}} to reference the element being iterated over.
307
308**Example**
309
310```html
311<h2>All genres:</h2>
312{{#each genres}}
313    {{.}}
314{{/each}}
315
316
317{{#each cars}}
318    <h3>{{category}}</h3>
319    Total: {{count}}
320    <ul>
321    {{#each list}}
322        {{.}}
323    {{/each}}
324    </ul>
325{{/each}}
326```
327
328```php
329<?php
330
331$model = [
332    "genres" => [
333        "Hip-Hop",
334        "Rap",
335        "Techno",
336        "Country"
337    ],
338    "cars" => [
339        "category" => "Foreign",
340        "count" => 4,
341        "list" => [
342            "Toyota",
343            "Kia",
344            "Honda",
345            "Mazda"
346        ],
347        "category" => "WTF",
348        "count" => 1,
349        "list" => [
350            "Fiat"
351        ],
352        "category" => "Luxury",
353        "count" => 2,
354        "list" => [
355            "Mercedes Benz",
356            "BMW"
357        ]
358    ],
359];
360
361    echo $engine->render($template, $model);
362```
363
364### EACH/ELSE
365
366You can optionally provide an {{else}} section which will display only when the list is empty.
367
368```html
369<h2>All genres:</h2>
370{{#each genres}}
371    {{.}}
372{{else}}
373    No genres found!
374{{/each}}
375```
376
377### Slice EACH Array[start:end]
378
379The #each helper (php only) also has the ability to slice the data
380
381 * {{#each Array[start:end]}} = starts at start through end -1
382 * {{#each Array[start:]}} = Starts at start though the rest of the array
383 * {{#each Array[:end]}} = Starts at the beginning through end -1
384 * {{#each Array[:]}} = A copy of the whole array
385 * {{#each Array[-1]}}
386 * {{#each Array[-2:]}} = Last two items
387 * {{#each Array[:-2]}} = Everything except last two items
388
389```html
390<h2>All genres:</h2>
391{{#each genres[0:10]}}
392    {{.}}
393{{else}}
394    No genres found!
395{{/each}}
396```
397
398#### {{@INDEX}} and {{@KEY}}
399
400When looping through items in each, you can optionally reference the current loop index via {{@index}}
401
402```html
403{{#each array}}
404  {{@index}}: {{this}}
405{{/each}}
406
407
408{{#each object}}
409  {{@key}}: {{this}}
410{{/each}}
411```
412
413---
414
415## Change Context: WITH
416
417You can shift the context for a section of a template by using the built-in with block helper.
418
419```php
420<?php
421
422$model = [
423    "genres" => [
424        "Hip-Hop",
425        "Rap",
426        "Techno",
427        "Country"
428    ],
429    "other_genres" => [
430        "genres" => [
431        "Hip-Hop",
432        "Rap",
433        "Techno",
434        "Country"
435        ]
436]
437];
438```
439
440```html
441<h2>All genres:</h2>
442{{#with other_genres}}
443{{#each genres}}
444    {{.}}
445{{/each}}
446{{/with}}
447```
448
449---
450
451## Handlebars Built-in Helpers
452
453### If
454```html
455{{#if isActive}}
456    This part will be shown if it is active
457{{#else if isValid}}
458    This part will be shown if it is valid
459{{else}}
460    This part will be shown if isActive and isValid are both "falsy" values
461{{/if}}
462```
463
464### Unless
465```html
466{{#unless isActive}}
467    This part will show when isActive is false
468{{else}}
469    Otherwise this one will show
470{{/unless}}
471```
472
473### Each
474```html
475{{#each genres[0:10]}}
476    {{.}}
477{{else}}
478    No genres found!
479{{/each}}
480```
481
482### With
483```html
484{{#with other_genres}}
485{{#each genres}}
486    {{.}}
487{{/each}}
488{{/with}}
489```
490
491---
492
493## Other Helpers
494
495#### For convenience, Voodoo\Handlebars added some extra helpers.
496
497---
498
499### Upper
500
501To format string to uppercase
502```html
503{{#upper title}}
504```
505
506### Lower
507
508To format string to lowercase
509```html
510{{#lower title}}
511```
512
513
514### Capitalize
515
516To capitalize the first letter
517```html
518{{#capitalize title}}
519```
520
521### Capitalize_Words
522
523To capitalize each words in a string
524```html
525{{#capitalize_words title}}
526```
527
528### Reverse
529
530To reverse the order of string
531```html
532{{#reverse title}}
533```
534
535### Format_Date
536
537To format date: `{{#format_date date '$format'}}`
538```html
539{{#format_date date 'Y-m-d H:i:s'}}
540```
541
542### Inflect
543
544To singularize or plurialize words based on count `{{#inflect count $singular $plurial}}`
545```html
546{{#inflect count '%d book' '%d books'}}
547```
548
549### Truncate
550
551To truncate a string: `{{#truncate title $length $ellipsis}}`
552```html
553{{#truncate title 21 '...'}}
554```
555
556### Default
557
558To use a default value if  the string is empty: `{{#default title $defaultValue}}`
559```html
560{{#default title 'No title'}}
561```
562
563### Raw
564
565This helper return handlebars expression as is. The expression will not be parsed
566```html
567{{#raw}}
568    {{#each cars}}
569        {{model}}
570    {{/each}}
571{{/raw}}
572
573->
574
575{{#each cars}}
576    {{model}}
577{{/each}}
578```
579
580
581### Repeat
582
583To truncate a string: `{{#repeat $count}}{{/repeat}}`
584```html
585{{#repeat 5}}
586    Hello World!
587{{/repeat}}
588```
589
590Variable and blocks can still be used
591```html
592{{#repeat 5}}
593    Hello {{name}}!
594{{/repeat}}
595```
596
597
598### Define/Invoke
599
600Allow to define a block of content and use it later. It helps follow the DRY (Don't repeat yourself) principle.
601
602
603Define
604```html
605{{#define $definedName}}
606    content
607{{/define}}
608```
609
610Invoke
611```html
612{{#invoke $definedName}}
613```
614
615
616Example:
617```html
618{{#define hello}}
619    Hello World! How do you do?
620{{/define}}
621
622{{#invoke hello}}
623
624->
625
626Hello World! How do you do?
627```
628
629---
630
631### Template Comments
632You can use comments in your handlebars code just as you would in your code. Since there is generally some level of logic, this is a good practice.
633
634```html
635{{!-- only output this author names if an author exists --}}
636```
637
638---
639
640### Partials
641
642Partials are other templates you can include inside of the main template.
643
644To do so:
645
646```html
647{{> my_partial}}
648```
649
650which is a file under /templates/my_partial.html
651
652---
653
654## Writing your own helpers
655
656Block helpers make it possible to define custom iterators and other helpers that can invoke the passed block with a new context.
657
658To create your own helper, use the method: `Handlebars::addHelper($name, $callback)`
659
660The following helper will UPPERCASE a string
661
662```php
663$handlebars->addHelper("upper",
664    function($template, $context, $args, $source){
665        return strtoupper($context->get($args));
666    }
667);
668```
669
670And now we can use the helper like this:
671
672```html
673{{#upper title}}
674```
675
676---
677
678## Data Variables for #each
679
680In Handlebars JS v1.1, data variables `@first` and `@last` were added for the #each helper. Due to the these variables
681not being backwards compatible, these data variables are disabled by default and must be enabled manually.
682
683To enable the new data variables, set the `enableDataVariables` option to `true` when instantiating the Handlebars
684instance.
685
686```php
687$handlebars = new Handlebars([
688    "loader" => $partialsLoader,
689    "partials_loader" => $partialsLoader,
690    "enableDataVariables" => true
691]);
692```
693
694Given the following template and data:
695```
696{{#each data}}{{#if @first}}FIRST: {{/if}}{{this}}<br>{{/each}}
697```
698```php
699'data' => ['apple', 'banana', 'carrot', 'zucchini']
700```
701The output will be
702```html
703FIRST: apple<br>banana<br>carrot<br>zucchini<br>
704```
705
706Given the following template and the data above:
707```
708{{#each data}}{{@first}}: {{this}}<br>{{/each}}
709```
710The output will be
711```html
712true: apple<br>banana<br>carrot<br>zucchini<br>
713```
714
715Data variables also support relative referencing within multiple #each statements.
716Given
717```
718{{#each data}}{{#each this}}outer: {{@../first}},inner: {{@first}};{{/each}}{{/each}}
719```
720```php
721'data' => [['apple', 'banana'], ['carrot', 'zucchini']]
722```
723The output will be
724```
725outer: true,inner: true;outer: true,inner: false;outer: false,inner: true;outer: false,inner: false;
726```
727
728Be aware that when data variables are enabled, variables starting with `@` are considered restricted and will override
729values specified in the data.
730
731For example, given the following template and the following data, the output will be different depending on if data
732variables are enabled.
733
734```
735{{#each objects}}{{@first}}, {{@last}}, {{@index}}, {{@unknown}}{{/each}}
736```
737
738```php
739$object = new stdClass;
740$object->{'@first'} = 'apple';
741$object->{'@last'} = 'banana';
742$object->{'@index'} = 'carrot';
743$object->{'@unknown'} = 'zucchini';
744$data = ['objects' => [$object]];
745
746$engine = new \Handlebars\Handlebars(array(
747    'loader' => new \Handlebars\Loader\StringLoader(),
748    'helpers' => new \Handlebars\Helpers(),
749    'enableDataVariables'=> $enabled,
750));
751$engine->render($template, $data)
752```
753
754When `enableDataVariables` is `false`, existing behavior is not changed where some variables will be return.
755
756```
757apple, banana, 0, zucchini
758```
759
760
761When `enableDataVariables` is `true`, the behavior matches HandlebarsJS 1.1 behavior, where all data variables replace
762variables defined in the data and any data variable prefixed with `@` that is unknown will be blank.
763
764```
765true, true, 0,
766```
767
768
769#### Credits
770
771* Fork of [Handlebars.php by XaminProject](https://github.com/XaminProject/handlebars.php)
772* The documentation was edited by [Mardix](http://github.com/mardix).
773
774#### Contribution
775
776Contributions are more than welcome!