Customizing Generators
Generators are responsible for creating individual files during CRUD generation, or running commands as part of the pipeline.
You can Create a Generator, or you can use (or extend) the Available Generators.
Creating a Generator
There are two types of generators:
The Standard Generator, which generates a single file from a stub, replacing placeholders with actual content.
The Custom Generator, which doesn't follow the standard pattern, and can be used for complex logic like running commands or modifying existing files.
Quick Start: Using the Make Command
The fastest way to create a new generator is using the make:generator command:
# Create a standard generator (recommended for most cases)
php artisan make:generator Repository
# Or create a custom generator for complex logic
php artisan make:generator Repository --customThis command will:
- Create a generator class at
app/Generators/{Name}Generator.php - Automatically register it in
app/Providers/CrudTemplatesServiceProvider.php
TIP
Use --force to overwrite an existing generator file.
Creating a Standard Generator
Step 1: Create a stub file
First, create the stub file that defines what you want to generate. Create the stub file at stubs/crud.repository.stub:
<?php
namespace App\Repositories{{ NAMESPACE_PATH }};
{{ NAMESPACES }}
class {{ MODEL }}Repository
{
public function all(): Collection
{
return {{ MODEL }}::all();
}
public function find(int $id): ?{{ MODEL }}
{
return {{ MODEL }}::find($id);
}
public function create(array $data): {{ MODEL }}
{
return {{ MODEL }}::create($data);
}
public function update({{ MODEL }} ${{ MODEL_CAMEL }}, array $data): {{ MODEL }}
{
${{ MODEL_CAMEL }}->update($data);
return ${{ MODEL_CAMEL }};
}
public function delete({{ MODEL }} ${{ MODEL_CAMEL }}): bool
{
return ${{ MODEL_CAMEL }}->delete();
}
}Step 2: Run the make command
Run the make:generator command to create a new generator:
php artisan make:generator Repository --standardThis will generate the following file:
<?php
namespace App\Generators;
use JCSoriano\CrudTemplates\Generators\Generator;
use JCSoriano\CrudTemplates\Generators\StandardGenerator;
use JCSoriano\CrudTemplates\DataObjects\Payload;
class RepositoryGenerator extends Generator
{
use StandardGenerator;
/**
* Returns the directory path where the file will be created.
*/
protected function directory(Payload $payload): string
{
return app_path('path/to/directory');
}
/**
* Returns the file name without the `.php` extension.
*/
protected function fileName(Payload $payload): string
{
return $payload->model->model()->studlyCase();
}
/**
* Returns the file type used in console logging (e.g., "Controller", "Model", "Policy").
*/
protected function fileType(Payload $payload): string
{
return 'File';
}
/**
* Returns the path to the stub file (checks custom stubs directory first).
*/
protected function stubPath(Payload $payload): string
{
return 'your-stub-file.stub';
}
}Simply fill out the methods with the appropriate values.
<?php
namespace App\Generators;
use JCSoriano\CrudTemplates\Generators\Generator;
use JCSoriano\CrudTemplates\Generators\StandardGenerator;
use JCSoriano\CrudTemplates\DataObjects\Payload;
class RepositoryGenerator extends Generator
{
use StandardGenerator;
protected function directory(Payload $payload): string
{
$namespace = $payload->model->namespace();
return app_path('Repositories/'.str_replace('\\', '/', $namespace));
}
protected function fileName(Payload $payload): string
{
return $payload->model->model()->studlyCase().'Repository';
}
protected function fileType(Payload $payload): string
{
return 'Repository';
}
protected function stubPath(Payload $payload): string
{
return 'crud.repository.stub';
}
protected function variables(Payload $payload): array
{
$modelName = $payload->model->model()->studlyCase();
$modelNamespace = $this->buildNamespace('App\\Models', $payload);
$namespaces = collect([
"{$modelNamespace}\\{$modelName}",
'Illuminate\Support\Collection',
]);
return [
...$payload->variables(),
'NAMESPACES' => $this->buildNamespaces($namespaces),
];
}
}The StandardGenerator trait automatically handles:
- Creating the directory if it doesn't exist
- Checking if the file exists (respects
--forceflag) - Generating the file from the stub
- Logging the generated file
- Returning the payload
The make command will also automatically register the generator in the CrudTemplatesServiceProvider:
use App\Generators\RepositoryGenerator;
public function registerGenerators(): void
{
$this->app->bind('crud-templates::generator::repository', RepositoryGenerator::class);
}You can then now use it in your custom template:
use App\Generators\RepositoryGenerator;
protected function template(): array
{
return $this->buildGenerators([
'controller',
'model',
'repository', // Your registered generator
'migration',
// ... other generators
]);
}Creating a Custom Generator
You can also create a custom generator for complex logic like running commands or modifying existing files.
Step 1: Run the make command
Run the make:generator command to create a new generator:
php artisan make:generator RunTests --customThis will generate the following file:
<?php
namespace {{ namespace }};
use JCSoriano\CrudTemplates\Generators\Generator;
use JCSoriano\CrudTemplates\DataObjects\Payload;
class RunTestsGenerator extends Generator
{
/**
* Generate the file.
*/
public function generate(Payload $payload): Payload
{
// Implement your custom generation logic here
return $payload;
}
}Simply fill out the generate() method with the appropriate logic.
<?php
namespace App\Generators;
use JCSoriano\CrudTemplates\Generators\Generator;
use JCSoriano\CrudTemplates\DataObjects\Payload;
use Illuminate\Support\Facades\Process;
class RunTestsGenerator extends Generator
{
public function generate(Payload $payload): Payload
{
$model = $payload->model;
$namespace = $model->namespace();
$modelName = $model->model()->studlyCase();
// Build path to the test file
$directory = base_path('tests/Feature/Api/'.str_replace('\\', '/', $namespace));
$fileName = $modelName.'ControllerTest';
$testPath = $directory.'/'.$fileName.'.php';
$result = Process::run("php artisan test {$testPath}");
// Output test results to console
$payload->components->line($result->output());
return $payload;
}
}The make command will automatically register the generator in the CrudTemplatesServiceProvider:
use App\Generators\RunTestsGenerator;
public function registerGenerators(): void
{
$this->app->bind('crud-templates::generator::run-tests', RunTestsGenerator::class);
}Use this approach when you need to:
- Run commands or external tools
- Modify existing files instead of creating new ones
- Perform operations on files created earlier in the pipeline
- Execute multiple operations in sequence
- Implement complex logic that doesn't fit the standard pattern
Available Generators
Below is a list of available generators, the stub they are based on, and the file they generate:
controller
- Stub File:
api/controller.stub - Generates file:
app/Http/Controllers/Api/Content/PostController.php
model
- Stub File:
api/model.stub - Generates file:
app/Models/Content/Post.php
policy
- Stub File:
api/policy.stub - Generates file:
app/Policies/PostPolicy.php
store-request
- Stub File:
api/request.store.stub - Generates file:
app/Http/Requests/Content/StorePostRequest.php
update-request
- Stub File:
api/request.update.stub - Generates file:
app/Http/Requests/Content/UpdatePostRequest.php
resource
- Stub File:
api/resource.stub - Generates file:
app/Http/Resources/Content/PostResource.php
migration
- Stub File:
api/migration.stub - Generates file:
database/migrations/2024_01_01_000000_create_posts_table.php
factory
- Stub File:
api/factory.stub - Generates file:
database/factories/Content/PostFactory.php
test
- Stub File:
api/test.stub - Generates file:
tests/Feature/Api/Content/PostControllerTest.php
api-route
- Stub File:
api/route.stub - Generates file: Appends routes to
routes/api.php
pint
- Stub File: N/A
- Generates file: Runs Laravel Pint on generated files
Customizing Stubs
All stubs are completely customizable. You can publish the stubs for modification by running:
php artisan vendor:publish --tag="crud-templates-stubs"See Customizing Stubs for more details.
Overriding existing generators
To override an existing generator, you can bind your custom generator to the key of the generator you want to override (e.g., crud-templates::generator::controller).
use App\Generators\MyCustomControllerGenerator;
public function registerGenerators(): void
{
$this->app->bind('crud-templates::generator::controller', MyCustomControllerGenerator::class);
}Now, any template that has the controller generator will use your custom generator instead.
StandardGenerator Trait Reference
The StandardGenerator trait simplifies generator creation by handling the common pattern of generating a single file from a stub.
Required Abstract Methods
When using StandardGenerator, you must implement these four abstract methods:
directory(Payload $payload): string
Returns the directory path where the file will be created.
protected function directory(Payload $payload): string
{
$namespace = $payload->model->namespace();
return app_path('Http/Controllers/Api/'.str_replace('\\', '/', $namespace));
}fileName(Payload $payload): string
Returns the file name without the .php extension.
protected function fileName(Payload $payload): string
{
return $payload->model->model()->studlyCase().'Controller';
}fileType(Payload $payload): string
Returns the file type used in console logging (e.g., "Controller", "Model", "Policy").
protected function fileType(Payload $payload): string
{
return 'Controller';
}stubPath(Payload $payload): string
Returns the path to the stub file (checks custom stubs directory first).
protected function stubPath(Payload $payload): string
{
return 'api/controller.stub';
}Overridable Methods
These methods have default implementations but can be overridden:
variables(Payload $payload): array
Returns variables for stub placeholder replacement. By default, returns $payload->variables().
protected function variables(Payload $payload): array
{
$modelName = $payload->model->model()->studlyCase();
return [
...$payload->variables(),
'CUSTOM_VAR' => 'custom value',
];
}conditions(Payload $payload): array
Returns conditions for stub conditional logic. By default, returns $payload->conditions().
protected function conditions(Payload $payload): array
{
return $payload->conditions();
}shouldSkipGeneration(Payload $payload): bool
Returns whether to skip generation. By default, returns false.
protected function shouldSkipGeneration(Payload $payload): bool
{
if ($payload->table) {
$payload->components->warn('Skipping generation...');
return true;
}
return false;
}The Payload Object
The Payload object contains all data needed for generation:
Properties
// Core properties
$payload->model; // Model: Model data (name, namespace, etc.)
$payload->fields; // Collection: Field definitions
$payload->components; // Factory: Laravel console components for output (read-only)
$payload->force; // bool: Force overwrite existing files (read-only)
$payload->table; // ?string: Database table name (read-only)
// Configuration arrays
$payload->options; // array: Additional options passed to the command
$payload->variables; // array: Custom variables for stub replacement
$payload->conditions; // array: Custom conditions for stub logic
$payload->data; // array: Additional data for generators
$payload->skip; // array: Generators to skipMethods
$payload->variables(); // Returns array: All variables for stub replacement (includes MODEL, MODEL_CAMEL, NAMESPACE_PATH, etc.)
$payload->conditions(); // Returns array: All conditions for stub logic (includes scopeUser, scopeTeam, scopeNone)Note: Properties marked as "read-only" can be accessed but not modified directly. The variables and conditions properties are arrays you can modify to add custom values, while the variables() and conditions() methods return the complete merged arrays including defaults.
Helper Methods
The Generator base class provides useful helper methods:
createDirectoryIfNotExists()
Creates a directory if it doesn't exist:
$this->createDirectoryIfNotExists(app_path('Http/Controllers/Api'));getStubPath()
Gets the path to a stub file (checks custom stubs first):
$stubPath = $this->getStubPath('crud.controller.stub');logGeneratedFile()
Logs a generated file to the console:
$this->logGeneratedFile('Controller', $directory, $fileName, $payload);buildNamespace()
Builds a namespace with the model's namespace path:
$namespace = $this->buildNamespace('App\\Models', $payload);
// Returns: App\Models\Content (if model is Content/Post)buildNamespaces()
Builds multiple use statements from a collection:
$namespaces = collect([
'App\Models\Post',
'Illuminate\Http\Request',
]);
$useStatements = $this->buildNamespaces($namespaces);
// Returns: "use App\Models\Post;\nuse Illuminate\Http\Request;"Working with Printers
Printers generate specific parts of files (like fillable arrays, casts, etc.). You can use printers in your generators:
public function variables(Payload $payload): Payload
{
// Use a printer (inherited from Generator base class)
$fillableOutput = $this->print('fillable', $payload);
$fillableFields = $fillableOutput->output;
// Add to variables
$variables = [
...$payload->variables(),
'FILLABLE' => $fillableFields,
];
// ... rest of generation logic
}See Customizing Printers for more details.
Accessing Field Data
Process fields in your generator:
public function generate(Payload $payload): Payload
{
$fields = $payload->fields;
// Filter specific field types
$relationFields = $fields->filter(function ($field) {
return in_array($field->type->name, ['belongsTo', 'hasMany', 'belongsToMany']);
});
// Check if certain fields exist
$hasTimestamps = $fields->contains(function ($field) {
return $field->name->name === 'created_at';
});
// Add to payload data
$payload->data['has_relations'] = $relationFields->isNotEmpty();
return $payload;
}Next Steps
- Explore Customizing Stubs to change generated code
- Check Customizing Field Types for custom field logic
- Learn about Customizing Printers to modify code snippets