orchestra/canvas-core
Core utilities for Orchestra Canvas code generators. Build and customize generators for Laravel apps and packages, with testing and coverage support. Provides the foundational services used by Canvas to scaffold code and streamline development workflows.
Installation:
composer require orchestra/canvas-core
For Laravel 11.x, target orchestra/canvas-core:^10.0 (check compatibility notes).
First Use Case:
Create a basic generator command by extending Orchestra\Canvas\Core\GeneratorCommand:
use Orchestra\Canvas\Core\GeneratorCommand;
class MakeApiResource extends GeneratorCommand
{
protected $name = 'make:api-resource';
protected $description = 'Create a new API resource with controller, model, and migrations';
protected $type = 'ApiResource';
public function configure()
{
$this->option('with-tests', null, 'Generate test stubs');
$this->option('with-migrations', null, 'Generate migrations');
}
protected function getStub()
{
return __DIR__.'/stubs/api-resource.stub';
}
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Http\Controllers\Api';
}
}
Register the Command:
Add the command to app/Console/Kernel.php:
protected $commands = [
\App\Console\Commands\MakeApiResource::class,
];
Run It:
php artisan make:api-resource User --with-tests
orchestra/canvas-core/src/: Study the GeneratorCommand, Preset, and GeneratesCodeListener classes.Extend GeneratorCommand for simple scaffolding (e.g., controllers, models):
class MakeDomainService extends GeneratorCommand
{
protected $name = 'make:domain-service';
protected $description = 'Create a new domain service';
protected $type = 'DomainService';
protected function getStub()
{
return __DIR__.'/stubs/domain-service.stub';
}
protected function getNamespace($targetNamespace)
{
return $targetNamespace.'\Services';
}
}
Use Laravel’s PromptsForMissingInput concern for dynamic inputs:
use Illuminate\Console\Concerns\PromptsForMissingInput;
class MakeFeatureFlag extends GeneratorCommand
{
use PromptsForMissingInput;
protected function promptForMissingInput()
{
$this->ask('Feature flag name', null, function ($name) {
return Str::of($name)->kebab();
});
}
}
Define reusable configurations in app/Canvas/Presets/ApiResourcePreset.php:
namespace App\Canvas\Presets;
use Orchestra\Canvas\Core\Presets\Preset;
class ApiResourcePreset extends Preset
{
public function namespace()
{
return 'App\Http\Controllers\Api';
}
public function stub()
{
return __DIR__.'/../../Console/Commands/stubs/api-resource.stub';
}
public function migrationsNamespace()
{
return 'App\Migrations';
}
}
Register the preset in AppServiceProvider:
public function boot()
{
Canvas::preset('api-resource', ApiResourcePreset::class);
}
Use GeneratesCodeListener for post-processing (e.g., run migrations, publish configs):
use Orchestra\Canvas\Core\GeneratesCodeListener;
class RunMigrationsAfterGeneration implements GeneratesCodeListener
{
public function afterCodeHasBeenGenerated($command, $name, $path)
{
if (Str::contains($command, 'make:api-resource') && $this->option('with-migrations')) {
Artisan::call('migrate', ['--path' => 'database/migrations']);
}
}
}
Bind the listener in AppServiceProvider:
public function boot()
{
Canvas::listen(RunMigrationsAfterGeneration::class);
}
Chain commands using orchestra/workbench (included via orchestra/sidekick):
use Orchestra\Workbench\Actions\Action;
use Orchestra\Workbench\Actions\Artisan;
class MakeModule extends GeneratorCommand
{
protected function handle()
{
$actions = [
new Artisan('make:model', ['name' => $this->argument('name')]),
new Artisan('make:controller', ['name' => 'ModuleController']),
new Artisan('make:migration', ['name' => 'create_'.$this->argument('name').'_table']),
];
Action::run($actions);
}
}
resources/stubs/ or a dedicated stubs/ directory in your package.orchestra/testbench-core to test generators in isolation.# .github/workflows/generate.yml
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
- run: composer install
- run: php artisan make:api-resource User --with-tests
getPath():
protected function getPath($name)
{
return $this->laravel->basePath("modules/{$name}/src");
}
Laravel Version Mismatches:
orchestra/canvas-core:^8.11 for Laravel 10.x or wait for Orchestra’s validation of v11.x.Stub Path Resolution:
__DIR__.'/stubs/...') breaks when moving generators to packages.Illuminate\Filesystem\join_paths() or base_path():
protected function getStub()
{
return join_paths(__DIR__, '..', '..', 'stubs', 'api-resource.stub');
}
Null Generators Config:
null to generators() in presets causes runtime errors.public function generators()
{
return [
'controller' => ControllerGenerator::class,
];
}
Deprecated Methods:
possibleModelsUsingCanvas() is soft-deprecated in v10.1.1.possibleModels() instead.PHP Attributes Overhead:
Sidekick Dependency:
orchestra/sidekick is a hidden dependency (required for orchestra/workbench actions).composer.json:
"require": {
"orchestra/sidekick": "^5.0"
}
--dry-run to test generators without writing files:
if ($this->option('dry-run')) {
$this->info("Would generate: {$this->getPath($name)}");
return;
}
dd($this->getStub()) to verify stub paths.GeneratesCodeListener:
public function afterCodeHasBeenGenerated($command, $name, $path)
{
\Log::info("Generated {$path} via {$command}");
}
$this->info("Options: " . print_r($this->options(), true));
Orchestra\Canvas\Core\GeneratorCommand for new scaffolding types.make:domain-event for CHow can I help you explore Laravel packages today?