Most of the application code can be generated using the set of console commands.
To install the extension:
composer require spiral/scaffolder
Note
Please note that the spiral/framework >= 2.7 already includes this component.
Make sure to add Spiral\Scaffolder\Bootloader\ScaffolderBootloader
to your App class:
use Spiral\Scaffolder\Bootloader\ScaffolderBootloader;
class App extends Kernel
{
// ...
protected const APP = [
// ...
ScaffolderBootloader::class
];
}
Note
Attention, the extension will invokeTokenizerConfig
, make sure to add it at the end of the bootload chain.
You can customize the scaffolder component by replacing declaration generators and their options using the scaffolder
configuration file. The default configuration located in
the ScaffolderBootloader
.
Some components can provide their own declarations to create elements using the Scaffolder.
Such components can register their custom declarations with the ScaffolderBootloader
:
namespace App\Bootloader;
use Spiral\Scaffolder\Bootloader\ScaffolderBootloader as BaseScaffolderBootloader;
class ScaffolderBootloader extends Bootloader
{
public const DEPENDENCIES = [
BaseScaffolderBootloader::class
];
public function boot(BaseScaffolderBootloader $scaffolder): void
{
$scaffolder->addDeclaration('declarationName', [
'namespace' => 'Namespace',
'postfix' => '', // like a Repository, Controller, etc
'class' => MyDeclaration::class, // declaration class
'options' => [
// some custom options
],
]);
}
}
Command | Description |
---|---|
create:bootloader | Create Bootloader declaration |
create:command | Create Command declaration |
create:config | Create Config declaration |
create:controller | Create Controller declaration |
create:filter | Create HTTP Request Filter declaration |
create:jobHandler | Create Job Handler declaration |
create:middleware | Create Middleware declaration |
create:migration | Create Migration declaration |
create:repository | Create Entity Repository declaration |
create:entity | Create Entity declaration |
php app.php create:bootloader <name>
<Name>Bootloader
class will be created.
php app.php create:bootloader my
Output is:
use Spiral\Boot\Bootloader\Bootloader;
class MyBootloader extends Bootloader
{
protected const BINDINGS = [];
protected const SINGLETONS = [];
protected const DEPENDENCIES = [];
public function boot(): void
{
}
}
php app.php create:command <name> [alias]
<Name>Command
class will be created. Command name will be equal to name
or alias
(if this value set).
alias
php app.php create:command my
Output is:
use Spiral\Console\Command;
class MyCommand extends Command
{
protected const NAME = 'my';
protected const DESCRIPTION = '';
protected const ARGUMENTS = [];
protected const OPTIONS = [];
/**
* Perform command
*/
protected function perform(): void
{
}
}
php app.php create:command my alias
Output is:
use Spiral\Console\Command;
class MyCommand extends Command
{
protected const NAME = 'alias';
protected const DESCRIPTION = '';
protected const ARGUMENTS = [];
protected const OPTIONS = [];
/**
* Perform command
*/
protected function perform(): void
{
}
}
php app.php create:config <name>
<Name>Config
class will be created. Also, <app directory>/config/<name>.php
file will be created if it doesn't
exist.
Available options:
reverse (r)
- Using this flag, scaffolder will look for <app directory>/config/<name>.php
file and create a
rich <Name>Config
class based on the given config file. The class will include default values and getters; in some
cases, it will also include by-key-getters for array values.
Details below: If an array-value consists of more than one sub-values with the same types for keys and sub-values, scaffolder will try to create a by-key-getter method. If a generated key is conflicting with an existing method, by-key-getter will be omitted.
php app.php create:config my
Output config file:
return [];
Output config class:
use Spiral\Core\InjectableConfig;
class MyConfig extends InjectableConfig
{
public const CONFIG = 'my';
/**
* @internal For internal usage. Will be hydrated in the constructor.
*/
protected $config = [];
}
//...existing "my.php" config file:
return [
//will create "getParam()" by-key-getter (successfully singularized name)
'params' => [
'one' => 'param',
'two' => 'another param',
],
//will create "getParameterBy()" by-key-getter (unsuccessfully singularized name)
'parameter' => [
'one' => 'parameter',
'two' => 'another parameter',
],
//will create "getValueBy()" by-key-getter (because "getValue()" conflicts with the next "value" field)
'values' => [
1 => 'value',
2 => 'another value',
],
'value' => 'third value',
//won't create by-key-getter due to only 1 sub-value
'few' => [
'one' => 'value',
],
//won't create by-key-getter due to mixed values' types
'mixedValues' => [
'one' => 'value',
'two' => 2,
],
//won't create by-key-getter due to mixed keys' types
'mixedKeys' => [
'one' => 'value',
2 => 'another value',
],
//won't create by-key-getter to name conflicts
//(because "getConflict()" and "getConflictBy()" conflicts with the next "conflict" and "conflictBy" fields)
'conflicts' => [
'one' => 'conflict',
'two' => 'another conflict',
],
'conflict' => 'third conflic',
'conflictBy' => 'fourth conflic',
];
php app.php create:config my -r
Output is:
use Spiral\Core\InjectableConfig;
class MyConfig extends InjectableConfig
{
public const CONFIG = 'my';
/**
* @internal For internal usage. Will be hydrated in the constructor.
*/
protected $config = [
'params' => [],
'parameter' => [],
'values' => [],
'value' => '',
'few' => [],
'mixedValues' => [],
'mixedKeys' => [],
'conflicts' => [],
'conflict' => '',
'conflictBy' => ''
];
/** @return string[] */
public function getParams(): array
{
return $this->config['params'];
}
/** @return string[] */
public function getParameter(): array
{
return $this->config['parameter'];
}
/** @return string[] */
public function getValues(): array
{
return $this->config['values'];
}
public function getValue(): string
{
return $this->config['value'];
}
/** @return string[] */
public function getFew(): array
{
return $this->config['few'];
}
public function getMixedValues(): array
{
return $this->config['mixedValues'];
}
/** @return string[] */
public function getMixedKeys(): array
{
return $this->config['mixedKeys'];
}
/** @return string[] */
public function getConflicts(): array
{
return $this->config['conflicts'];
}
public function getConflict(): string
{
return $this->config['conflict'];
}
public function getConflictBy(): string
{
return $this->config['conflictBy'];
}
public function getParam(string $param): string
{
return $this->config['params'][$param];
}
public function getParameterBy(string $parameter): string
{
return $this->config['parameter'][$parameter];
}
public function getValueBy(int $value): string
{
return $this->config['values'][$value];
}
}
php app.php create:controller <name>
<Name>Controller
class will be created. Available options:
action (a)
(multiple values allowed) - you can add actions using this optionprototype (p)
- if set, PrototypeTrait
will be addedphp app.php create:controller my
Output is:
class MyController
{
}
prototype
optionphp app.php create:controller my -p
Output is:
use Spiral\Prototype\Traits\PrototypeTrait;
class MyController
{
use PrototypeTrait;
}
php app.php create:controller my \
-a index \
-a create \
-a update \
-a delete
Output is:
class MyController
{
public function index()
{
}
public function create()
{
}
public function update()
{
}
public function delete()
{
}
}
php app.php create:filter <name>
<Name>Filter
class will be created. Available options:
entity (e)
- you can pass an EntityClass
and the filter command will fetch the all the givenclass properties into the filter and try to define each property's type based on its type declaration (if php74),
default value or a PhpDoc. Otherwise you can optionally specify filter schema using field
option.
field (f)
(multiple values allowed).Full field format is name:type(source:origin)
. Where type
, origin
and source:origin
are optional and can be omitted, defaults are:
Note
See more about filters in filters package
php app.php create:filter my
Output is:
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [];
protected const VALIDATES = [];
protected const SETTERS = [];
}
php app.php create:filter my \
-f unknown_val \
-f str_val:string \
-f int_val:int \
-f bool_val:bool(query:from_bool) \
-f float_val:float(query)
Output is:
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'unknown_val' => 'data:unknown_val',
'str_val' => 'data:str_val',
'int_val' => 'data:int_val',
'bool_val' => 'query:from_bool',
'float_val' => 'query:float_val'
];
protected const VALIDATES = [
'unknown_val' => [
'notEmpty',
'string'
],
'str_val' => [
'notEmpty',
'string'
],
'int_val' => [
'notEmpty',
'integer'
],
'bool_val' => [
'notEmpty',
'boolean'
],
'float_val' => [
'notEmpty',
'float'
]
];
protected const SETTERS = [];
}
//...existing "MyEntity" class:
class MyEntity
{
protected bool $typedBool;
public $noTypeString;
/** @var SomeOtherEntity */
public $obj;
/** @var int */
protected $intFromPhpDoc;
private $noTypeWithFloatDefault = 1.1;
}
php app.php create:filter my -e MyEntity
Output is:
use Spiral\Filters\Filter;
class MyFilter extends Filter
{
protected const SCHEMA = [
'typedBool' => 'data:typedBool',
'noTypeString' => 'data:noTypeString',
'obj' => 'data:obj',
'intFromPhpDoc' => 'data:intFromPhpDoc',
'noTypeWithFloatDefault' => 'data:noTypeWithFloatDefault'
];
protected const VALIDATES = [
'typedBool' => [
'notEmpty',
'boolean'
],
'noTypeString' => [
'notEmpty',
'string'
],
'obj' => [
'notEmpty',
'string'
],
'intFromPhpDoc' => [
'notEmpty',
'integer'
],
'noTypeWithFloatDefault' => [
'notEmpty',
'float'
]
];
protected const SETTERS = [];
}
php app.php create:jobHandler <name>
<Name>Job
class will be created.
php app.php create:jobHandler my
Output is:
use Spiral\Jobs\JobHandler;
class MyJob extends JobHandler
{
public function invoke(): void
{
}
}
php app.php create:middleware <name>
<Name>
class will be created.
php app.php create:middleware my
Output is:
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class My implements MiddlewareInterface
{
/**
* {@inheritdoc}
*/
public function process(Request $request, RequestHandlerInterface $handler): Response
{
return $handler->handle($request);
}
}
php app.php create:migration <name>
<Name>Migration
class will be created. Available options:
table (t)
for table namefield (f)
(multiple values allowed) for a field(s) definition. Will work only with table
option. Field format
is name:type
.Note
See more about migrations in migrations package
php app.php create:migration my
Output is:
use Spiral\Migrations\Migration;
class MyMigration extends Migration
{
/**
* Create tables, add columns or insert data here
*/
public function up(): void
{
}
/**
* Drop created, columns and etc here
*/
public function down(): void
{
}
}
php app.php create:migration my -t my_table -c int_col:int
Output is:
use Spiral\Migrations\Migration;
class MyMigration extends Migration
{
/**
* Create tables, add columns or insert data here
*/
public function up(): void
{
$this->table('my_table')
->addColumn('int_col', 'int')
->create();
}
/**
* Drop created, columns and etc here
*/
public function down(): void
{
$this->table('my_table')->drop();
}
}
php app.php create:repository <name>
<Name>Repository
class will be created.
php app.php create:repository my
Output is:
use Cycle\ORM\Select\Repository;
class MyRepository extends Repository
{
}
php app.php create:entity <name> [<format>]
<Name>Entity
class will be created.
format
is responsible for the declaration format. Currently, only annotations
format supported.
Available options:
role (r)
- Entity role, defaults to lowercase class name without a namespacemapper (m)
- Mapper class name, defaults to Cycle\ORM\Mapper\Mappertable (t)
- Entity source table, defaults to plural form of entity roleaccessibility (a)
- accessibility accessor (public, protected, private), defaults to publicinflection (i)
- Optional column name inflection, allowed values: tableize (or t), camelize (or c).
See Doctrine inflector
field (f)
- Add field in a format "name:type" (multiple values allowed)repository (e)
- Repository class to represent read operations for an entity, defaults
to Cycle\ORM\Select\Repository
database (d)
- Database name, defaults to null (default database)php app.php create:entity my
Output is:
use Cycle\Annotated\Annotation as Cycle;
/**
* @Cycle\Entity()
*/
class My
{
}
Note
It's recommended to replace the generatedEntity
annotation with an attribute
php app.php create:entity my -f field:string
Output is:
use Cycle\Annotated\Annotation as Cycle;
/**
* @Cycle\Entity()
*/
class My
{
/**
* @Cycle\Column(type = "string")
*/
public $field;
}
php app.php create:entity my \
-f field:string \
-a protected
Output is:
use Cycle\Annotated\Annotation as Cycle;
/**
* @Cycle\Entity()
*/
class My
{
/**
* @Cycle\Column(type = "string")
*/
protected $field;
public function setField(string $value)
{
$this->field = $value;
}
public function getField()
{
return $this->field;
}
}
php app.php create:entity my \
-f int_field:int \
-f stringField:string \
-i t
Output is:
use Cycle\Annotated\Annotation as Cycle;
/**
* @Cycle\Entity()
*/
class My
{
/**
* @Cycle\Column(type = "int")
*/
public $int_field;
/**
* @Cycle\Column(type = "string", name = "string_field")
*/
public $stringField;
}
php app.php create:entity my \
-f int_field:int \
-f stringField:string \
-i c
Output is:
use Cycle\Annotated\Annotation as Cycle;
/**
* @Cycle\Entity()
*/
class My
{
/**
* @Cycle\Column(type = "int", name = "intField")
*/
public $int_field;
/**
* @Cycle\Column(type = "string")
*/
public $stringField;
}
php app.php create:entity my \
-r myRole \
-m MyMapper \
-t my_table \
-d my_db \
-e
Output is:
use Cycle\Annotated\Annotation as Cycle;
/**
* @Cycle\Entity(role = "myRole", mapper = "MyMapper", repository = "my", table = "my_table", database = "my_db")
*/
class My
{
}
And the repository class is also created:
use Cycle\ORM\Select\Repository;
class MyRepository extends Repository
{
}