Skip to content

Commit

Permalink
Version for Laravel 9 - 10, Latte 3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
miloslavkostir committed Feb 28, 2024
1 parent f2ff77b commit e290281
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.gitattributes export-ignore
.gitignore export-ignore
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
.vscode/
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,82 @@ $ composer require miko/laravel-latte
Then the templating engine is used according to the file extension:
- `*.blade.php` - [Blade (Laravel default)](https://laravel.com/docs/blade)
- `*.latte` - [Latte](https://latte.nette.org)

## Tags

See https://latte.nette.org/tags

And additional:

### `link` and `n:href`

Similar to [tags in Nette](https://doc.nette.org/en/application/creating-links#toc-in-the-presenter-template)
except that the separator berween controller and method is not `:` but `@` and the default method is not `default` but `index` according to the Laravel conventions.
Basically this is a simplified call to Laravel's [action()](https://laravel.com/docs/urls#urls-for-controller-actions) helper when
there is no need to write the entire FQCN and the word `Controller`.
In addition, it is possible to use the keyword `this` for the current action - then there is no need to write unchanged parameters.

```html
{link User@detail} {* shortcut for action([App\Http\Controllers\UserController::class, 'detail']) *}
{link User@} {* shortcut for action([App\Http\Controllers\UserController::class, 'index']) *}
{link detail} {* shortcut for action([$current_controller, 'detail']) *}
{link User@detail 123, 456} {* shortcut for action([App\Http\Controllers\UserController::class, 'detail'], [123, 456]) *}
{link User@detail foo => bar} {* shortcut for action([App\Http\Controllers\UserController::class, 'detail'], ['foo' => 'bar']) *}
{link "Admin\Article@detail"} {* shortcut for action([App\Http\Controllers\Admin\ArticleController::class, 'detail']) *}
{link this} {* generates a link for the current action and current arguments (current URL) *}

{* n tag *}
<a n:href="User@detail 123, foo">

{* Named arguments can be used... *}
<a n:href="Product@show $product->id, lang: cs">product detail</a>
{link Product@show $product->id, lang: cs}

{* ...and (expand) as well because of Latte v2 BC *}
{var $args = [$product->id, lang => cs]}
<a n:href="Product@show (expand) $args">product detail</a>
{link Product@show (expand) $args}
```
> `(expand)` - the equivalent of `...$arr` operator https://latte.nette.org/en/syntax#toc-a-window-into-history
#### Usage of `this`
route:
```php
Route::get('/users/permissions/{user}/{permission}', [\App\Http\Controllers\UserController::class, 'permissions']);
```
template:
```html
<a n:href="this sort => date">Sort by date</a>
```
At address `mysite.com/users/permissions/1/2` generates link `mysite.com/users/permissions/1/2?sort=date`

### `asset` and `n:src`

Adds the `m` parameter to the URL with the timestamp of the last file change. Every time the file is changed, it will be reloaded
and there is no need to clear the browser cache:
```html
<script n:src="/js/some-script.js"></script>
<link rel="stylesheet" href="{asset '/css/some-style.css'}">
<img n:src="/imgs/some-image.png">
```

### `csrf`

Generates hidden input `_token` in form with CSRF token https://laravel.com/docs/csrf#preventing-csrf-requests
```html
<form method="POST" action="/profile">
{csrf}

<!-- Equivalent to... -->
<input type="hidden" name="_token" value="{csrf_token()}" />
</form>
```

### `method`

Generates a hidden input `_method` in the form for [Form Method Spoofing](https://laravel.com/docs/routing#form-method-spoofing)
```html
<form action="/example" method="POST">
{method PUT}
</form>
```
11 changes: 8 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@
}
],
"require": {
"php": ">=7.4.0",
"illuminate/view": "^8.0 || ^9.0",
"latte/latte": "^2.9"
"php": ">=8.0",
"illuminate/view": "^9.0 || ^10.0",
"latte/latte": "^3.0"
},
"autoload": {
"psr-4": {
"Miko\\LaravelLatte\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
}
22 changes: 22 additions & 0 deletions src/Extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Miko\LaravelLatte;

use Latte\Extension as LatteExtension;

class Extension extends LatteExtension
{
public function getTags(): array
{
return [
'n:href' => [Nodes\LinkNode::class, 'create'],
'link' => [Nodes\LinkNode::class, 'create'],
'n:src' => [Nodes\AssetNode::class, 'create'],
'asset' => [Nodes\AssetNode::class, 'create'],
'csrf' => [Nodes\CsrfNode::class, 'create'],
'method' => [Nodes\MethodNode::class, 'create'],
];
}
}
8 changes: 2 additions & 6 deletions src/LatteEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@

use Latte\Engine;


class LatteEngine implements \Illuminate\Contracts\View\Engine
{

private $latte;

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

/**
/**
* Get the evaluated contents of the view.
*
* @param string $path
Expand All @@ -28,6 +26,4 @@ public function get($path, array $data = [])
{
return $this->latte->renderToString($path, $data);
}


}
77 changes: 77 additions & 0 deletions src/Nodes/AssetNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Miko\LaravelLatte\Nodes;

use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\Php\ModifierNode;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;

class AssetNode extends StatementNode
{
public ExpressionNode $destination;
public ArrayNode $args;
public ModifierNode $modifier;
public string $mode;


public static function create(Tag $tag): ?static
{
$tag->outputMode = $tag::OutputKeepIndentation;
$tag->expectArguments();
$node = new static();
$node->destination = $tag->parser->parseUnquotedStringOrExpression();
$tag->parser->stream->tryConsume(',');
$node->args = $tag->parser->parseArguments();
$node->modifier = $tag->parser->parseModifier();
$node->modifier->escape = true;
$node->modifier->check = false;
$node->mode = $tag->name;

if ($tag->isNAttribute()) {
// move at the beginning
array_unshift($tag->htmlElement->attributes->children, $node);
return null;
}

return $node;
}

public function print(PrintContext $context): string
{
if ($this->mode === 'src') {
$context->beginEscape()->enterHtmlAttribute(null, '"');
$res = $context->format(
<<<'XX'
echo ' src="'; echo %modify(\Miko\LaravelLatte\Runtime\Asset::generate(%node)); echo '"';
XX,
$this->modifier,
$this->destination,
$this->args,
$this->position,
);
$context->restoreEscape();
return $res;
}

return $context->format(
'echo %modify(\Miko\LaravelLatte\Runtime\Asset::generate(%node));',
$this->modifier,
$this->destination,
$this->args,
$this->position,
);
}


public function &getIterator(): \Generator
{
yield $this->destination;
yield $this->args;
yield $this->modifier;
}
}
30 changes: 30 additions & 0 deletions src/Nodes/CsrfNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Miko\LaravelLatte\Nodes;

use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;

class CsrfNode extends StatementNode
{
public static function create(Tag $tag): ?static
{
$node = $tag->node = new self();
return $node;
}

public function print(PrintContext $context): string
{
return $context->format('echo csrf_field();');
}

public function &getIterator(): \Generator
{
if (false) {
yield;
}
}
}
77 changes: 77 additions & 0 deletions src/Nodes/LinkNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Miko\LaravelLatte\Nodes;

use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\Php\ModifierNode;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;

class LinkNode extends StatementNode
{
public ExpressionNode $destination;
public ArrayNode $args;
public ModifierNode $modifier;
public string $mode;


public static function create(Tag $tag): ?static
{
$tag->outputMode = $tag::OutputKeepIndentation;
$tag->expectArguments();
$node = new static();
$node->destination = $tag->parser->parseUnquotedStringOrExpression();
$tag->parser->stream->tryConsume(',');
$node->args = $tag->parser->parseArguments();
$node->modifier = $tag->parser->parseModifier();
$node->modifier->escape = true;
$node->modifier->check = false;
$node->mode = $tag->name;

if ($tag->isNAttribute()) {
// move at the beginning
array_unshift($tag->htmlElement->attributes->children, $node);
return null;
}

return $node;
}

public function print(PrintContext $context): string
{
if ($this->mode === 'href') {
$context->beginEscape()->enterHtmlAttribute(null, '"');
$res = $context->format(
<<<'XX'
echo ' href="'; echo %modify(\Miko\LaravelLatte\Runtime\Link::generate(%node, %node?)) %line; echo '"';
XX,
$this->modifier,
$this->destination,
$this->args,
$this->position,
);
$context->restoreEscape();
return $res;
}

return $context->format(
'echo %modify(\Miko\LaravelLatte\Runtime\Link::generate(%node, %node?)) %line;',
$this->modifier,
$this->destination,
$this->args,
$this->position,
);
}


public function &getIterator(): \Generator
{
yield $this->destination;
yield $this->args;
yield $this->modifier;
}
}
47 changes: 47 additions & 0 deletions src/Nodes/MethodNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Miko\LaravelLatte\Nodes;

use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
use Latte\Compiler\Tag;

class MethodNode extends StatementNode
{
private ExpressionNode $method;

public static function create(Tag $tag): ?static
{
$tag->outputMode = $tag::OutputKeepIndentation;
$tag->expectArguments();
$node = $tag->node = new self();
$node->method = $tag->parser->parseUnquotedStringOrExpression();
return $node;
}

public function print(PrintContext $context): string
{
return $context->format(
<<<'XX'
$ʟ__method = %node %line;
Miko\LaravelLatte\Runtime\Method::validateArgument($ʟ__method);
echo method_field($ʟ__method);
XX,
$this->method,
$this->position,
);
}

public function &getIterator(): \Generator
{
if (false) {
yield;
}
}


}
Loading

0 comments on commit e290281

Please sign in to comment.