76 Commits
Author SHA1 Message Date
mcndave 200b58bdf5 - WIP on supporting multiple contexts, better handling of caching for methods / functions (instead of in every files, etc... 2024-05-07 12:28:15 +00:00
mcndave 5e54407f74 - WIP on urlextension 2023-12-07 15:46:25 +00:00
Dave Mc Nicoll b856743741 - Merged some code added a long time ago to the extension 2023-12-01 20:27:26 +00:00
mcndave 10fb17c7c3 - Removed Ui def 2023-11-15 23:26:27 -05:00
mcndave 8b6dd85fd5 - Added PPH-DI definition file 2023-11-15 22:12:25 -05:00
mcndave 8c7a4d730b - quickfix 2023-11-08 06:54:30 -05:00
mcndave ed0eb8225b Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-11-03 19:52:51 -04:00
mcndave 0e655fd387 - Added a default action for print 2023-11-03 19:52:15 -04:00
mcndave ce43abb954 - Added some flags for PrintExtension outputting 2023-11-01 09:57:43 -04:00
mcndave 5aa70dfd17 - Fixed missing formatter loading 2023-10-31 14:30:56 +00:00
mcndave 043fb90f8d - WIP on Picea's Asset. 2023-10-17 20:12:39 -04:00
mcndave d5de5e665b - WIP on picea-asset AND error handling 2023-10-12 19:07:16 +00:00
mcndave 3ccbf6bfab - WIP on picea-asset 2023-10-12 11:43:01 -04:00
mcndave 42d84796ee - Optional parameter of Opcache is now fixed 2023-08-31 09:15:05 -04:00
mcndave 386e0aa448 - Fixed error thrown on optional routes which arguments were not all fullfield 2023-06-09 15:31:53 -04:00
mcndave be65d45b41 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-05-31 14:40:23 -04:00
mcndave 7d2bb8d407 - Quickfix on context name 2023-05-31 14:40:14 -04:00
mcndave 215bc26981 - UrlExtension now supports multiple application URL 2023-05-31 18:29:36 +00:00
Dave Mc Nicoll 3dce6a41cb - Added a batch() function to ease array iterations in templates 2023-05-01 14:36:47 -04:00
mcndave f9c955a82a - Fixed port redirection for URL behind proxy 2023-04-28 19:57:52 +00:00
Dave Mc Nicoll 2fe97c60ed - Fixed a bad merge 2023-04-28 19:56:05 +00:00
Dave Mc Nicoll 710ecad98d - Adjusted route name on exception 2023-04-28 19:52:19 +00:00
mcndave 3bcd5c9780 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-04-20 18:16:29 +00:00
mcndave 7a2fa872fa - Added a no_html() method 2023-04-20 18:16:20 +00:00
mcndave 5182ba19bc - Fixed optional arguments in buildUrl() 2023-04-19 13:23:29 -04:00
mcndave 57367968ca Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-04-13 15:50:23 -04:00
mcndave b1e1324bfa - Fixed some PHP 8.x bugs (missing vars within compiler's context) and removed Picea's error handler - needs some work before putting online 2023-04-13 15:49:45 -04:00
mcndave 44b0283938 - Merged with new code-base 2023-03-30 14:46:52 -04:00
mcndave 1611ebe047 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-03-30 18:29:35 +00:00
mcndave 6ee3e1bbae - Forced parameters in URL to be filled in UrlExtension buildUrl() and disabled the custom exception for now... 2023-03-30 18:29:17 +00:00
mcndave 169400cd61 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-03-29 15:13:54 +00:00
mcndave b4964402fd - Renamed MoneyExtension to NumberExtension 2023-03-29 15:13:41 +00:00
mcndave a65aa72733 - Fixed bug Creation of dynamic property Picea\Compiler\BaseContext:: 2023-03-29 14:02:11 +00:00
mcndave b3a8e3aca2 - Token name and options are now passed through parse() method 2023-02-02 19:38:40 +00:00
mcndave e9b2f0ece3 - Fixed a bug added in previous commit 2023-02-02 18:12:31 +00:00
mcndave acd9f8c72e - First task of v2 done, tokens can now have unlimited options attached to it 2023-02-02 16:46:46 +00:00
mcndave afeb8789b5 - Added a new doc section about both new tokens 'section.prepend/append' 2023-02-01 18:20:42 +00:00
mcndave ded3d5e907 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-02-01 18:09:01 +00:00
mcndave 9ea4856f49 - Added tokens 'section.append' and 'section.prepend' which equates longer form '{% section mysection, [ "action" => "prepend" ] %}' 2023-02-01 18:08:47 +00:00
mcndave 84020a871c Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-02-01 18:06:32 +00:00
mcndave 2dc16de7c3 - Simplified SLUG() method of the UrlExtension, since it was way too slow 2023-02-01 18:06:23 +00:00
mcndave af212b07ca Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2023-01-26 13:36:49 +00:00
mcndave 58766decaf - Fixed a bug within sections if it doesn't exist first 2023-01-26 13:36:38 +00:00
mcndave 08211282c2 - Added an event manager. Also, fixed some more bugs within Block and Section tokens 2023-01-26 13:32:52 +00:00
mcndave 58f73d914b - Some more documentation made / fixed 2022-12-22 14:35:26 +00:00
mcndave 46535c2734 - Finalizing the documentation before tagging 1.0 release 2022-12-21 04:20:11 +00:00
mcndave bb29a56077 - Added while/endwhile and do/while syntaxes
- Fix the abstractclass requirement of parse() function
2022-12-19 19:36:39 +00:00
mcndave 33298ff2e4 - Removed optional brackets from routes in UrlExtension. 2022-12-08 19:10:43 +00:00
mcndave 3de5f46de6 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2022-11-29 19:46:51 +00:00
mcndave 4bd4cddbff - Cleaned up a bit and added some methods to give Lean/Console more flexibility 2022-11-29 19:46:40 +00:00
mcndave 7e80d4be6d - Added an 'urlize' method to the UrlExtension class 2022-11-24 20:00:43 +00:00
mcndave 14cfb46c98 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2022-10-12 18:28:52 +00:00
mcndave 08201b1958 - Remove the old {? / ?} token as a PHP opening
- Added url versions if needed
2022-10-12 18:25:39 +00:00
mcndave 5bac6cd843 - Rencoding UTF-8 to manage problematics strings in data to encode 2022-10-10 15:27:54 +00:00
mcndave 4c3772376c - Quickfix in block error message output without variables 2022-07-18 18:14:49 +00:00
mcndave aa0bdea0c5 - added a new lang.raw and _.raw token to Lang extexion 2022-07-18 17:44:03 +00:00
mcndave 531f786747 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2022-06-27 17:29:31 +00:00
mcndave 22e046dfcd - Fixed post() method 2022-06-27 17:28:35 +00:00
mcndave b08325049b - Addedd a new Money extension and a new Function token 2022-06-03 15:31:50 +00:00
mcndave 48ecd9f7d9 - Made some bugfixes regarding blocks and slots 2022-06-03 15:30:16 +00:00
Ubuntu 18a0fd7fdc - Adjusted some protocol recognition into var to match proxy's one 2022-05-11 15:20:04 +00:00
Dave Mc Nicoll 2b81e9cef8 - Addeed a slug method 2022-05-11 14:09:41 +00:00
Dave Mc Nicoll b836fc5bd8 - added missing json.html tag 2022-02-25 15:32:47 +00:00
Dave Mc Nicoll 45c8173029 - Mostly bugfixes made while working on ParcInfo 2021-10-19 12:39:14 +00:00
mcndave 67040db155 - Added the missing continue token 2021-03-24 13:49:38 +00:00
mcndave 7588748ebd - Global variables were merged by error with the block's arguments 2021-03-15 13:48:31 +00:00
mcndave f143b082ae Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2021-03-01 16:37:19 +00:00
mcndave 09408827da - Fixed the request() function which was not working at all 2021-03-01 16:36:59 +00:00
mcndave 30af321215 - Corrected a bug where the 'or' variable was not reset properly on a iterations of for/foreach. 2021-03-01 16:07:39 +00:00
mcndave 8df38b568c - A lot of bugfixes made for this release. 2021-02-16 03:17:32 +00:00
mcndave 4e909b2840 - Added the PrintExtension 2021-01-06 19:31:58 +00:00
Dave Mc Nicoll 82eca2e520 - Fixed ViewToken and BlockToken inline vars 2020-11-27 11:32:01 -05:00
mcndave 082bdd4ad3 - WIP on the exception handler 2020-10-22 13:19:26 +00:00
mcndave 69db3efdce Merge branch 'master' of https://git.mcnd.ca/mcndave/picea 2020-10-21 03:57:02 +00:00
mcndave 1334278e37 - Added basic exception remodeling, seaking original template sources. 2020-10-21 03:20:35 +00:00
mcndave 2f8abebc44 - Added a new Include token (which was added as RAW content)
- Fixed UrlExtension to reflect on notes-routes changes
- Added some methods to FileFetcher
2020-10-20 19:38:37 +00:00
87 changed files with 2219 additions and 342 deletions

No files matched your search

+10 -1
View File
@@ -6,7 +6,7 @@
"authors": [
{
"name": "Dave Mc Nicoll",
"email": "mcndave@gmail.com"
"email": "info@mcnd.ca"
}
],
"autoload": {
@@ -16,5 +16,14 @@
},
"require": {
"psr/http-message": "^1.0"
},
"extra": {
"lean": {
"autoload": {
"definitions": [
"meta/definitions.php"
]
}
}
}
}
+47
View File
@@ -0,0 +1,47 @@
# Picea
Welcome to the official Picea documentation.
This library offers modern features from templating language, while simply generating normal PHP after compilation, such as view inheritance, definable blocks, 'or' token on unrunned looped, etc...
Picea uses the same delimiters that Twig uses, which are `{% %}`, `{{ }}` and `{# #}`.
The first `{% %}` is used for most **control structure** and **extensions**.
The `{{ }}` delimiter is used to **echo escaped content** in a page. (see *01-echoing*)
The `{# #}` is exclusively used as a **comment** enclosure. (see *01-comment*)
## Quick start
Render a simple Picea view:
```php
$picea = new Picea\Picea();
$picea->renderHtml('view/path/hello_world');
```
And the view content could look like:
*path/hello_world*
```html
<!DOCTYPE html>
<html>
<head>
<title>Picea's simple </title>
</head>
<body>
<nav>
{% foreach $navigation as $navItem %}
<a href="{% route $navItem->route %}" title="{{ $navItem->description }}">
{{ $navItem->caption }}
</a>
{% endforeach %}
</nav>
{{ $someText }}
</body>
</html>
```
+26
View File
@@ -0,0 +1,26 @@
# Control structures - Comments
There is no single-line token to comment code within Picea, it's a tag working as such :
**[PICEA]** So, using this code:
```html
{# This is how you comment in a Picea view #}
<div>
{# Is it hello or good bye ? not sure on this ! #}
<i>Good bye world !</i>
</div>
{# This is a multi-line comment
<div>This part will not be rendered</div>
#}
```
**[HTML]** Would render as such:
```html
<div>
<i>Good bye world !</i>
</div>
```
+73
View File
@@ -0,0 +1,73 @@
# Echoing text or data
There is two token groups that you can use to print content into a view, `echo` or `print`.
Escaped by default : `{% echo $argument %}` = `{{ $argument }}` = `{% print $argument %}`
Raw when needed: `{% echo.raw $argument %}` = `{{= $argument }}` = `{% print.raw $argument %}`
## Outputing content to a view using `echo` / `{{ }}` / `print`, `echo.raw` / `{{= }}` / `print.raw`
Using `print` or `echo`, which are, by default, made safer by using PHP's native `htmlspecialchars`.
**[PICEA]** So, using this code:
```html
{{ "Hello World !" }}
{% echo "This is another way to output <safe> content" %}
{% print "This is the third way to output <safe> content" %}
<hr>
{% echo.raw $someHTML %}
{% print.raw $someOtherHTML %}
{{= $someMoreHTML }}
```
**[PHP]** Would yield internally:
```html
<?php echo htmlspecialchars((string) "Hello World !", 3, 'UTF-8', true) ?>
<?php echo htmlspecialchars((string) "This is another way to output &lt;safe&gt; content", 3, 'UTF-8', true) ?>
<?php echo htmlspecialchars((string) "This is another way to output &lt;safe&gt; content", 3, 'UTF-8', true) ?>
<hr>
<?php echo $someHTML ?>
<?php echo $someOtherHTML ?>
<?php echo $someMoreHTML ?>
```
## Using string format variant `printf` / `printf.raw`
Those tokens represents the equivalent of the printf() function from PHP.
**[PICEA]** So, using this code:
```html
{% php
$num = 5;
$location = 'tree';
%}
<span>{% printf 'There are %d monkeys in the %s', $num, $location %}</span>
<i>{% printf.raw 'There are %d monkeys in the %s', $num, $location %}</i>
```
**[PHP]** Would render internally as :
```html
<?php
$num = 5;
$location = 'tree';
?>
<span><?php echo htmlspecialchars((string) sprintf('There are %d monkeys in the %s', $num, location), 3, 'UTF-8', true); ?></span>
<i><?php printf('There are %d monkeys in the %s', $num, location) ?></i>
```
+86
View File
@@ -0,0 +1,86 @@
# Control structure - Comparisons
Most control structures used within PHP view templates are supported natively in Picea.
The goal for this project always has been to not loose original feature while removing some limitations with PHP's original templating syntax.
## Comparison using `if` / `then` / `else` / `elseif` / `endif`
Comparisons works the same way as using PHP's alternative syntax.
This is also how they are rendered in the compilation process.
**[PICEA]** So, using this code:
```html
{% php $search = 'Pack my box with five dozen liquor jugs' %}
{% if strpos($search, 'beer') !== false %}
<span>Found 'beer' into {{ $search }} !</span>
{% elseif strpos($search, 'liquor') !== false %}
<span>Found 'liquor' into {{ $search }} !</span>
{% else %}
<b>Neither 'beer' or 'liquor' were found in {{ $search }}</b>
{% endif %}
```
**[PHP]** Would yield:
```php
<?php $search = 'Pack my box with five dozen liquor jugs'; ?>
<?php if (strpos($search, 'beer') !== false): ?>
<span>Found 'beer' into <?php echo htmlspecialchars((string) $search, 3, 'UTF-8', true) ?> !</span>
<?php elseif (strpos($search, 'beer') !== false): ?>
<span>Found 'liquor' into <?php echo htmlspecialchars((string) $search, 3, 'UTF-8', true) ?> !</span>
<?php else: ?>
<b>Neither 'beer' or 'liquor' were found in <?php echo htmlspecialchars((string) $search, 3, 'UTF-8', true) ?></b>
<?php endif ?>
```
And then, the code would be runned through PHP's native `include` mechanic.
## Comparison using `switch` / `case` / `break` / `endswitch`
Using switches within HTML in plain PHP can be quite cumbersome because of it's limitation
disallowing any output (including whitespace) between it's statement.
Picea will allow some more complex (and readable) switches within your views.
**[PICEA]** So, using this code:
```php
{% php $selected = random_int(0,5) %}
{% switch $selected %}
{% case 1 %}
{{ "One person selected" }}
{% break %}
{% case 2 %}
{% default %}
{{ "Multiple person ($selected) selected" }}
{% break %}
{% endswitch %}
```
**[PHP]** Would render as such:
```php
<?php $selected = random_int(0,5) ?>
<?php switch($selected):
case 1: ?>
<?php echo htmlspecialchars((string) "One person selected", 3, 'UTF-8', true) ?>
<?php break; ?>
<?php case 2: ?>
<?php default: ?>
<?php echo htmlspecialchars((string) "Multiple person ($selected) selected", 3, 'UTF-8', true) ?>
<?php break; ?>
<?php endswitch; ?>
```
@@ -0,0 +1,170 @@
# Control structure - `extends` / `section`
A nice feature of most robust templating engine is the ability to inherit from other view.
Picea follows a similar train of tought, since it's also possible create a chain of inheritance
using `extends` which have definable parts you can declare using `section`.
## Basic `extends` (string $path)
You must provide a valid `$path` from which the template will be inherited.
**[PICEA]** So, using this code:
*path/base/layout.phtml*
```html
<!DOCTYPE html>
<html>
<head>
{% section "head" %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title() }} - AnExampleApp</title>
{% section %}
</head>
<body>
<header id="header"">
{% section "header" %}{% view "path/base/navigation" %}{% endsection %}
</header>
<main id="main">{% section "main" %}{% endsection %}</main>
<footer id="footer">
{% section "footer" %}
&copy; Copyright {{ date('Y') }}
{% endsection %}
</footer>
</body>
</html>
```
*path/home.phtml*
```html
{% extends "path/base/layout" %}
{% title "Home page" %}
{% section "main" %}
<h1>Welcome !</h1>
{# @TODO integrate our new blog engine below ! #}
<article>
This is our new blog ! We hope you are gonna enjoy your stay on our new platform !
</article>
{% endsection %}
```
**[HTML]** Would render as such :
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Home page - AnExampleApp</title>
</head>
<body>
<header id="header"">
<nav>
<a href="#home">Home</a>
<a href="#login">Login</a>
</nav>
</header>
<main id="main">
<h1>Welcome !</h1>
<article>
This is our new blog ! We hope you are gonna enjoy your stay on our new platform !
</article>
</main>
<footer id="footer">
&copy; Copyright 2022
</footer>
</body>
</html>
```
### Inherit an already extended view
We could use the previous file `path/home` and generate, let's say, the same page, but without a navigation menu.
**[PICEA]** So, using this code:
*path/home-navless.phtml*
```html
{% extends "path/home" %}
{% section "header" %}{% endsection %}
```
**[HTML]** Would render as such :
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Home page - AnExampleApp</title>
</head>
<body>
<header id="header""></header>
<main id="main">
<h1>Welcome !</h1>
<article>
This is our new blog ! We hope you are gonna enjoy your stay on our new platform !
</article>
</main>
<footer id="footer">
&copy; Copyright 2022
</footer>
</body>
</html>
```
Notice that the `<header>` tag is now empty, since we've redeclared it in our navless view.
## Overview of `section` (string $name, array $options)
In your `$name` variable, accepted characters are alpha-numeric and those specific caracters : `.-_:`, so
names suches as :
`body.content`
`my-cool-section:heading`
`MyOtherSection`
`_another_accepted_name`
Allowed options are :
string `action` : allowed are `prepend`, `default`, `append`
int `order` : if you must
### Actions can also be passed using specials tokens :
`section.prepend` and `section.append` can also be used without passing an `action` option.
*path/home-nav.phtml*
```html
{% extends "path/home" %}
{% section "header" %}
<a href="#mylink">First link here !</a>
{% endsection %}
```
*path/home-admin-nav.phtml*
```html
{% extends "path/home-nav" %}
{% section.prepend "header" %}
<h4>My new NAV header</h4>
{% endsection %}
{% section.append "header" %}
<a href="#mylink">Second link here !</a>
{% endsection %}
```
**[HTML]** Would render as such :
```html
<h4>My new NAV header</h4>
<a href="#mylink">First link here !</a>
<a href="#mylink">Second link here !</a>
```
+42
View File
@@ -0,0 +1,42 @@
# Control structure - `function`
Sometimes comes a need to have a small subset of code which is gonna be used twice (or more)
in the same view. Instead of simply duplicating the code (with everything that can then go wrong if you ever need
to play with it later), you could create a `function`.
Functions are declared exactly like vanilla PHP.
**[PICEA]** So, using this code:
```html
{% use Psr\Http\Message\ServerRequestInterface %}
{% title "My generic title" %}
{% function printCustomTitle(ServerRequestInterface $request) : bool %}
{% if $request->getAttribute('lean.route')->name === 'home' %}
<h4 class="title">This is a custom title !</h4>
{% return true %}
{% endif %}
{% return false %}
{% endfunction %}
{% if ! printCustomTitle($request) %}
<h1>{{ title() }}</h1>
{% endif %}
```
**[HTML]** Would yield:
*page **is** 'home'*
```php
<h4>This is a custom title !</h4>
```
*page **is not** 'home'*
```php
<h4>My generic title</h4>
```
+193
View File
@@ -0,0 +1,193 @@
# Control structure - Loops
Picea's loop works the same as PHP original alternative syntax.
There is, however, some minors improvments and a new `{% or %}` clause working much like the `{% else %}` clause in a comparison.
## Loop using `for` / `or` / `continue` / `break` / `endfor`
The simplest of sequence loop, `for` / `endfor` simply iterate over a given counter (or whatever your needs).
**[PICEA]** So, using this code:
```html
{% php $users = array_slice([ 'Tom', 'Sam', 'Mario', 'Steve' ], 0, random_int(0, 4)) %}
<h1>User list</h1>
<ul>
{% for $i = 0; $i < count($users); $i++ %}
{% if $users[$i] === 'Steve' %}
{# Will leave the loop if user's name is Steve #}
{% break %}
{% else %}
<li>{{ $users[$i] }}</li>
{% endif %}
{% or %}
<li class="empty">Given user list was empty</li>
{% endfor %}
</ul>
```
**[PHP]** Would compile like :
```php
<?php $users = array_slice([ 'Tom', 'Sam', 'Mario', 'Steve' ], 0, random_int(0, 4)) ?>
<h1>User list</h1>
<ul>
<?php for($i = 0; $i < count($users); $i++): ?><?php $for_a1kro2k2o = true;
<?php if ($users[$i] === 'Steve'): ?>
<?php break ?>
<?php else: ?>
<li><?php echo htmlspecialchars((string) $users[$i], 3, 'UTF-8', true) ?></li>
<?php endif ?>
<?php endfor ?><?php if (empty($for_a1kro2k2o)): ?><li class="empty">Given user list was empty</li><?php endif ?>
</ul>
```
**[HTML]** And would render as such given random_int() returns a **3**:
```html
<h1>User list</h1>
<ul>
<li>Tom</li>
<li>Sam</li>
<li>Mario</li>
</ul>
```
**[HTML]** Or would render as such given random_int() returns a **0**:
```html
<h1>User list</h1>
<ul>
<li class="empty">Tom</li>
</ul>
```
## Loop using `foreach` / `or` / `continue` / `break` / `endforeach`
The more complex `foreach` / `endforeach` allows to iterate over keys and values of an array.
**[PICEA]** So, using this code:
```html
{# Generate a random list of 0 to 4 names #}
{% php $users = array_slice([ 'Tom', 'Sam', 'Mario', 'Steve', 'Joan' ], 0, random_int(0, 5)) %}
<h1>Random User list</h1>
<ul>
{% foreach $users as $index => $name %}
{% if $name === 'Steve' %}
{# We skip Steve, but allows Joan #}
{% continue %}
{% endif %}
<li>{{ $name }}</li>
{% or %}
<li class="empty">Given user list was empty</li>
{% endforeach %}
</ul>
```
**[HTML]** Could render as such if prior random_int() returns '**2**':
```html
<h1>User list</h1>
<ul>
<li>Tom</li>
<li>Sam</li>
</ul>
```
**[HTML]** Could render as such given random_int() returns '**0**':
```html
<h1>User list</h1>
<ul></ul>
```
## Loop using `while` / `or` / `continue` / `break` / `endwhile`
This syntax allows to loop on a computed iteration count.
**[PICEA]** So, using this code:
```html
{% php $fruits = [ 'apple', 'pear', 'banana', 'tomato' ] %}
<h1>Grocery list</h1>
<ul>
{% while $item = array_pop($fruits) %}
<li>{{ ucfirst($item) }}</li>
{% or %}
<li class="empty">We should never see this, since the list is always populated !</li>
{% endwhile %}
</ul>
```
**[HTML]** Would render as such:
```html
<h1>Grocery list</h1>
<ul>
<li>Apple</li>
<li>Pear</li>
<li>Banana</li>
<li>Tomato</li>
</ul>
```
## Loop using `do` / `continue` / `break` / `while`
This syntax do not have an alternative syntax in vanilla PHP.
Picea allows for this unusual syntax to be used the same way you would with any other control structure.
There is, however, no support for the `or` clause for this structure since it will iterate at least once.
**[PICEA]** So, using this code:
```html
<h1>Random number generator</h1>
<ul>
{% do %}
{% php $number = random_int(1, 25); %}
{% if $number === 10 %}
{# For a reason I'm not entirely sure why, 10 must not be displayed ! #}
{% continue %}
{% endif %}
<li>{{ str_pad($number, 2, '0' ,STR_PAD_LEFT) }}</li>
{% while $number !== 15 %} {# Loop stops whenever $number is equal to 15 #}
</ul>
```
**[HTML]** Could render as such:
```html
<h1>Random number generator</h1>
<ul>
<li>02</li>
<li>12</li>
<li>07</li>
<li>13</li>
<li>04</li>
<li>07</li>
<li>01</li>
<li>16</li>
<li>14</li>
<li>24</li>
<li>14</li>
<li>01</li>
<li>15</li>
</ul>
```
+34
View File
@@ -0,0 +1,34 @@
# Control structure - `Use`
While working with inside a template, it might be needed that you reference some other namespaces.
The use clause works exaclty like PHP's vanilla top of the page `use`.
**[PICEA]** So, using this code:
```html
{% use AnyVendor\My\Super\Randomizer %}
{# One chance out of ten to end this loop %}
{% while Randomizer::run(1, 10) === 10 %}
{% if Randomizer::run(1, 100) < 50 %}
<span>Lower than 50 !</span>
{% else %}
<strong>Greater than 50 !</strong>
{% endif %}
{% endwhile %}
<h4>DONE !</h4>
```
**[HTML]** Could render as such output:
```html
<span>Lower than 50 !</span>
<span>Greater than 50 !</span>
<span>Lower than 50 !</span>
<span>Greater than 50 !</span>
<span>Lower than 50 !</span>
<span>Lower than 50 !</span>
<h4>DONE !</h4>
```
@@ -0,0 +1,112 @@
# Control structure - View (or `include`) / Block
There is three ways to include other views into your current view `{% view %} / {% include %}` or
using `{% block %}{% endblock %}`.
The order of loading depends on the order given within your directory configuration; the first file found from it
will be used.
## Inline `view` (string $path, array|null $arguments = null)
You must provide a valid `$path` from which the template will be loaded. If no `$arguments` are provided, defined
variables from the caller's scope will be given (from `get_defined_vars()`).
**[PICEA]** So, using this code:
*path/base/nav.phtml*
```html
<nav>
{% foreach $navigation as $index => $navItem %}
{% view "path/base/nav-item", [ 'item' => $navItem, 'index' => $index] %}
{% endforeach %}
</nav>
```
*path/base/nav-item.phtml*
```html
<a href="{{ $item->anchor }}" tabindex="{{ $index }}">{{ $item->name }}"</a>
```
**[HTML]** Could render such as :
```html
<nav>
<a href="#top">Top</a>
<a href="#bottom">Bottom</a>
<a href="#left">Left</a>
<a href="#right">Right</a>
</nav>
```
## `include` (string $path) content from external file
Whenever you need to `include` a raw file from one of your view directories
## Reusable `block` (string $path, ...$arguments) / `endblock`
A better way to achieve this behaviour could be to create a `block` which you can define as needed.
**[PICEA]** So, using this code:
*path/base/nav.phtml*
```html
<nav>
{% foreach $navigation as $index => $navItem %}
{% block "path/base/nav-item-block", $navItem->name, $navItem->anchor, $index %}{% endblock %}
{% endforeach %}
</nav>
```
*path/base/nav-item-block.phtml*
```html
{% arguments string $name, string $anchor, int $index = 0 %}
<a href="{{ $anchor }}" tabindex="{{ $index }}">{{ $name }}"</a>
```
**[HTML]** Would render the same as the `view` example :
```html
<nav>
<a href="#top">Top</a>
<a href="#bottom">Bottom</a>
<a href="#left">Left</a>
<a href="#right">Right</a>
</nav>
```
### Extending a `block` using `define` and `slot`
You might need to define some custom content inside of a `block`.
You can do so by using `define` and `slot`.
**[PICEA]** So, using this code:
*path/base/nav.phtml*
```html
<nav>
{% foreach $navigation as $index => $navItem %}
{% block "path/base/nav-item-block", $navItem->name, $navItem->anchor, $index %}
{% slot "attributes" %} class="nav-item" href="{{ $anchor }}" {% endslot %}
{% endblock %}
{% endforeach %}
</nav>
```
*path/base/nav-item-block.phtml*
```html
{% arguments string $name, string $anchor, int $index = 0 %}
{% define slot %}
<a {% slot "attributes" %}href="{{ $anchor }}" tabindex="{{ $index }}"{% endslot %}>{{ $name }}"</a>
```
**[HTML]** Would render the same as the `view` example :
```html
<nav>
<a href="#top">Top</a>
<a href="#bottom">Bottom</a>
<a href="#left">Left</a>
<a href="#right">Right</a>
</nav>
```
View File
Whitespace-only changes.
+4
View File
@@ -0,0 +1,4 @@
# Extension - Creating an extension
+4
View File
@@ -0,0 +1,4 @@
# Extension - `json`, `json.pretty`, `json.html`
This extension
View File
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
<?php
View File
Whitespace-only changes.
+82
View File
@@ -0,0 +1,82 @@
# Using Dependency Injection
Here's an excerpt from Lean's definition file 'template.php' which also loads Picea\Ui.
It also insert `Tell` inside the LanguageHandler which handles the `{% _ 'relative.lang.key' %}` and `{% lang 'my.full.lang.key' %}`.
```php
use function DI\autowire, DI\create, DI\get;
use Laminas\Diactoros\Response\HtmlResponse;
use Picea\{ Picea, Caching\Cache, Caching\Opcache, Compiler, Compiler\Context, Compiler\BaseContext, FileFetcher, Language\DefaultRegistrations, Method\Request };
use Picea\Extension\{ LanguageHandlerInterface, LanguageExtension, TitleExtension, NumberExtension, UrlExtension };
use Picea\Ui\{ Method, Ui };
return [
Picea::class => function($c) {
return new Picea($c->get(Context::class), $c->get(Cache::class), $c->get(Compiler::class), null, $c->get(FileFetcher::class), null, getenv("DEBUG"));
},
Context::class => function($c) {
return new BaseContext( $c->get(Lean\Lean::class)->getPiceaContext() );
},
Compiler::class => function($c) {
return new Compiler(new class(array_merge([
$c->get(LanguageExtension::class),
$c->get(TitleExtension::class),
$c->get(NumberExtension::class),
$c->get(UrlExtension::class),
$c->get(Method\Form::class),
$c->get(Method\Pagination::class),
$c->get(Request::class),
], class_exists(\Taxus\Picea\Extension::class) ? [ $c->get(\Taxus\Picea\Extension::class) ] : [],
array_map(fn($class) => $c->get($class), $c->get(Lean\Lean::class)->getPiceaExtensions() ))) extends DefaultRegistrations {
public function registerAll(Compiler $compiler) : void
{
parent::registerAll($compiler);
( new Ui() )->registerFormExtension($compiler);
}
});
},
Request::class => autowire(Request::class),
Method\Form::class => autowire(Method\Form::class),
Method\Pagination::class => autowire(Method\Pagination::class),
LanguageExtension::class => create(LanguageExtension::class)->constructor(get(LanguageHandlerInterface::class)),
LanguageHandlerInterface::class => function($c) {
return new class( $c->get(Tell\I18n::class) ) implements LanguageHandlerInterface {
public Tell\I18n $tell;
public function __construct(Tell\I18n $tell) {
$this->tell = $tell;
}
public function languageFromKey(string $key, array $variables = []) : array|string
{
return $this->tell->fromKey($key, $variables) ?: "";
}
};
},
TitleExtension::class => autowire(TitleExtension::class),
NumberExtension::class => autowire(NumberExtension::class),
UrlExtension::class => create(UrlExtension::class)->constructor(get(Context::class), getenv("URL_BASE"), get('git.commit')),
Cache::class => create(Opcache::class)->constructor(getenv("CACHE_PATH"), get(Context::class)),
FileFetcher::class => function($c) {
return new FileFetcher($c->get(Lean\Lean::class)->getViewPaths());
},
];
```
+59
View File
@@ -0,0 +1,59 @@
<?php
use function DI\autowire, DI\create, DI\get;
use Picea\{
Caching\Cache,
Caching\Opcache,
Compiler,
Compiler\Context,
Compiler\BaseContext,
FileFetcher,
Method\Request
};
use Picea\Extension\{ LanguageHandlerInterface, LanguageExtension, TitleExtension, NumberExtension, UrlExtension };
return [
Picea\Picea::class => function($c) {
return new Picea\Picea($c->get(Context::class), $c->get(Cache::class), $c->get(Compiler::class), null, $c->get(FileFetcher::class), null, getenv("DEBUG"));
},
Context::class => function($c) {
return new BaseContext($c->get(Lean\Lean::class)->getPiceaContext());
},
Compiler::class => autowire(Compiler::class),
Request::class => autowire(Request::class),
LanguageExtension::class => create(LanguageExtension::class)->constructor(get(LanguageHandlerInterface::class)),
# LanguageHandlerInterface::class => autowire(\Lean\LanguageHandler::class),
# LanguageRegistration::class => create(\Lean\PiceaDefaultRegistration::class)->constructor(get('picea.extensions'), [], [], get(Ui::class), null),
'picea.extensions' => function(\Psr\Container\ContainerInterface $c) {
return array_merge([
$c->get(LanguageExtension::class),
$c->get(TitleExtension::class),
$c->get(NumberExtension::class),
$c->get(UrlExtension::class),
$c->get(Request::class),
], class_exists(\Taxus\Picea\Extension::class) ? [ $c->get(\Taxus\Picea\Extension::class) ] : [],
array_map(fn($class) => $c->get($class), $c->get(Lean\Lean::class)->getPiceaExtensions())
);
},
TitleExtension::class => autowire(TitleExtension::class),
NumberExtension::class => autowire(NumberExtension::class),
UrlExtension::class => create(UrlExtension::class)->constructor(getenv("URL_BASE"), get('git.commit'), explode(',', getenv('APP_URL')), (bool) getenv('FORCE_SSL')),
Cache::class => create(Opcache::class)->constructor(getenv("CACHE_PATH"), get(Context::class)),
FileFetcher::class => function($c) {
return new FileFetcher($c->get(Lean\Lean::class)->getViewPaths());
},
];
+6 -2
View File
@@ -4,7 +4,7 @@ namespace Picea;
class Builder
{
const TEMPLATE_CLASSNAME_PREFIX = "PiceaTemplate_";
public const TEMPLATE_CLASSNAME_PREFIX = "PiceaTemplate_";
protected string $templatePath = "";
@@ -15,13 +15,17 @@ class Builder
public function build(Compiler\Context &$context, string $compiledSource) : Compiler\Context
{
$path = fn($p) => addslashes(str_replace('/', DIRECTORY_SEPARATOR, $p));
$context->className = static::generateClassName($context->viewPath);
$replace = [
'%NAMESPACE%' => $context->namespace,
'%USE%' => ( $uses = $context->renderUses() ) ? "use $uses;" : false,
'%CLASSNAME%' => $context->className,
'%PATHNAME%' => $context->viewPath,
'%PATHNAME%' => $path($context->viewPath),
'%FULLPATH%' => $path($context->filePath),
'%TEMPLATE%' => $path($this->templatePath),
'%EXTENDS%' => $context->extendFrom ? "extends " . static::TEMPLATE_CLASSNAME_PREFIX . static::generateClassUID($context->extendFrom) : '',
'%EXTENDS_TEMPLATE%' => $context->extendFrom,
'%CONTENT%' => $compiledSource,
+115 -6
View File
@@ -5,11 +5,16 @@ namespace %NAMESPACE%;
%USE%
# %PATHNAME%
if (! class_exists("%NAMESPACE%\%CLASSNAME%", false) ) {
class %CLASSNAME% %EXTENDS% {
public array $blockList = [];
public array $sectionList = [];
public array $sectionStack = [];
public array $variableList = [];
public ?object $thisProxy = null;
@@ -18,6 +23,12 @@ class %CLASSNAME% %EXTENDS% {
public static $context;
public bool $rendering = false;
public bool $renderingInsideSection = false;
public int $depth = 0;
public function __construct(\Picea\Picea $picea, array $variablesList = [], ?object $thisProxy = null) {
$this->picea = $picea;
$this->variableList = $variablesList;
@@ -25,45 +36,143 @@ class %CLASSNAME% %EXTENDS% {
$this->exportFunctions();
static::$context = $picea->context;
$picea->declareLoadedTemplate(static::class, __FILE__);
}
public function output(array $variablesList = []) : void
{
( function($___class__template, $___global_variables, $___variables, $picea) {
$__event = new \Picea\Builder\ClassTemplateEvent();
$this->rendering = true;
$this->depth++;
$__event->eventExecute(\Picea\Event\Builder\ClassTemplateOutputing::class, $variablesList);
try {
( function($___class__template, $___global_variables, $___variables, $__event, $picea) {
extract($___global_variables);
extract($___variables, \EXTR_OVERWRITE);
?>%CONTENT%<?php
} )->call($this->thisProxy ?? new class(){}, $this, $this->variableList, $variablesList, $this->picea);
%PARENT_OUTPUT%
} )->call($this->thisProxy ?? new class() {}, $this, $this->variableList, $variablesList, $__event, $this->picea);
} catch (\Throwable $error) {
throw $this->errorHandler($error);
}
public function renderSection($name) : void
$__event->eventExecute(\Picea\Event\Builder\ClassTemplateOutputDone::class, $variablesList);
try { %PARENT_OUTPUT% } catch (\Throwable $error) {
throw $this->errorHandler($error);
}
$this->depth--;
$this->rendering = false;
}
public function renderSection(string $name, bool $return = false) /*: string|void*/
{
$result = [];
foreach([ 'prepend', 'default', 'append' ] as $item) {
if ( !isset($this->sectionList[$name][$item]) || ! is_array($this->sectionList[$name][$item]) ) continue;
usort($this->sectionList[$name][$item], fn($a, $b) => $a['order'] <=> $b['order']);
foreach($this->sectionList[$name][$item] as $section) {
if ($return) {
ob_start();
}
$section['callback']();
if ($return) {
$result[] = ob_get_clean();
}
if ( $item === 'default' ) {
break 1;
}
}
}
return $return ? implode("", $result) : "";
}
public function exportFunctions() : void
{
static $caching = [];
%FUNCTIONS%
}
public static function getSourceLineFromException(int $sourceLine) : ? int
{
$selfSource = file_get_contents(__FILE__);
foreach(explode("\n", $selfSource) as $line => $content) {
if ( strpos($content, str_replace('$', '%', '/*$EXCEPTION_LINE_BASE$*/')) !== false ) {
return $sourceLine - $line;
}
}
$sourceFile = file_get_contents("%TEMPLATE%");
if ( $sourceFile ) {
foreach(explode("\n", $sourceFile) as $line => $content) {
if ( strpos($content, str_replace('$', '%', '$CONTENT$')) !== false ) {
return $sourceLine - $line;
}
}
}
return null;
}
public static function getParam(string $param) : ? string
{
return [
'classname' => "%CLASSNAME%",
'namespace' => "%NAMESPACE%",
'extends' => "%EXTENDS_TEMPLATE%",
'view' => "%FULLPATH%",
][$param] ?? null;
}
public function __invoke(array $variablesList = []) : string
{
ob_start();
$this->output($variablesList);
return ob_get_clean();
}
public static function isTemplateError(\Throwable $error) : bool
{
$class = substr(static::class, strripos(static::class, '\\') + 1) . ".php";
return substr($error->getFile(), -strlen($class)) === $class;
}
return [ 'classname' => "%CLASSNAME%", 'namespace' => "%NAMESPACE%", 'extends' => "%EXTENDS_TEMPLATE%" ];
protected function errorHandler(\Throwable $error) : \Throwable
{
$class = substr(static::class, strrpos(static::class, '\\') + 1) . ".php";
#dump( __NAMESPACE__, basename($error->getFile()), $class );
$basename = basename($error->getFile());
#dump(is_subclass_of(sprintf("\\%s\\%s", __NAMESPACE__, substr($basename,0 , strrpos($basename, '.'))), static::class, true));
if (str_ends_with($error->getFile(), $class)) {
$error = new \Picea\Exception\RenderHtmlException($this, $error->getMessage(), $error->getCode(), $error);
}
#elseif (is_subclass_of(sprintf("\\%s\\%s", __NAMESPACE__, substr($basename,0 , strrpos($basename, '.'))), static::class, true)) {
# $error = new \Picea\Exception\RenderHtmlException($this, $error->getMessage(), $error->getCode(), $error);
#}
return $error;
}
}
}
return [ 'classname' => %CLASSNAME%::getParam('classname'), 'namespace' => %CLASSNAME%::getParam('namespace'), 'extends' => %CLASSNAME%::getParam('extends'), 'view' => %CLASSNAME%::getParam('view') ];
+8
View File
@@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Picea\Builder;
class ClassTemplateEvent
{
use \Picea\EventTrait;
}
+5
View File
@@ -29,4 +29,9 @@ interface Cache {
* @return bool
*/
public function save(Context $context) : bool;
/**
* Purge path cache
*/
public function purge() : void;
}
+20 -12
View File
@@ -2,8 +2,7 @@
namespace Picea\Caching;
use Picea\Builder,
Picea\Compiler\Context;
use Picea\ { Builder, Compiler\Context };
class Opcache implements Cache {
@@ -13,9 +12,9 @@ class Opcache implements Cache {
protected array $compiled = [];
protected ? Context $context;
protected null|Context $context;
public function __construct(?string $cachePath = null, ? Context $context)
public function __construct(?string $cachePath = null, null|Context $context = null)
{
$this->cachePath = $cachePath ?? sys_get_temp_dir();
$this->context = $context;
@@ -35,7 +34,7 @@ class Opcache implements Cache {
$compiledContext = $this->compiled[$viewPath];
$fullName = isset($compiledContext['namespace']) ? "\\{$compiledContext['namespace']}\\{$compiledContext['classname']}" : $compiledContext['classname'];
$fullName = isset($compiledContext['namespace']) ? "\\{$compiledContext['namespace']}\\{$compiledContext['classname']}" : ($compiledContext['classname'] ?? $viewPath);
return new $fullName(...$arguments);
}
@@ -58,10 +57,6 @@ class Opcache implements Cache {
if ( file_exists($path = $this->cachePath($viewPath)) ) {
$this->compiled[$viewPath] = include($path);
# if ( $this->compiled[$viewPath]['extends'] ?? false ) {
# $this->compiled($this->compiled[$viewPath]['extends']);
# }
return true;
}
@@ -86,7 +81,7 @@ class Opcache implements Cache {
return true;
}
protected function cachePath(string $fileName = "") : string
public function cachePath(string $fileName = "") : string
{
return implode(DIRECTORY_SEPARATOR, array_filter([ $this->cachePath, $this->cacheFolder, $fileName ? str_replace([ "/", DIRECTORY_SEPARATOR ], "~", Builder::generateClassName($fileName) . ".php") : null ]));
}
@@ -95,7 +90,7 @@ class Opcache implements Cache {
spl_autoload_register(function ($class) use ($namespace) {
$prefix = "$namespace\\";
$baseDir = $this->cachePath() . "/";
$baseDir = $this->cachePath() . DIRECTORY_SEPARATOR;
$len = strlen($prefix);
@@ -103,11 +98,24 @@ class Opcache implements Cache {
return;
}
$file = $baseDir . str_replace('\\', '/', substr($class, $len)) . '.php';
$file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, substr($class, $len)) . '.php';
if ( file_exists($file) ) {
require $file;
}
});
}
public function getFilelist() : array
{
return glob($this->cachePath() . DIRECTORY_SEPARATOR . Builder::TEMPLATE_CLASSNAME_PREFIX . "*");
}
public function purge() : void
{
foreach($this->getFilelist() as $dir)
{
dump($dir);
}
}
}
+64 -19
View File
@@ -2,10 +2,17 @@
namespace Picea;
use Picea\Event\Compiler\CompileTokenExtension;
use Picea\Event\Compiler\CompileTokenExtensionEvent;
use Picea\Event\Compiler\CompileTokenTagEvent;
use Picea\Exception\RegisterExtensionToken;
use Picea\Extension\FunctionExtension;
use Picea\Language\LanguageRegistration;
class Compiler
{
use EventTrait;
protected string $sourceCode = "";
protected array $syntaxObjectList = [];
@@ -18,11 +25,10 @@ class Compiler
public array $extensionList = [];
public ? LanguageRegistration $languageRegistration;
public function __construct(?LanguageRegistration $languageRegistration = null)
public function __construct(
public LanguageRegistration $languageRegistration
)
{
$this->languageRegistration = $languageRegistration;
$this->languageRegistration->registerAll($this);
}
@@ -45,21 +51,24 @@ class Compiler
$this->sourceCode = preg_replace_callback($replace, function ($matches) use (&$context) {
$matches[2] = trim($matches[2]);
list($token, $arguments) = array_pad(array_filter(explode(' ', $matches[2], 2), 'strlen'), 2, null);
$token = trim($token);
$token = strtolower(trim($token));
$tokenName = $context->tokenName($token);
$tokenOptions = $context->tokenOptions($token);
# @TODO Refractor this parts to allows registration to the tag's name
if ( $this->tagList[$token] ?? false ) {
return $this->tagList[$token]->parse($context, $arguments, $token);
if ( $this->tagList[$tokenName] ?? false ) {
$this->eventExecute(CompileTokenTagEvent::class, $context, $arguments, $tokenName, $tokenOptions);
return $this->tagList[$tokenName]->parse($context, $arguments, $tokenName, $tokenOptions);
}
elseif ( $this->extensionList[$token] ?? false ) {
return $this->extensionList[$token]->parse($context, $arguments, $token);
elseif ( $this->extensionList[$tokenName] ?? false ) {
$this->eventExecute(CompileTokenExtensionEvent::class, $context, $arguments, $tokenName, $tokenOptions);
return $this->extensionList[$tokenName]->parse($context, $arguments, $tokenName, $tokenOptions);
}
else {
throw new \LogicException("Impossible to find token `$token` declared in `{$matches[2]}`. Perhapse you forgot to add a custom token to Picea's engine ?");
throw new \LogicException("Impossible to find token `$tokenName` declared in `{$matches[2]}`. Perhapse you forgot to add a custom token to Picea's engine ?");
}
}, $this->sourceCode);
@@ -81,13 +90,18 @@ class Compiler
public function registerSyntax(Syntax\Syntax $syntaxObject) : self
{
$this->syntaxObjectList[get_class($syntaxObject)] = $syntaxObject;
return $this;
}
public function registerControlStructure(ControlStructure\ControlStructure $controlStructureObject) : self
{
foreach($controlStructureObject->tokens ?? (array) ( $controlStructureObject->token ?? [] ) as $token) {
$this->tagList[$token] = $controlStructureObject;
if ( strpos($token, '.') !== false ) {
throw new RegisterExtensionToken(sprintf("Could not register token '%s' from control structure '%s' in this version. Options are now delt by extensions themselve instead.", $token, $controlStructureObject::class));
}
$this->tagList[strtolower($token)] = $controlStructureObject;
}
return $this;
@@ -95,22 +109,53 @@ class Compiler
public function registerExtension(Extension\Extension $extension) : self
{
foreach($extension->tokens ?? (array) ( $extension->token ?? [] ) as $token) {
$this->extensionList[$token] = $extension;
$tokens = $extension->tokens ?? (array) ( $extension->token ?? [] );
foreach($tokens as $token) {
if ( strpos($token, '.') !== false ) {
throw new RegisterExtensionToken(sprintf("Could not register token '%s' from extension '%s' in this version. Options are now delt by extensions themselve instead.", $token, $extension::class));
}
$this->extensionList[strtolower($token)] = $extension;
}
if (! $tokens ) {
$this->extensionList[] = $extension;
}
return $this;
}
public function registerExtensionsFunctions(Compiler\Context $context) : void
{
foreach($this->extensionList as $ext) {
if ($ext instanceof FunctionExtension) {
foreach ($ext->exportFunctions() as $name => $value) {
$callable = is_string($value) ? fn(...$args) => call_user_func_array([ $ext, $value ], $args) : null;
$context->pushFunction(is_numeric($name) ? $value : $name, $callable ?? $value);
}
}
}
}
public function __toString() : string
{
return "???";
}
public function getExtensionFromToken(string $name) : Extension\Extension
{
if ( false === $this->extensionList[$name] ?? false ) {
$tokenName = explode('.', $name)[0];
if ( ! isset($this->extensionList[$tokenName]) ) {
throw new \InvalidArgumentException(<<<MSG
Requested extension from token `$name` could not be found from loaded Picea compiler's extension.
Requested extension from token `$tokenName` could not be found from loaded Picea compiler's extension.
MSG);
}
return $this->extensionList[$name];
return $this->extensionList[$tokenName];
}
public function exportFunctions() : void
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Compiler;
class BaseContext extends Context {
public function __construct(?string $defaultNamespace = null) {
public function __construct(array|string|null $defaultNamespace = null) {
if ( $defaultNamespace !== null ) {
$this->namespace = $defaultNamespace;
}
+31 -2
View File
@@ -6,7 +6,7 @@ use Picea\Compiler;
abstract class Context {
public string $namespace = "";
public string|array $namespace = "";
public string $extendFrom = "";
@@ -14,14 +14,24 @@ abstract class Context {
public string $compiledSource = "";
public ? string $sectionAction = null;
public string $viewPath = "";
public string $filePath = "";
public string $classPath = "";
public array $switchStack = [];
public array $iterateStack = [];
public array $iterationStack = [];
public array $useStack = [];
public array $sections = [];
public int $functions = 0;
public array $functionStack = [];
public array $hooks = [];
@@ -52,9 +62,15 @@ abstract class Context {
public function renderFunctions() : string
{
if ($this->extendFrom) {
return "parent::exportFunctions();";
}
else {
$cls = $this->compiledClassPath();
$ns = $this->namespace ? "\\{$this->namespace}\\" : "";
$list = ['static $caching = [];'];
foreach ($this->functionStack as $name => $function) {
$list[] = <<<FUNC
if ( false === ( ( \$caching['$ns$name'] ?? false) || function_exists( '$ns$name' ) ) ) {
@@ -69,12 +85,25 @@ abstract class Context {
return implode(PHP_EOL, $list);
}
}
public function exportFunctions() : void
{
}
public function tokenName(string $token) : string
{
return explode('.', $token)[0];
}
public function tokenOptions(string $token, bool $export = false) : array|string
{
$options = array_slice(explode('.', strtolower($token)), 1);
return $export ? var_export($options, true) : $options;
}
public function pushFunction($name, Callable $callable) : void
{
$this->functionStack[$name] = $callable;
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace Picea\ControlStructure;
abstract class AbstractLoop implements ControlStructure {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
switch($token) {
case "while":
case "foreach":
$name = "$".uniqid("{$token}_");
$stack = array_filter($context->iterationStack ?? [], function($item) {
return ! $item['or'];
});
$count = count($stack);
if ( $count > 0 ) {
$name .= "[" . end($stack)['uid'] . "]";
}
$context->iterationStack[] = [
'or' => false,
'uid' => $name,
'token' => "end{$token}",
];
return "<?php $token ($arguments): $name = ( $name ?? 0 ) + 1; ?>";
case "endwhile":
case "endforeach":
$last = end($context->iterationStack);
if ( $last['or'] === false ) {
$output = "<?php $token; ?>";
}
else {
$output = "<?php endif; if ( isset({$last['uid']}) ) unset({$last['uid']}); ?>";
}
array_pop($context->iterationStack);
return $output;
}
}
}
+25 -9
View File
@@ -6,7 +6,8 @@ class BlockToken implements ControlStructure {
public array $token = [ "arguments", "block", "endblock", "define", "slot", "endslot", "using" ];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
static $slotDefinitions = [];
switch($token) {
@@ -24,14 +25,16 @@ class BlockToken implements ControlStructure {
return <<<PHP
<?php
try {
extract( \$this->using ?? [], \EXTR_OVERWRITE);
extract( \\$class::parseArguments(function($arguments) {}, \$inlineVariables), \EXTR_OVERWRITE);
unset(\$inlineVariables);
}
catch(\TypeError \$ex) {
throw new \Exception('A block awaiting arguments `$arguments` instead received `' . implode(', ', array_map('gettype', \$inlineVariables)) . '`');
throw new \Exception(
sprintf('A block awaiting arguments `%s` instead received `%s` with values `%s`', '$arguments', implode(', ', array_map('gettype', \$inlineVariables ?? [])), json_encode(\$inlineVariables))
);
}
?>
/*%EXCEPTION_LINE_BASE%*/?>
PHP;
case "define":
@@ -48,6 +51,8 @@ class BlockToken implements ControlStructure {
list($name, $definition) = array_pad(explode(',', $arguments, 2), 2, "");
$loops = count($context->iterationStack ?? []) ? ",". implode(', ', array_filter(array_column($context->iterationStack, 'uid'), fn($e) => strpos($e, '[') === false)) : null;
if ($def->hasDefinitions() ) {
$slotName = eval("return $name;");
$def->currentSlot = $slotName;
@@ -55,13 +60,21 @@ class BlockToken implements ControlStructure {
$definition = $def->printDefinition($slotName);
if ($definition) {
$definition .= ",";
}
return <<<PHP
<?php \$this->printSlot($name, function($definition, array \$___using = []) { extract(\$___using, \EXTR_SKIP); ?>
<?php \$this->printSlot($name, function($definition array \$___using = []) use (\$picea $loops) { extract(\$___using, \EXTR_SKIP); ?>
PHP;
}
else {
if ($definition) {
$definition .= ",";
}
return <<<PHP
<?php \$___block->slotIsSet($name) || \$___block->setSlot($name, function($definition, array \$___using = []) { extract(\$___using, \EXTR_SKIP); ?>
<?php (\$___block ?? \$this)->slotIsSet($name) || \$___block->setSlot($name, function($definition array \$___using = []) use (\$picea $loops) { extract(\$___using, \EXTR_SKIP); ?>
PHP;
}
@@ -72,11 +85,11 @@ class BlockToken implements ControlStructure {
$definition = $def->getCurrentSlotDefinitionVars();
if ($definition) {
$definition = "$definition,";
$definition .= ",";
}
return <<<PHP
<?php })->call(\$this, $definition \$this->using); ?>
<?php })->call(\$this, $definition array_merge(get_defined_vars(), \$this->using)); ?>
PHP;
}
else {
@@ -120,6 +133,9 @@ class BlockToken implements ControlStructure {
elseif ( $value->isVariadic() ) {
$parameters[ $value->getName() ] = [];
}
else {
$parameters[ $value->getName() ] = null;
}
}
return $parameters;
@@ -207,7 +223,7 @@ class BlockToken implements ControlStructure {
{
$this->rendering = true;
return $classTemplate->picea->inlineHtml($this, $this->viewPath, ...$this->arguments);
return $classTemplate->picea->inlineBlock($this, $this->viewPath, ...$this->arguments);
}
public function setSlot(string $name, Callable $method) : void
+2 -1
View File
@@ -6,7 +6,8 @@ class BreakToken implements ControlStructure {
public string $token = "break";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
return "<?php break; ?>";
}
+2 -1
View File
@@ -6,7 +6,8 @@ class CaseToken implements ControlStructure {
public string $token = "case";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
$output = "";
if ( $context->switchStack ) {
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace Picea\ControlStructure;
class ContinueToken implements ControlStructure {
public string $token = "continue";
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
return "<?php continue; ?>";
}
}
+1 -1
View File
@@ -3,5 +3,5 @@
namespace Picea\ControlStructure;
interface ControlStructure {
public function parse(\Picae\Compiler\Context &$context, string $sourceCode, string $token);
public function parse(\Picea\Compiler\Context &$context, string $sourceCode, string $token);
}
+1 -1
View File
@@ -6,7 +6,7 @@ class DefaultToken implements ControlStructure {
public string $token = "default";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
$output = "";
if ( $context->switchStack ) {
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace Picea\ControlStructure;
class EchoToken implements ControlStructure {
public array $token = [ "echo", "echo.raw" ];
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
switch($token) {
case "echo":
return "<?php \\Picea\\ControlStructure\\EchoToken::echoSafe($arguments) ?>";
case "echo.raw":
return "<?php \\Picea\\ControlStructure\\EchoToken::echoRaw($arguments) ?>";
}
}
public static function echoRaw($arguments) : string
{
return "<?php echo $arguments ?>";
}
public static function echoSafe($arguments) : string
{
return "<?php echo $arguments ?>";
}
}
-12
View File
@@ -1,12 +0,0 @@
<?php
namespace Picea\ControlStructure;
class EndRawToken implements ControlStructure {
public string $token = "endraw";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ class ExtendsToken implements ControlStructure {
public string $token = "extends";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $path, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $path, string $token) {
# Triming string's quotes
$path = trim($path, "\"\' \t");
+5 -3
View File
@@ -6,7 +6,7 @@ class ForToken implements ControlStructure {
public array $token = [ "for", "endfor" ];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
switch($token) {
case "for":
$uid = "$".uniqid("for_");
@@ -20,11 +20,13 @@ class ForToken implements ControlStructure {
return "<?php for ($arguments): {$uid} = 1; ?>";
case "endfor":
if ( end($context->iterationStack)['or'] === false ) {
$last = end($context->iterationStack);
if ( $last['or'] === false ) {
$output = "<?php endfor; ?>";
}
else {
$output = "<?php endif; ?>";
$output = "<?php endif; unset({$last['uid']}) ?>";
}
array_pop($context->iterationStack);
+2 -38
View File
@@ -2,44 +2,8 @@
namespace Picea\ControlStructure;
class ForeachToken implements ControlStructure {
use DI\Definition\Source\AnnotationBasedAutowiring;
class ForeachToken extends AbstractLoop {
public array $token = [ "foreach", "endforeach" ];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
switch($token) {
case "foreach":
$name = "$".uniqid("foreach_");
$stack = array_filter($context->iterationStack ?? [], function($item) {
return ! $item['or'];
});
$count = count($stack);
if ( $count > 0 ) {
$name .= "[" . end($stack)['uid'] . "]";
}
$context->iterationStack[] = [
'or' => false,
'uid' => $name,
'token' => 'endforeach',
];
return "<?php foreach ($arguments): $name = ( $name ?? 0 ) + 1; ; ?>";
case "endforeach":
if ( end($context->iterationStack)['or'] === false ) {
$output = "<?php endforeach; ?>";
}
else {
$output = "<?php endif; ?>";
}
array_pop($context->iterationStack);
return $output;
}
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace Picea\ControlStructure;
class FunctionToken implements ControlStructure {
public array $token = [ "function", "endfunction", "return" ];
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
switch($token) {
case "function":
$context->functions++;
return $this->printFunction($context, $arguments);
case "return":
if ( empty($context->functions) ) {
throw new \RuntimeException("A function return tag {% return %} was found without an opening {% function ... %} tag");
}
return $this->printReturn($context, $arguments);
case "endfunction":
if ( empty($context->functions) ) {
throw new \RuntimeException("A function closing tag {% endfunction %} was found without an opening {% function ... %} tag");
}
$context->functions--;
return $this->printEndFunction($context);
}
}
protected function printFunction($context, ?string $arguments) : string
{
return "<?php function $arguments { ?>";
}
protected function printReturn($context, ?string $arguments) : string
{
return "<?php return $arguments; ?>";
}
protected function printEndFunction($context) : string
{
return "<?php } ?>";
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ class IfToken implements ControlStructure {
public array $token = [ "if", "else", "elseif", "endif" ];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
switch($token) {
case "if":
return "<?php if ($arguments): ?>";
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace Picea\ControlStructure;
class IncludeToken implements ControlStructure {
public string $token = "include";
public function parse(\Picea\Compiler\Context &$context, ? string $viewPath, string $token) {
return "<?php echo \$___class__template->picea->inlineContent($viewPath); ?>";
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ class NamespaceToken implements ControlStructure {
public string $token = "namespace";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
$context->namespace = $arguments;
return "";
}
+2 -2
View File
@@ -6,9 +6,9 @@ class OrToken implements ControlStructure {
public string $token = "or";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
if ( empty($context->iterationStack) ) {
throw new \LogicException("Token `or` was used outside of iterator. Make sure your `for` or `foreach` declaration are properly made.");
throw new \LogicException("Token `or` was used outside of iterator. Make sure your `for`, `foreach`, `while`, `do/until` declaration are properly made.");
}
$key = count( $context->iterationStack ) - 1;
-12
View File
@@ -1,12 +0,0 @@
<?php
namespace Picea\ControlStructure;
class RawToken implements ControlStructure {
public string $token = "raw";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
}
}
+36 -8
View File
@@ -2,11 +2,28 @@
namespace Picea\ControlStructure;
enum PrintActionEnum : string {
case prepend = "prepend";
case default = "default";
case append = "append";
# case both = "both";
}
class SectionToken implements ControlStructure {
public array $token = [ "section", "endsection" ];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
protected PrintActionEnum $action = PrintActionEnum::default;
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
if (in_array('prepend', $options)) {
$this->action = PrintActionEnum::prepend;
}
elseif (in_array('append', $options)) {
$this->action = PrintActionEnum::append;
}
switch($token) {
case "section":
return $this->printSection($context, $arguments);
@@ -16,6 +33,8 @@ class SectionToken implements ControlStructure {
throw new \RuntimeException("A section closing tag {% endsection %} was found without an opening {% section %} tag");
}
$this->action = PrintActionEnum::default;
return $this->printEndSection($context);
}
}
@@ -28,7 +47,7 @@ class SectionToken implements ControlStructure {
$options = eval("return $options;");
}
if ( ! ctype_alnum(str_replace([".", "\"", "'", "-", "_"], "", $name)) ) {
if ( ! ctype_alnum(str_replace([".", "\"", "'", "-", "_", ":"], "", $name)) ) {
throw new \RuntimeException("Your section named `{$name}` contains invalid character. Allowed are only letters, numbers, dashes, underscores and dots");
}
@@ -37,7 +56,7 @@ class SectionToken implements ControlStructure {
'options' => $options,
];
$action = $options['action'] ?? "default";
$action = $options['action'] ?? $this->action->value;
if (! in_array($action, ['prepend', 'append', 'default'])) {
throw new \RuntimeException("An unsupported action `$action` was given as an option of a {% section %} tag");
@@ -45,15 +64,24 @@ class SectionToken implements ControlStructure {
$order = $options['order'] ?? "count(\$___class__template->sectionList[$name]['$action'])";
return "<?php \$___class__template->sectionList[$name] ??= [ 'prepend' => [], 'append' => [], 'default' => [] ];
\$___class__template->sectionList[$name]['$action'][] = [ 'order' => $order, 'callback' => function() use (\$picea, \$___class__template, \$___global_variables, \$___variables) {
extract(\$___global_variables); extract(\$___variables, \EXTR_OVERWRITE); ?>";
return "<?php \$___class__template->sectionList[$name] ??= [ 'prepend' => [], 'append' => [], 'default' => [] ];" .
"\$___class__template->sectionList[$name]['$action'][] = [ ".
"'order' => $order, " .
"'callback' => function() use (\$picea, \$___class__template, \$___global_variables, \$___variables, \$__event) {" .
"extract(\$___global_variables); extract(\$___variables, \EXTR_OVERWRITE);" .
"\$___class__template->sectionStack[] = $name;" .
"\$__event->eventExecute(\Picea\Event\Builder\ClassTemplateRenderSection::class, $name);?>";
}
protected function printEndSection($context) : string
{
$section = array_pop($context->sections);
$build = $context->extendFrom ? "" : "\$___class__template->renderSection({$section['name']});";
return "<?php }]; $build?>";
$build = $context->extendFrom ? "!empty(\$___class__template->sectionStack) && \$___class__template->renderSection({$section['name']}, false);" : "\$___class__template->renderSection({$section['name']}, false);";
return "<?php \$__event->eventExecute(\Picea\Event\Builder\ClassTemplateRenderSectionDone::class, {$section['name']});" .
"array_pop(\$___class__template->sectionStack); }];" .
$build .
"?>";
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ class SwitchToken implements ControlStructure {
public array $token = [ "switch", "case", "endswitch" ];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
switch($token) {
case "switch":
$context->switchStack[] = true;
+1 -1
View File
@@ -6,7 +6,7 @@ class UseToken implements ControlStructure {
public string $token = "use";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
$context->useStack[] = $arguments;
return "";
}
+3 -2
View File
@@ -6,7 +6,8 @@ class ViewToken implements ControlStructure {
public string $token = "view";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
return "<?php echo \$___class__template->picea->renderHtml($arguments, get_defined_vars(), \$this); ?>";
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
# The way this is ordered, if you provide a second arguments, being an array of variables, get_defined_vars() will not be pushed inside the view
return "<?php echo \$___class__template->picea->inlineHtml(\$this, $arguments, get_defined_vars()); ?>";
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace Picea\ControlStructure;
class WhileToken extends AbstractLoop {
public array $token = [ "do", "while", "endwhile", ];
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string {
switch($token) {
case "do":
$context->iterationStack[] = [
'or' => false,
'token' => "do",
];
return "<?php do { ?>";
case "while":
if ( $context->iterationStack ?? false ) {
if ( end($context->iterationStack)['token'] === 'do' ) {
array_pop($context->iterationStack);
return "<?php } while($arguments); ?>";
}
}
}
return parent::parse($context, $arguments, $token);
}
}
@@ -0,0 +1,7 @@
<?php
namespace Picea\Event\Builder;
interface ClassTemplateOutputDone {
public function execute(string $name, array $variablesList) : mixed;
}
@@ -0,0 +1,7 @@
<?php
namespace Picea\Event\Builder;
interface ClassTemplateOutputing {
public function execute(string $name, array $variablesList) : mixed;
}
@@ -0,0 +1,7 @@
<?php
namespace Picea\Event\Builder;
interface ClassTemplateRenderSection {
public function execute(string $name) : mixed;
}
@@ -0,0 +1,7 @@
<?php
namespace Picea\Event\Builder;
interface ClassTemplateRenderSectionDone {
public function execute(string $section) : mixed;
}
@@ -0,0 +1,10 @@
<?php
namespace Picea\Event\Compiler;
use Picea\Compiler\Context;
interface CompileTokenExtensionEvent
{
public function execute(Context $context, ?string $arguments, string $token, array $options = []);
}
@@ -0,0 +1,10 @@
<?php
namespace Picea\Event\Compiler;
use Picea\Compiler\Context;
interface CompileTokenTagEvent
{
public function execute(Context &$context, ?string $arguments, string $token, array $options = []);
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace Picea\Event\Extension;
interface BuildAssetUrl
{
public function execute(string $uri, array $parameters = [], bool $appendVersion = true) : mixed;
}
@@ -0,0 +1,8 @@
<?php
namespace Picea\Event\Extension;
interface UrlBuildAssetEvent
{
public function execute(string $uri, array $parameters = [], bool $appendVersion) : void;
}
@@ -0,0 +1,8 @@
<?php
namespace Picea\Event\Extension;
interface UrlBuildRouteEvent
{
public function execute(string $uri, array $parameters = [], bool $appendVersion) : void;
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace Picea\Event\Extension;
interface UrlBuildUrlEvent
{
public function execute(string $uri, array $parameters = [], bool $appendVersion) : void;
}
@@ -0,0 +1,8 @@
<?php
namespace Picea\Event\Extension;
interface UrlRegisterRouteEvent
{
public function execute(string $name, array $route) : void;
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace Picea;
trait EventTrait
{
protected $eventTraitMethod = "execute";
public array $eventTraitList = [];
protected array $eventTraitReturnList = [];
public function eventRegister(object $event) : void
{
$this->eventTraitList[] = $event;
}
public function eventFromType(string $type) : array
{
return array_filter($this->eventTraitList, fn($ev) => $ev instanceof $type);
}
public function eventExecute(string $type, ...$arguments) : void
{
foreach($this->eventFromType($type) as $event) {
$this->eventTraitReturnList[$event::class][] = call_user_func_array([ $event, $this->eventTraitMethod ], $arguments);
}
}
}
@@ -0,0 +1,7 @@
<?php
namespace Picea\Exception;
use Picea\Picea;
class ParseExtensionException extends \Exception {}
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace Picea\Exception;
use Picea\Picea;
class RegisterExtensionToken extends \Exception {}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Picea\Exception;
use Picea\Picea;
/**
* Whenever an error occured rendering a view, this is the exception that will show up.
*
*/
class RenderHtmlException extends \Exception
{
public function __construct(object $template, string $message, int $code, \Throwable $previous)
{
parent::__construct($message, $code, $previous);
$this->defineError($previous, $template);
}
protected function defineError(\Throwable $previous, object $template) : void
{
/*$loadedTemplates = array_flip($this->picea->loadedTemplateFile);*/
$this->file = $template::getParam('view');
$this->line = $template::getSourceLineFromException($previous->getLine());
}
protected function getTemplateFile(string $filePath) : ? array
{
$content = null;
if ( is_array($content) && isset($content['classname'], $content['namespace'], $content['view'], $content['extends']) ) {
return $content;
}
return null;
}
}
-13
View File
@@ -1,13 +0,0 @@
<?php
namespace Picea\Extension;
class AssetExtension implements Extension {
public string $token = "asset";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
return "<?php echo 'assets! $arguments' ?>";
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace Picea\Extension;
use Picea\Compiler\Context;
class BatchExtension implements Extension, FunctionExtension {
public string $token = "batch";
public function exportFunctions(): array
{
return [
"batch" => [ $this, 'batch' ]
];
}
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
return "<?php echo batch($arguments) ?>";
}
public function batch(iterable $array, int $size, mixed $default = null) : \Generator
{
$split = ceil( count($array) / $size );
for($i = 0; $i < $split; $i++) {
$slice = array_slice(is_array($array) ? $array : iterator_to_array($array, true), $i * $size, $size, true);
$pad = ( count($slice) !== $size ) && ( $default !== null );
yield $pad ? array_pad($slice, $size, $default) : $slice;
}
}
}
+1 -1
View File
@@ -3,5 +3,5 @@
namespace Picea\Extension;
interface Extension {
public function parse(\Picae\Compiler\Context &$context, string $sourceCode, string $token);
public function parse(\Picea\Compiler\Context &$context, string $sourceCode, string $token, array $options = []) : string;
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace Picea\Extension;
use Picea\Compiler\Context;
interface FunctionExtension
{
public function exportFunctions() : array;
}
+33 -21
View File
@@ -4,38 +4,50 @@ namespace Picea\Extension;
use Picea\Compiler\Context;
class JsonExtension implements Extension {
class JsonExtension implements Extension, FunctionExtension {
public array $token = [ "json", "json.pretty" ];
public array $token = [ "json" ];
public int $flags = JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_QUOT | \JSON_THROW_ON_ERROR;
public int $flags = JSON_HEX_TAG | \JSON_HEX_QUOT | \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_UNICODE;
public function __construct(? Context $context = null) {
if ($context) {
$this->register($context);
}
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token)
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
$flag = $this->flags;
switch ($token) {
case "json.pretty":
if ( in_array('pretty', $options) ) {
$flag |= \JSON_PRETTY_PRINT;
break;
case "json.html":
return "<?php echo htmlentities(json_encode($arguments, {$this->flags}), ENT_QUOTES, 'UTF-8') ?>";
}
return "<?php echo json_encode($arguments, $flag) ?>";
if ( in_array('html', $options) ) {
return "<?php echo htmlentities(json_encode($arguments, $flag), ENT_QUOTES, 'UTF-8') ?>";
}
public function register(Context $context) : void
$cls = static::class;
return "<?php echo json_encode(\\$cls::utf8($arguments), $flag) ?>";
}
public function exportFunctions(): array
{
$context->pushFunction("json", function($arguments, ? int $flags = null) {
return json_encode($arguments, \JSON_FORCE_OBJECT);
});
return [
'json' => function($arguments, ? int $flags = null) {
return json_encode($arguments, $flags ?? $this->flags);
},
];
}
public static function utf8($src) {
if (is_array($src)) {
foreach ($src as $key => $value) {
$src[$key] = static::utf8($value);
}
}
elseif (is_string($src)) {
return mb_convert_encoding($src, "UTF-8", "UTF-8");
}
return $src;
}
}
+57 -11
View File
@@ -3,40 +3,69 @@
namespace Picea\Extension;
use Picea\Compiler\Context;
use Picea\Event\Builder\ClassTemplateRenderSectionDone;
class LanguageExtension implements Extension {
class LanguageExtension implements Extension, FunctionExtension {
public array $tokens = [ "lang", "_", "language.set" ];
public array $tokens = [ "lang", "_", "language" ];
public string $currentLanguage = "";
protected LanguageHandler $languageHandler;
protected LanguageHandlerInterface $languageHandler;
public function __construct(Context $context, LanguageHandler $handler) {
$this->register($context);
public function __construct(LanguageHandlerInterface $handler) {
$this->languageHandler = $handler;
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
public function exportFunctions(): array
{
switch($token) {
case "language.set":
return "<?php \$picea->compiler->getExtensionFromToken('$token')->currentLanguage = $arguments; ?>";
return [
'_' => 'relativeLang',
'lang' => 'absoluteLang',
];
}
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
switch(explode('.', $token)[0]) {
case "language":
$cls = $this::class;
return <<<PHP
<?php
(function(\\$cls \$ext) use (\$___class__template, \$__event) {
#\$extends = \$___class__template->getParam('extends');
if ( \$___class__template->depth === 1 || \$___class__template->sectionStack ) {
\$ext->currentLanguage = $arguments;
# @TODO Make sure this event is only registered when we output() a template, if we are in a section, we must attach it a view/section/block output event instead !
\$__event->eventRegister(\\$cls::outputDoneEvent(\$ext));
}
})(\$picea->compiler->getExtensionFromToken('$token'));
?>
PHP;
case "lang":
if ( in_array('raw', $options) ) {
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->absoluteLang($arguments) ?>";
}
return "<?php echo htmlspecialchars(\$picea->compiler->getExtensionFromToken('$token')->absoluteLang($arguments), \ENT_QUOTES, ini_get('default_charset'), true) ?>";
case "_":
if ( in_array('raw', $options) ) {
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->relativeLang($arguments) ?>";
}
return "<?php echo htmlspecialchars(\$picea->compiler->getExtensionFromToken('$token')->relativeLang($arguments), \ENT_QUOTES, ini_get('default_charset'), true) ?>";
}
return "";
}
public function register(Context $context) : void
{
$context->pushFunction("_", [ $this, 'relativeLang' ]);
$context->pushFunction("lang", [ $this, 'absoluteLang' ]);
}
public function relativeLang(string $key, array $variables = []) #: array|string
@@ -48,4 +77,21 @@ class LanguageExtension implements Extension {
{
return $this->languageHandler->languageFromKey($key, $variables);
}
public static function outputDoneEvent(LanguageExtension $languageExtension) : ClassTemplateRenderSectionDone
{
return new class($languageExtension) implements ClassTemplateRenderSectionDone {
protected string $current;
public function __construct(protected LanguageExtension $languageExtension) {
$this->current = $this->languageExtension->currentLanguage;
}
public function execute(string $name) : mixed
{
return $this->current ? $this->languageExtension->currentLanguage = $this->current : null;
}
};
}
}
@@ -2,7 +2,7 @@
namespace Picea\Extension;
interface LanguageHandler
interface LanguageHandlerInterface
{
public function languageFromKey(string $key, array $variables = []); #: array|string;
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace Picea\Extension;
use Picea\Compiler\Context;
class NumberExtension implements Extension, FunctionExtension {
public string $locale;
public \NumberFormatter $formatter;
public function __construct(
public string|array $token = [ "money", "format", "format_zero" ],
public string $title = "",
) {
$this->locale = explode('.', \Locale::getDefault())[0];
$this->formatter = new \NumberFormatter($this->locale, \NumberFormatter::CURRENCY);
}
public function exportFunctions(): array
{
return [
"money" => [ $this, 'money' ],
"format" => [ $this, 'format' ],
"format_zero" => [ $this, 'formatZero' ],
];
}
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
switch($token) {
case "format":
return "<?php echo format($arguments) ?>";
case "format_zero":
return "<?php echo format_zero($arguments) ?>";
case "money":
return "<?php echo money($arguments) ?>";
}
return "";
}
public function format(int|float $num, int $decimals = 0, null|string $decimalSeparator = ".", null|string $thousandsSeparator = ",") : string
{
return number_format($num, $decimals, $decimalSeparator, $thousandsSeparator);
}
public function formatZero(int $num, int $length) : string
{
return sprintf("%0{$length}d", $num);
}
public function money(float $money, ? string $currency = null) /* : string|false */
{
$this->formatter->setTextAttribute(\NumberFormatter::CURRENCY_CODE, 'CAD');
$this->formatter->setPattern( str_replace('¤#','¤ #', $this->formatter->getPattern() ) );
return $this->formatter->formatCurrency($money, $currency ?? $this->formatter->getTextAttribute(\NumberFormatter::CURRENCY_CODE));
}
}
+2 -1
View File
@@ -6,7 +6,8 @@ class PhpExtension implements Extension {
public array $token = [ "php" ];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
return "<?php $arguments ?>";
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace Picea\Extension;
use Picea\Compiler\Context;
class PrintExtension implements Extension {
public array $token = [ "echo", "print", "printf", "no_html" ];
public int $flag = \ENT_QUOTES | \ENT_DISALLOWED | \ENT_SUBSTITUTE;
public string $encoding;
public bool $doubleEncode = true;
public function __construct() {
$this->encoding = ini_get("default_charset");
}
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
switch($token) {
case 'echo':
case 'print':
if (in_array('raw', $options)) {
return "<?php echo $arguments ?>";
}
return "<?php echo htmlspecialchars((string) $arguments, {$this->flag}, '{$this->encoding}', " . ($this->doubleEncode ? "true" : "false") . ") ?>";
case 'printf':
if (in_array('raw', $options)) {
return "<?php printf((string) $arguments) ?>";
}
return "<?php echo htmlspecialchars(sprintf((string) $arguments), {$this->flag}, '{$this->encoding}', " . ($this->doubleEncode ? "true" : "false") . ") ?>";
case 'no_html':
return "<?php echo nl2br(strip_tags((string) $arguments)) ?>";
}
}
}
+13 -13
View File
@@ -2,28 +2,28 @@
namespace Picea\Extension;
use Picea\Compiler\Context;
class TitleExtension implements Extension {
class TitleExtension implements Extension, FunctionExtension {
public string $token = "title";
public string $title = "";
public function __construct(Context $context) {
$this->register($context);
}
public function register(Context $context) : void
public function exportFunctions(): array
{
$context->pushFunction("title", [ $this, 'handleTitle' ]);
return [
"title" => [$this, 'handleTitle'],
];
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
return "<?php echo title($arguments) ?>";
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
return "<?php ".
"if ( null !== \$title = title($arguments) ) {".
"echo \$title;".
"} ?>";
}
public function handleTitle(? string $set = null) : ? string
public function handleTitle(? string $set = null, ...$arguments) : ? string
{
if ( $set === null ) {
return $this->title;
@@ -31,7 +31,7 @@ class TitleExtension implements Extension {
else {
# Fixed a bug where template inheritance was rewriting title
if ( empty($this->title) ) {
$this->title = $set;
$this->title = $arguments ? sprintf($set, ...$arguments) : $set;
}
}
+158 -50
View File
@@ -2,25 +2,45 @@
namespace Picea\Extension;
use Picea\Compiler\Context;
use Picea\EventTrait;
use Picea\Event\Extension\{ UrlBuildAssetEvent, UrlBuildUrlEvent, UrlBuildRouteEvent, UrlRegisterRouteEvent };
class UrlExtension implements Extension {
class UrlExtension implements Extension, FunctionExtension {
use EventTrait;
public const URLIZE_PATTERN_URL = <<<PATTERN
~(?<!href=['"])https?://[\w/._\-&?]*(?!</a>)(?=[^\w/._\-&])~s
PATTERN;
public const URLIZE_PATTERN_EMAIL = <<<PATTERN
/(?:[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])/
PATTERN;
protected string $urlBase;
protected array $appUrl;
protected string $assetToken;
protected array $routes;
public array $tokens = [ "url" , "route", "asset", "url.parameters" ];
protected array $routesTarget;
public function __construct(Context $context, string $urlBase = "", string $assetToken = "") {
protected bool $forceSSL = false;
public array $tokens = [ "url" , "route", "asset", "slug" ];
#[\Deprecated]
protected bool $enforceExistingArguments = true;
public function __construct(string $urlBase = "", string $assetToken = "", array $appUrl = [], bool $forceSSL = false) {
$this->urlBase = trim($urlBase, "/");
$this->assetToken = $assetToken;
$this->register($context);
$this->appUrl = $appUrl;
$this->forceSSL = $forceSSL;
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : ?string
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
{
switch($token) {
case "asset":
@@ -30,20 +50,37 @@ class UrlExtension implements Extension {
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildRouteUrl($arguments) ?>";
case "url":
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildUrl($arguments) ?>";
case "url.parameters":
if ( in_array('parameters', $options) ) {
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->setUrlParameters($arguments) ?>";
}
return null;
elseif ( in_array('current', $options) ) {
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->currentUrl($arguments) ?>";
}
public function register(Context $context) : void
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildUrl($arguments) ?>";
case "slug":
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->slug($arguments) ?>";
}
throw new \Exception("Unknown token given $token in UrlExtension.");
}
public function exportFunctions(): array
{
$context->pushFunction("url", [ $this, 'buildUrl' ]);
$context->pushFunction("asset", [ $this, 'buildAssetUrl' ]);
$context->pushFunction("route", [ $this, 'buildRouteUrl' ]);
return [
"url" => [ $this, 'buildUrl' ],
"current_url" => [ $this, 'currentUrl' ],
"asset" => [ $this, 'buildAssetUrl' ],
"route" => [ $this, 'buildRouteUrl' ],
"slug" => [ $this, 'slug' ],
"urlize" => [ $this, 'urlize' ]
];
}
public function getRouteList(bool $full = false) : array
{
return $this->routes;
}
public function setUrlParameters(string $url = "", array $parameters = []) : string
@@ -51,20 +88,40 @@ class UrlExtension implements Extension {
return $url . ( $parameters ? "?" . http_build_query($parameters) : "" );
}
public function buildUrl(string $uri = "", array $parameters = []) : string
public function urlize(string $string) : string
{
return $this->setUrlParameters($this->url() . "/" . ltrim($uri, "/"), $parameters);
# Normal URL patterns
$string = preg_replace(static::URLIZE_PATTERN_URL, '<a href="$0" target="_blank" title="$0">$0</a>', $string);
# Email patterns
$string = preg_replace(static::URLIZE_PATTERN_EMAIL, '<a href="mailto:$0" title="$0">$0</a>', $string);
return $string;
}
public function buildAssetUrl(string $uri, array $parameters = []) : string
public function currentUrl(array $parameters = []) : string
{
return $this->buildUrl($uri, array_replace([ 'v' => $this->assetToken ], $parameters));
return $this->buildUrl($this->uri(), $parameters);
}
public function buildRouteUrl(string $name, array $parameters = []) : string
public function buildUrl(string $uri = "", array $parameters = [], bool $appendVersion = false) : string
{
$this->eventExecute(UrlBuildUrlEvent::class, $uri, $parameters, $appendVersion);
return $this->setUrlParameters($this->url() . "/" . ltrim($uri, "/"), $appendVersion ? array_replace([ 'v' => $this->assetToken ], $parameters) : $parameters);
}
public function buildAssetUrl(string $uri, array $parameters = [], bool $appendVersion = true) : string
{
$this->eventExecute(UrlBuildAssetEvent::class, $uri, $parameters, $appendVersion);
return $this->buildUrl($uri, $parameters, $appendVersion);
}
public function buildRouteUrl(string $name, array $parameters = [], bool $appendVersion = false) : string
{
if ( false !== ( $route = $this->routes[$name] ?? false ) ) {
return $this->buildUrl($this->prepareRoute($route, $parameters), $parameters);
return $this->buildUrl($this->prepareRoute($route, $parameters), $parameters, $appendVersion);
}
$routeList = json_encode($this->routes, \JSON_PRETTY_PRINT);
@@ -80,9 +137,26 @@ class UrlExtension implements Extension {
return $this->scheme() . $this->domain() . $this->base();
}
public function registerRoute(string $name, string $route) : void
# src: https://stackoverflow.com/a/14550919
public static function slug(string $text, string $separator = '-') : string
{
$this->routes[$name] = $route;
$clean = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text);
$clean = preg_replace("/[^a-zA-Z0-9\/_| -]/", '', $clean);
return preg_replace("/[\/_| -]+/", $separator, strtolower(trim($clean, '-')));
}
public function registerRoute(string $name, string $route, string $class, string $method, array $routeMethods) : void
{
$this->routes[$name] = [
'route' => $route,
'routeMethods' => $routeMethods,
'class' => $class,
'classMethod' => $method,
];
$this->eventExecute(UrlRegisterRouteEvent::class, $name, $this->routes[$name]);
}
/**
@@ -96,11 +170,7 @@ class UrlExtension implements Extension {
$uri = explode('?', $uri, 2)[0];
}
if ( ($base = $this->config['base'] ?? false) && ( stripos($uri, $base) === 0 ) ) {
$uri = substr($uri, strlen($base));
}
return '/' . ltrim($uri, '/');
return '/' . ltrim(substr($uri, strlen($this->base())), '/');
}
/**
@@ -113,7 +183,7 @@ class UrlExtension implements Extension {
protected function scheme() : string
{
return ( $this->isHttps() ? "https" : "http" ) . "://";
return ( $this->forceSSL || $this->isHttps() ? "https" : "http" ) . "://";
}
protected function base() : string
@@ -132,7 +202,25 @@ class UrlExtension implements Extension {
protected function domain() : string
{
return strtolower($_SERVER['HTTP_HOST']);
if ( ! empty($_SERVER['HTTP_X_FORWARDED_PROTO']) || ! empty($_SERVER['HTTP_X_FORWARDED_SSL']) ) {
$port = "";
}
else {
$port = $this->isDefaultPort() ? "" : ":" . $_SERVER['SERVER_PORT'];
}
return strtolower($this->appUrl($port) ?: $_SERVER['HTTP_HOST']);
}
protected function appUrl(string $port) : string
{
$domain = strtolower($_SERVER['HTTP_HOST']);
if (in_array($domain, $this->appUrl)) {
return $domain . $port;
}
return $this->appUrl[0] . $port;
}
protected function isDefaultPort() : bool
@@ -142,52 +230,72 @@ class UrlExtension implements Extension {
protected function isHttps() : bool
{
$header = in_array('https', array_map("strtolower", [
$https = in_array('https', array_map("strtolower", [
$_SERVER['REQUEST_SCHEME'] ?? "",
$_SERVER['X-Url-Scheme'] ?? "",
$_SERVER['Front-End-Https'] ?? "",
$_SERVER['X-Forwarded-Ssl'] ?? "",
$_SERVER['X-Forwarded-Proto'] ?? "",
$_SERVER['X-Forwarded-Protocol'] ?? "",
]));
$_SERVER['HTTP_X_FORWARDED_PROTO'] ?? "",
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] ?? "",
])) || isset($_SERVER['HTTP_X_ARR_SSL']);
return $header
|| ( "443" === ( $_SERVER['SERVER_PORT'] ?? "" ) )
|| ( "https" === ( $_SERVER['REQUEST_SCHEME'] ?? "http") )
|| ( "off" !== ( strtolower($_SERVER['HTTPS'] ?? "off")) );
return $https
|| ( "443" === ( $_SERVER['SERVER_PORT'] ?? "" ) ) || ( "443" === ( $_SERVER['HTTP_X_FORWARDED_PORT'] ?? "" ) )
|| ( "off" !== ( strtolower($_SERVER['HTTPS'] ?? $_SERVER['HTTP_X_FORWARDED_SSL'] ?? $_SERVER['X-Forwarded-Ssl'] ?? "off")) );
}
protected function prepareRoute(string $route, array &$arguments)
protected function prepareRoute(array $routeParam, array &$arguments)
{
$route = $routeParam['route'];
if ( preg_match_all('~{(.*?)}~si', $route, $matches, PREG_SET_ORDER) ) {
$search = [];
foreach($matches as $item) {
if (strpos($item[1], "=") !== false) {
$default = null;
$variable = $item[1];
# Handles default
if (strpos($variable, "=") !== false) {
list($variable, $default) = explode('=', $item[1]);
}
else {
$variable = $item[1];
# Handles type
if (strpos($variable, ":") !== false) {
list($variable, ) = explode(':', $item[1]);
}
if ( array_key_exists($variable, $arguments) ) {
$value = $arguments[ $item[1] ];
}
else if ( isset($default) ) {
$value = ""; # $default;
$value = $arguments[ $variable ];
unset($arguments[ $variable ]);
}
else {
$variable = $item[1];
if ($default ?? false) {
$value = $default;
}
elseif ( strpos($route, "[{$matches[0][0]}]") !== false && $this->enforceExistingArguments) {
throw new \RuntimeException(sprintf("Error while preparing route %s : could not match variable '%s' into given arguments ( %s ) from %s::%s", $route, $variable, json_encode($arguments), $routeParam['class'], $routeParam['classMethod']));
}
}
$search[ $item[0] ] = $value;
unset($arguments[ $item[1] ]);
$search[$item[0]] = $value ?? "";
}
$route = str_replace(array_keys($search), array_values($search), $route);
}
return $route;
/*
* @TODO - must also take into account that recursivity is possible here [/test[/another[/still]]],
* so the regex must be adjusted (or simply using another method could also be a possiblity)
* while(strpos($route, '[') !== false && strpos($route, ']') !== false ) {
if ( preg_match_all('~[(.*?)]~si', $route, $matches, PREG_SET_ORDER) ) {
}
}*/
$route = trim(str_replace([ '[', ']' ], '', $route), '/');
return $route;
}
}
+63 -5
View File
@@ -2,6 +2,8 @@
namespace Picea;
use RecursiveIteratorIterator, RecursiveDirectoryIterator;
class FileFetcher
{
protected array $folderList = [];
@@ -12,13 +14,13 @@ class FileFetcher
public function __construct(?array $folderList = null) {
if ( $folderList !== null ) {
$this->folderList = $folderList;
$this->addFolders($folderList);
}
}
public function addFolder(string $folder, int $order = 100) : void
{
$folder = rtrim($folder, DIRECTORY_SEPARATOR);
$folder = $this->normalizeFolder($folder);
$this->folderList[$folder] = [
'path' => $folder,
@@ -28,7 +30,14 @@ class FileFetcher
public function addFolders(array $folderList) : void
{
$this->folderList = array_replace($this->folderList, $folderList);
foreach($folderList as $folder) {
$this->addFolder($folder['path'], $folder['order']);
}
}
protected function normalizeFolder(string $path) : string
{
return rtrim($path, '/\\');
}
public function folderList(?array $set = null) : ?array
@@ -40,21 +49,70 @@ class FileFetcher
{
usort($this->folderList, fn($a, $b) => $a['order'] <=> $b['order']);
foreach($this->folderList as $folder) {
foreach($this->folderList() as $folder) {
foreach($this->supportedExtensionList as $extension) {
$file = $folder['path'] . DIRECTORY_SEPARATOR . "$fileName.$extension";
$file = str_replace([ '\\', '/' ], DIRECTORY_SEPARATOR, $file);
if ( file_exists($file) ) {
if ( is_dir($file) ) {
throw new \RuntimeException("Given view file `$fileName` is a folder, you must provide a valid filename.");
}
return $file;
}
}
}
# Fallback on full-path
foreach($this->folderList() as $folder) {
$file = $folder['path'] . DIRECTORY_SEPARATOR . $fileName;
$file = str_replace([ '\\', '/' ], DIRECTORY_SEPARATOR, $file);
if ( file_exists($file) ) {
if ( is_dir($file) ) {
throw new \RuntimeException("Given view file `$fileName` is a folder, you must provide a valid filename.");
}
return $file;
}
}
throw new \RuntimeException("Given view file `$fileName` can not be found within given folder list..");
}
public function getFileList() : array
{
usort($this->folderList, fn($a, $b) => $a['order'] <=> $b['order']);
$list = [];
foreach($this->folderList() as $folder) {
$path = $folder['path'] . "/";
$list[$path] = [];
if ( \file_exists($path) ) {
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD);
foreach ($iterator as $file) {
if ($file->isFile() && in_array($file->getExtension(), $this->supportedExtensionList)) {
$list[$path][] = $file;
}
}
}
}
return $list;
}
public function getFilePath(string $fileName) : string
{
return $this->findFile($fileName);
}
public function getFileContent(string $fileName) : string
{
return file_get_contents($this->findFile($fileName));
return file_get_contents($this->getFilePath($fileName));
}
}
+12 -20
View File
@@ -4,25 +4,13 @@ namespace Picea\Language;
use Picea\Compiler;
use Picea\Compiler\Context;
class DefaultRegistrations implements LanguageRegistration
{
protected array $extensions;
protected array $syntaxes;
protected array $controlStructures;
protected ? Context $context;
public function __construct(? Context $context = null, array $extensions = [], array $syntaxes = [], array $controlStructure = [])
{
$this->context = $context;
$this->extensions = $extensions;
$this->syntaxes = $syntaxes;
$this->controlStructures = $controlStructure;
}
public function __construct(
protected array $extensions,
protected array $syntaxes,
protected array $controlStructures,
) { }
public function registerAll(Compiler $compiler) : void
{
@@ -33,7 +21,6 @@ class DefaultRegistrations implements LanguageRegistration
public function registerSyntax(Compiler $compiler) : void
{
$compiler->registerSyntax(new \Picea\Syntax\PhpTagToken());
$compiler->registerSyntax(new \Picea\Syntax\CommentToken());
$compiler->registerSyntax(new \Picea\Syntax\EchoRawToken());
$compiler->registerSyntax(new \Picea\Syntax\EchoSafeToken());
@@ -50,13 +37,17 @@ class DefaultRegistrations implements LanguageRegistration
$compiler->registerControlStructure(new \Picea\ControlStructure\IfToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\ForeachToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\ForToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\WhileToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\OrToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\SwitchToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\DefaultToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\BreakToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\ContinueToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\ExtendsToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\SectionToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\FunctionToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\BlockToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\IncludeToken());
$compiler->registerControlStructure(new \Picea\ControlStructure\ViewToken());
foreach($this->controlStructures ?? [] as $controlStructure) {
@@ -67,8 +58,9 @@ class DefaultRegistrations implements LanguageRegistration
public function registerExtension(Compiler $compiler) : void
{
$compiler->registerExtension(new \Picea\Extension\PhpExtension());
$compiler->registerExtension(new \Picea\Extension\JsonExtension($this->context));
$compiler->registerExtension(new \Picea\Extension\AssetExtension());
$compiler->registerExtension(new \Picea\Extension\PrintExtension());
$compiler->registerExtension(new \Picea\Extension\JsonExtension());
$compiler->registerExtension(new \Picea\Extension\BatchExtension());
foreach($this->extensions ?? [] as $extension) {
$compiler->registerExtension($extension);
+29 -11
View File
@@ -7,9 +7,11 @@ use Picea\Extension\Extension,
use Picea\Compiler\Context;
use Picea\Extension\FunctionExtension;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
class Request implements Extension {
class Request implements Extension, FunctionExtension {
use ExtensionTrait;
public array $tokens;
@@ -20,18 +22,19 @@ class Request implements Extension {
public function __construct(ServerRequestInterface $request, Context $context) {
$this->request = $request;
$this->register($context);
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string { }
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string { }
public function register(Context $context) : void
public function exportFunctions(): array
{
$context->pushFunction("cookie", [ $this, 'cookie' ]);
$context->pushFunction("get", [ $this, 'get' ]);
$context->pushFunction("post", [ $this, 'post' ]);
$context->pushFunction("request", [ $this, 'request' ]);
$context->pushFunction("server", [ $this, 'server' ]);
return [
"cookie" => [ $this, 'cookie' ],
"get" => [ $this, 'get' ],
"post" => [ $this, 'post' ],
"request" => [ $this, 'request' ],
"server" => [ $this, 'server' ],
];
}
public function cookie(? string $variableName = null, $default = null)
@@ -46,12 +49,12 @@ class Request implements Extension {
public function post(? string $variableName = null, $default = null)
{
return $variableName === null ? $this->request->getParsedBody() : static::arrayGet($this->request->getParsedBody(), $variableName) ?? $default;
return $variableName === null ? $this->_post() : static::arrayGet($this->_post(), $variableName) ?? $default;
}
public function request(? string $variableName = null, $default = null)
{
return $variableName === null ? array_merge(get(null), post(null)) : $this->post($variableName) ?? $this->get($variableName) ?? $default;
return $variableName === null ? array_merge($this->get(), $this->post()) : $this->post($variableName) ?? $this->get($variableName) ?? $default;
}
public function server(? string $variableName = null, $default = null)
@@ -75,4 +78,19 @@ class Request implements Extension {
return null;
}
}
protected function _post() : array
{
$post = $this->request->getParsedBody();
if ( ! $post ) {
$content = utf8_encode((string) $this->request->getBody());
if ( $content && ( $json = json_decode($content, true) ) ) {
$post = $json;
}
}
return $post ?: [];
}
}
+63 -34
View File
@@ -3,7 +3,9 @@
namespace Picea;
use Closure;
use Picea\Language\LanguageRegistration;
use Picea\Language\LanguageRegistration,
Picea\Exception\RenderingError;
use Psr\Http\Message\ResponseInterface;
class Picea implements LanguageRegistration
@@ -18,16 +20,19 @@ class Picea implements LanguageRegistration
public string $builderTemplatePath;
public Closure $responseHtml;
public Caching\Cache $cache;
public FileFetcher $fileFetcher;
public bool $debug;
public array $loadedTemplateFile = [];
public array $globalVariables = [];
public array $compiled = [];
public function __construct(
? Closure $responseHtml = null,
? Compiler\Context $context = null,
? Caching\Cache $cache = null,
? Compiler $compiler = null,
@@ -36,39 +41,71 @@ class Picea implements LanguageRegistration
? string $builderTemplatePath = null,
bool $debug = false
){
$this->response = $responseHtml;
$this->cache = $cache ?? new Caching\Memory("");
$this->context = $context ?? new Compiler\BaseContext();
$this->languageRegistration = $languageRegistration ?? new Language\DefaultRegistrations();
$this->languageRegistration = $languageRegistration ?? new Language\DefaultRegistrations([], [], []);
$this->builderTemplatePath = $builderTemplatePath ?? dirname(__FILE__) . static::DEFAULT_BUILDER_TEMPLATE;
$this->compiler = $compiler ?? $this->instanciateCompiler();
$this->fileFetcher = $fileFetcher ?? $this->instanciateFileFetcher();
$this->debug = $debug;
#$this->context->exportFunctions();
$this->compiler->registerExtensionsFunctions($this->context);
$this->renderContext($this->context);
}
public function renderHtml(string $viewPath, array $variables = [], ?object $proxy = null) : string
public function gatherCompiledObject(string $viewPath, array $variables = [], ? object $proxy = null) : ? object
{
if ( null === $object = $this->fetchFromCache($viewPath, $variables, $proxy) ) {
if ( null === $object = $this->fetchFromCache($viewPath, $this->globalVariables + $variables, $proxy) ) {
throw new \RuntimeException("An error occured while trying to save a compiled template.");
}
return $object();
return $object;
}
public function renderHtml(string $viewPath, array $variables = [], ?object $proxy = null) : ? string
{
$object = $this->gatherCompiledObject($viewPath, $variables, $proxy);
try {
return call_user_func($object);
}
catch(\Throwable $ex) {
#if (! $ex instanceof Exception\RenderHtmlException ) {
# throw new Exception\RenderHtmlException($object, $this, "An error occurred trying to render HTML view `$viewPath` : " . $ex->getMessage(), 911, $ex);
#}
#else {
throw $ex;
#}
}
return null;
}
/**
* Method used by Block and View tokens
* @param object $proxy
* @param string $viewPath
* @param array $variables
* @return type
*/
public function inlineHtml(? object $proxy, string $viewPath, ... $variables) {
return $this->renderHtml($viewPath, [ 'inlineVariables' => $variables ], $proxy);
public function inlineHtml(? object $proxy, string $viewPath, array $variables) {
return $this->renderHtml($viewPath, $this->globalVariables + $variables, $proxy);
}
/**
* Allows block to be called from templates
*/
public function inlineBlock(? object $proxy, string $viewPath, ... $variables) {
return $this->renderHtml($viewPath, [ 'inlineVariables' => $variables, 'globalVariables' => $this->globalVariables ], $proxy);
}
/**
* Push view content inline
*/
public function inlineContent(string $viewPath) {
return $this->fileFetcher->getFileContent($viewPath);
}
/**
* Renders a compile context
*/
public function renderContext(Compiler\Context $context) : object
{
if ( null === $object = $this->contextFromCache($context) ) {
@@ -78,22 +115,6 @@ class Picea implements LanguageRegistration
return $object;
}
#public function outputHtml(string $viewPath, array $variables) : ResponseInterface
#{
# if ( $this->response ?? false ) {
# if ( false === $content = $this->cache->handle($viewPath) ) {
#
# }
# $source = $this->compileSource($source ?? "test");
# $response = $this->response;
# return $response("abc");
# }
# throw new \InvalidArgumentException("No \Psr\Http\Message\ResponseInterface closure provided. Please provide one using the constructor or assigning it to the class variable `responseHtml`.");
#}
public function compileSource(string $source) : array
{
$this->compiler->loadSourceCode($source);
@@ -141,13 +162,15 @@ class Picea implements LanguageRegistration
public function fetchFromCache(string $viewPath, array $variables = [], ?object $proxy = null) : ?object
{
if ( ( true === $this->debug ) || (! $this->cache->compiled($viewPath))) {
if ( ! isset($this->compiled[$viewPath]) && ( true === $this->debug ) || (! $this->cache->compiled($viewPath))) {
$context = $this->compileView($viewPath);
$this->cache->save($context);
if ( $context->extendFrom ) {
$this->fetchFromCache($context->extendFrom, $variables, $proxy);
}
$this->compiled[$viewPath] = true;
}
return $this->cache->load($viewPath, $this, $variables, $proxy);
@@ -157,6 +180,7 @@ class Picea implements LanguageRegistration
{
if ( $this->debug || ! $this->cache->compiled( $context->cacheFilename() ) ) {
$context = $this->compileContext($context);
$this->cache->save($context);
}
@@ -200,6 +224,11 @@ class Picea implements LanguageRegistration
$this->languageRegistration->registerExtension($compiler);
}
public function declareLoadedTemplate(string $className, string $filePath) : void
{
$this->loadedTemplateFile[$className] = $filePath;
}
protected function compileView(string $viewPath) : Compiler\Context
{
$tmpFolder = sys_get_temp_dir();
@@ -207,10 +236,10 @@ class Picea implements LanguageRegistration
$compiled = $this->compileSource($this->fileFetcher->getFileContent($viewPath));
$context = $compiled['context'];
$context->viewPath = $viewPath;
$context->filePath = $this->fileFetcher->getFilePath($viewPath);
$context = $builder->build($compiled['context'], $compiled['source']) ;
$context->classPath = $tmpFolder . DIRECTORY_SEPARATOR . $context->className . ".php";
if ( $context->extendFrom ) {
$this->compileView($context->extendFrom);
}
+1 -1
View File
@@ -10,7 +10,7 @@ class CommentToken implements Syntax {
protected string $tokenClose = "\#\}";
public function parse(/*\Picae\Compiler\Context*/ &$context, string &$sourceCode)
public function parse(\Picea\Compiler\Context &$context, string &$sourceCode)
{
$sourceCode = preg_replace("#({$this->tokenOpen})(.*?)({$this->tokenClose})#s", "", $sourceCode);
}
+1 -1
View File
@@ -8,7 +8,7 @@ class EchoRawToken implements Syntax {
public string $tokenClose = "\}\}";
public function parse(/*\Picae\Compiler\Context*/ &$content, string &$sourceCode)
public function parse(\Picea\Compiler\Context &$content, string &$sourceCode)
{
$sourceCode = preg_replace_callback("#({$this->tokenOpen})(.*?)({$this->tokenClose})#s", function ($matches) {
$line = trim($matches[2], " \t\n\r\0\x0B;");
+2 -2
View File
@@ -18,12 +18,12 @@ class EchoSafeToken implements Syntax {
$this->encoding = ini_get("default_charset");
}
public function parse(/*\Picae\Compiler\Context*/ &$context, string &$sourceCode)
public function parse(\Picea\Compiler\Context &$context, string &$sourceCode)
{
$sourceCode = preg_replace_callback("#({$this->tokenOpen})(.*?)({$this->tokenClose})#s", function ($matches) {
$line = trim($matches[2], " \t\n\r\0\x0B;");
return "<?php echo htmlspecialchars({$line}, {$this->flag}, '{$this->encoding}', " . ($this->doubleEncode ? "true" : "false") . ") ?>";
return "<?php echo htmlspecialchars((string) ({$line}), {$this->flag}, '{$this->encoding}', " . ($this->doubleEncode ? "true" : "false") . ") ?>";
}, $sourceCode);
}
-18
View File
@@ -1,18 +0,0 @@
<?php
namespace Picea\Syntax;
class PhpTagToken implements Syntax {
public string $tokenOpen = "\{\?";
public string $tokenClose = "\?\}";
public function parse(/*\Picae\Compiler\Context*/ &$context, string &$sourceCode)
{
$sourceCode = preg_replace_callback("#({$this->tokenOpen})(.*?)({$this->tokenClose})#s", function ($matches) {
return "<?php {$matches[2]} ?>";
}, $sourceCode);
}
}
+1 -1
View File
@@ -3,5 +3,5 @@
namespace Picea\Syntax;
interface Syntax {
public function parse(\Picae\Compiler\Context &$context, string &$sourceCode);
public function parse(\Picea\Compiler\Context &$context, string &$sourceCode);
}