afterflow / recipe
Laravel 生成器库
Requires
- illuminate/events: 5.*|6.*|7.*
- illuminate/filesystem: 5.*|6.*|7.*
- illuminate/view: 5.*|6.*|7.*
Requires (Dev)
- phpunit/phpunit: 8.*
- squizlabs/php_codesniffer: ^3.5
This package is auto-updated.
Last update: 2024-09-29 05:22:23 UTC
README
Recipe 是一个基于 Laravel 组件构建的生成器框架,允许您根据提供的数据和模板生成任何内容。
本库的主要目标是提供创建生成器所需的工具。
要求
尽管它依赖于 Laravel 框架的 Blade 模板系统,但此库不需要 Laravel,它只拉取其中的一些组件。
这意味着您可以安全地将它包含在自己的框架无关的 composer 包中。
安装
composer require afterflow/recipe 0.1.*
基本用法
假设我们有一个简单的 Blade 模板
{{-- templates/user.blade.php --}} Name: {{$name}} Last Name: {{ $last_name }}
让我们使用 Recipe 编译一个字符串
$recipe = new \Afterflow\Recipe\Recipe(); $data = $recipe->with([ 'name' => 'Vlad', 'last_name' => 'Libre' ]) ->template(__DIR__ . '/templates/user.blade.php') ->render();
返回结果
Name: Vlad
Last Name: Libre
所以基本上我们只是编译了一个带有给定数据的 Blade 模板。但那并不是重点。现在让我们看看一些高级用法。
自定义 Recipe 类
让我们构建一个简单的自定义 recipe,我们可以重用它或将其嵌套在其他 recipe 中。它将从模板创建一个类并返回源代码。
模板
{{--templates/class.blade.php--}} {{-- Otherwise this file will be treated as PHP script--}} {!! '<'.'?php' !!} @unless(empty( $namespace )) namespace {{ $namespace }}; @endunless @unless(empty( $imports )) @foreach( $imports as $import) import {{ $import }}; @endforeach @endunless class {{ $name }} {{ isset($extends) ? 'extends '. $extends : '' }} {{ !empty($implements) ? 'implements '. collect($implements)->implode(', ') : '' }} { @unless(empty($traits)) use {{ collect($traits)->implode(', ') }}; @endunless @isset($content) {{--This function indents each line of $content string with 4 spaces--}} @indent($content,4) @endisset }
Recipe
<?php namespace Afterflow\Recipe\Recipes; use Afterflow\Recipe\Recipe; class ClassRecipe extends Recipe { protected $template = __DIR__ . '/../../templates/class.blade.php'; protected $props = [ 'name' => [ 'rules' => 'required|string', ], 'extends' => [ 'default' => '', 'rules' => 'string', ], 'namespace' => [ 'rules' => 'string', ], 'content' => [ 'default' => '', 'rules' => 'string', ], 'imports' => [ 'default' => [], 'rules' => 'array', ], 'implements' => [ 'default' => [], 'rules' => 'array', ], 'traits' => [ 'default' => [], 'rules' => 'array', ], ]; }
用法
$data = ( new ClassRecipe() )->with([ 'namespace' => 'App', 'name' => 'User', 'extends' => 'Authenticatable', 'imports' => [ 'Illuminate\Foundation\Auth\User as Authenticatable', 'Illuminate\Notifications\Notifiable', 'Laravel\Passport\HasApiTokens', ], 'traits' => [ 'HasApiTokens', 'Notifiable', ], 'implements' => [ 'SomeInterface', 'OtherInterface' ], ])->render();
由于我们现在使用自己扩展了 Recipe
的 ClassRecipe
类,所以这里发生的变化不多。这允许我们在类内部定义模板,并使用更短的用法语法。
在这里,您会注意到我们定义了一个新的 $props
变量,它与 VueJs 组件中使用的变量有些相似。
首先,我们添加了一些验证,告诉 Recipe 在此 recipe 中 name
数据属性是必需的。您可以像在 Laravel 应用程序中通常那样定义验证规则 - 这是一回事。
其次,我们为导入设置了默认值。如果用户没有提供任何输入,则将应用这些默认值。
不渲染构建数据
有时可能只需要从用户输入和定义的属性构建数据,而不使用任何模板
$data = ( new ClassRecipe() )->with([ 'namespace' => 'App', 'name' => 'User', 'extends' => 'Authenticatable', 'imports' => [ 'Illuminate\Foundation\Auth\User as Authenticatable', 'Illuminate\Notifications\Notifiable', 'Laravel\Passport\HasApiTokens', ], 'traits' => [ 'HasApiTokens', 'Notifiable', ], 'implements' => [ 'SomeInterface', 'OtherInterface' ], ])->build();
如果您调用 build()
而不是 render()
,您将获得在原始输入上应用属性后归一化的数据。您可以使用这些数据在其他 recipe 中创建复合生成器。
替代语法
在构建复杂的嵌套 recipe 时,简短的语法可能很有用。
// Full syntax $recipe = (new ClassRecipe())->with($data)->render(); // Pass data into constructor: $recipe = (new ClassRecipe($data))->render(); // Less braces: $recipe = ClassRecipe::make($data); // If your recipe defines a template or a custom render() function: $string = ClassRecipe::quickRender($data); // Compile data only: $data = ClassRecipe::quickBuild($data);
在渲染之前准备模板数据
有时在将数据发送到 Blade 编译器之前转换数据很有用。如果您的 recipe 有 dataForTemplate()
方法,则其返回值将用作模板的数据。
<?php namespace Afterflow\Recipe\Recipes; use Afterflow\Recipe\Recipe; class FunctionRecipe extends Recipe { protected $template = __DIR__ . '/../../templates/function.blade.php'; protected $props = [ 'name' => [ 'rules' => 'required', ], 'arguments' => [], // ... ]; public function dataForTemplate() { $data = $this->data(); $data['arguments'] = collect($data['arguments'])->implode(', '); return $data; } }
自定义渲染
通过在 recipe 中重写 render()
方法,您可以创建没有模板的 recipe 或定义任何其他自定义渲染逻辑。只需确保在内部调用 $this->build()
以应用属性。
<?php namespace Afterflow\Recipe\Recipes; use Afterflow\Recipe\Recipe; class ClassVarRecipe extends Recipe { protected $props = [ 'name' => [ 'rules' => 'required|string', ], 'visibility' => [ 'rules' => 'string|in:public,private,protected', ], 'value' => [ 'default' => '', 'rules' => 'string', ], 'static' => [ 'default' => false, 'rules' => 'boolean', ], 'const' => [ 'default' => false, 'rules' => 'boolean', ], 'docBlock' => [ 'default' => '', 'rules' => 'string', ], ]; public function render() { $string = ''; if ($v = $this->data('docBlock')) { $string .= $v . PHP_EOL; } if ($v = $this->data('visibility')) { $string .= $v . ' '; } if ($this->data('static')) { $string .= 'static '; } if ($this->data('const')) { $string .= 'const '; } $string .= $this->data('name'); if ($v = $this->data('value')) { $string .= ' = ' . $v; } $string .= ';'; return $string; } }
实现流畅式 API
为了让过程更加有趣和简单,您可以在 recipe 上撒一些流畅性
// ... public function name($value) { return $this->input('name', $value); } public function value($value) { return $this->input('value', $value); } public function const() { return $this->input('const', true); } // ...
然后您可以使用此 recipe 如此
$data = ClassVarRecipe::make()->name( '$name' ) ->protected() ->value( '"Vlad"' ) ->docBlock( '// First Name' ) ->render();
或在使用 recipe 时使用 MagicSetters
trait 以实现相同的功能(这不会处理 IDE 突出显示);
嵌套 recipe
现在让我们看看这有多么强大
/** * This recipe nests other recipes and shows alternative syntax to pass data through constructor */ $data = ClassRecipe::make()->namespace('App')->name('User')->content( /** * See ClassVarRecipe to learn how to render things without template */ Recipe::sequence([ ClassVarRecipe::make()->protected()->name('$name')->docBlock('// First Name')->render(), ClassVarRecipe::make()->protected()->name('$lastName')->docBlock('// Last Name')->render(), /** * See ClassVarRecipe to learn how to filter data before render */ ConstructorRecipe::make()->arguments([ 'string $name', 'string $lastName', ])->body('$this->name = $name;' . eol() . '$this->lastName = $lastName;')->render(), FunctionRecipe::make()->name('getLastName')->return('$this->lastName;')->render(), FunctionRecipe::make()->name('getName')->return('$this->name;')->render(), ], eol(2)) )->render();
由于 Recipe 实现了 __toString
,您可以省略这里的 render()
调用。
这将产生
<?php namespace App; class User { // First Name protected $name = "Vlad"; // Last Name protected $lastName; function __construct(string $name, string $lastName) { $this->name = $name; $this->lastName = $lastName; } function getLastName() { return $this->lastName; } function getName() { return $this->name; } }
现在您可以通过扩展或嵌套 ClassRecipe
来创建 Laravel 模型 recipe,并创建一个控制台命令来生成模型或执行任何其他疯狂的事情。
您可以使用的内置 recipe(工作正在进行中)
- ClassRecipe
- ClassVarRecipe
- FunctionRecipe
- MethodCallRecipe
- ConstructorRecipe
- Laravel/Models/ModelRecipe
- laravel/Models/Relations/RelationRecipe
指令
@indent($string, $spaces = 4) - indents each line of string output @sequence($array, $glue = ', ') - useful to render arrays
辅助函数
所有辅助函数都有命名空间
function q( $what ) { return "'" . $what . "'"; } function qq( $what ) { return '"' . $what . '"'; } function eol( $times = 1 ) { $str = ''; for ( $i = 0; $i < $times; $i ++ ) { $str .= PHP_EOL; } return $str; } function arr( $what, $d = [ '[', ']' ] ) { return Recipe::array( $what, $d ); }