update project

This commit is contained in:
root
2026-05-30 01:11:35 -04:00
parent 3a0628ecc7
commit 2225f6bc72
9743 changed files with 1122482 additions and 59 deletions
+84
View File
@@ -0,0 +1,84 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "orchestra/canvas-core",
"description": "Code Generators Builder for Laravel Applications and Packages",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
},
{
"name": "Mior Muhammad Zaki",
"email": "crynobone@gmail.com"
}
],
"autoload": {
"psr-4": {
"Orchestra\\Canvas\\Core\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Orchestra\\Canvas\\Core\\Tests\\": "tests/",
"Workbench\\App\\": "workbench/app/"
}
},
"require": {
"php": "^8.3",
"composer-runtime-api": "^2.2",
"composer/semver": "^3.0",
"illuminate/console": "^13.0",
"illuminate/support": "^13.0",
"orchestra/sidekick": "~1.1.23|~1.2.20"
},
"require-dev": {
"laravel/framework": "^13.0",
"laravel/pint": "^1.24",
"mockery/mockery": "^1.6.10",
"orchestra/testbench-core": "^11.0",
"phpstan/phpstan": "^2.1.17",
"phpunit/phpunit": "^11.5.50|^12.5.8|^13.0.0",
"symfony/yaml": "^7.4|^8.0"
},
"config": {
"audit": {
"ignore": {
"GHSA-78fx-h6xr-vch4": "Affected component is not in use"
}
},
"sort-packages": true
},
"support": {
"issues": "https://github.com/orchestral/canvas/issues"
},
"extra": {
"laravel": {
"providers": [
"Orchestra\\Canvas\\Core\\LaravelServiceProvider"
]
}
},
"scripts": {
"post-autoload-dump": [
"@clear",
"@prepare"
],
"clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
"prepare": "@php vendor/bin/testbench package:discover --ansi",
"lint": [
"@php vendor/bin/pint --ansi --parallel",
"@php vendor/bin/phpstan analyse --verbose --ansi"
],
"test": "@php vendor/bin/phpunit -c ./ --color",
"ci": [
"@composer audit",
"@prepare",
"@lint",
"@test"
]
},
"prefer-stable": true,
"minimum-stability": "dev"
}
@@ -0,0 +1,26 @@
<?php
namespace Orchestra\Canvas\Core\Actions;
/**
* @api
*/
class DumpComposerAutoloads
{
/**
* Construct a new action.
*/
public function __construct(
protected string $workingPath
) {}
/**
* Handle the action.
*/
public function handle(): void
{
app('canvas.composer')
->setWorkingPath($this->workingPath)
->dumpAutoloads();
}
}
@@ -0,0 +1,45 @@
<?php
namespace Orchestra\Canvas\Core\Actions;
use RuntimeException;
use function Orchestra\Sidekick\Filesystem\join_paths;
/**
* @api
*/
class ModifyComposer
{
/**
* Construct a new action.
*/
public function __construct(
protected string $workingPath
) {}
/**
* Handle the action.
*
* @param callable(array):array $callback
*
* @throws \RuntimeException
*/
public function handle(callable $callback): void
{
$composerFile = join_paths($this->workingPath, 'composer.json');
if (! file_exists($composerFile)) {
throw new RuntimeException("Unable to locate `composer.json` file at [{$this->workingPath}].");
}
$composer = json_decode((string) file_get_contents($composerFile), true, 512, JSON_THROW_ON_ERROR);
$composer = \call_user_func($callback, $composer);
file_put_contents(
$composerFile,
json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)
);
}
}
@@ -0,0 +1,129 @@
<?php
namespace Orchestra\Canvas\Core\Actions;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Filesystem\Filesystem;
use Orchestra\Sidekick\Env;
/**
* @api
*/
class WriteEnvironmentVariables
{
/**
* Construct a new action instance.
*/
public function __construct(
public Filesystem $filesystem,
public string|false|null $filename,
) {}
/**
* Handle the action.
*
* @param array<string, mixed> $variables
*
* @throws \RuntimeException
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
public function handle(array $variables, bool $overwrite = false): void
{
if (! \is_string($this->filename)) {
throw new FileNotFoundException;
}
$this->writeVariables($variables, $this->filename, $overwrite);
}
/**
* Write an array of key-value pairs to the environment file.
*
* @laravel-overrides
*
* @param array<string, mixed> $variables
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
protected function writeVariables(array $variables, string $filename, bool $overwrite = false): void
{
if ($this->filesystem->missing($filename)) {
throw new FileNotFoundException("The file [{$filename}] does not exist.");
}
$lines = explode(PHP_EOL, $this->filesystem->get($filename));
foreach ($variables as $key => $value) {
$lines = $this->addVariableToEnvContents($key, $value, $lines, $overwrite);
}
$this->filesystem->put($filename, implode(PHP_EOL, $lines));
}
/**
* Add a variable to the environment file contents.
*
* @laravel-overrides
*/
protected function addVariableToEnvContents(string $key, mixed $value, array $envLines, bool $overwrite): array
{
$prefix = explode('_', $key)[0].'_';
$lastPrefixIndex = -1;
$shouldQuote = \is_string($value) && preg_match('/^[a-zA-z0-9]+$/', $value) === 0;
$lineToAddVariations = [
$key.'='.(\is_string($value) ? '"'.addslashes($value).'"' : Env::encode($value)),
$key.'='.(\is_string($value) ? "'".addslashes($value)."'" : Env::encode($value)),
$key.'='.Env::encode($value),
];
$lineToAdd = $shouldQuote ? $lineToAddVariations[0] : $lineToAddVariations[2];
if ($value === '') {
$lineToAdd = $key.'=';
}
foreach ($envLines as $index => $line) {
if (str_starts_with($line, $prefix)) {
$lastPrefixIndex = $index;
}
if (\in_array($line, $lineToAddVariations)) {
// This exact line already exists, so we don't need to add it again.
return $envLines;
}
if ($line === $key.'=') {
// If the value is empty, we can replace it with the new value.
$envLines[$index] = $lineToAdd;
return $envLines;
}
if (str_starts_with($line, $key.'=')) {
if (! $overwrite) {
return $envLines;
}
$envLines[$index] = $lineToAdd;
return $envLines;
}
}
if ($lastPrefixIndex === -1) {
if (\count($envLines) && $envLines[\count($envLines) - 1] !== '') {
$envLines[] = '';
}
return array_merge($envLines, [$lineToAdd]);
}
return array_merge(
\array_slice($envLines, 0, $lastPrefixIndex + 1),
[$lineToAdd],
\array_slice($envLines, $lastPrefixIndex + 1)
);
}
}
@@ -0,0 +1,84 @@
<?php
namespace Orchestra\Canvas\Core\Commands;
use Illuminate\Console\GeneratorCommand as Command;
use Orchestra\Canvas\Core\Concerns;
use Orchestra\Canvas\Core\Contracts\GeneratesCode;
/**
* @property string|null $name
* @property string|null $description
*/
abstract class GeneratorCommand extends Command implements GeneratesCode
{
use Concerns\CodeGenerator;
use Concerns\TestGenerator;
use Concerns\UsesGeneratorOverrides;
/** {@inheritDoc} */
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/** {@inheritDoc} */
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/** {@inheritDoc} */
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/** {@inheritDoc} */
#[\Override]
protected function qualifyModel(string $model)
{
return $this->qualifyModelUsingCanvas($model);
}
/** {@inheritDoc} */
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/** {@inheritDoc} */
#[\Override]
protected function userProviderModel()
{
return $this->userProviderModelUsingCanvas();
}
/** {@inheritDoc} */
#[\Override]
protected function viewPath($path = '')
{
return $this->viewPathUsingCanvas($path);
}
/** {@inheritDoc} */
#[\Override]
protected function possibleModels()
{
return $this->possibleModelsUsingCanvas();
}
/** {@inheritDoc} */
#[\Override]
protected function possibleEvents()
{
return $this->possibleEventsUsingCanvas();
}
}
@@ -0,0 +1,38 @@
<?php
namespace Orchestra\Canvas\Core\Commands;
use Illuminate\Console\MigrationGeneratorCommand as Command;
use Orchestra\Canvas\Core\Concerns\MigrationGenerator;
/**
* @property string|null $name
* @property string|null $description
*/
abstract class MigrationGeneratorCommand extends Command
{
use MigrationGenerator;
/** {@inheritDoc} */
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/** {@inheritDoc} */
#[\Override]
protected function createBaseMigration($table)
{
return $this->createBaseMigrationUsingCanvas($table);
}
/** {@inheritDoc} */
#[\Override]
protected function migrationExists($table)
{
return $this->migrationExistsUsingCanvas($table);
}
}
@@ -0,0 +1,100 @@
<?php
namespace Orchestra\Canvas\Core\Concerns;
use Illuminate\Console\Concerns\CreatesMatchingTest;
use Illuminate\Support\Str;
trait CodeGenerator
{
use CreatesUsingGeneratorPreset;
/**
* Generate code.
*/
public function generateCode(): bool
{
$name = $this->getNameInput();
$force = $this->hasOption('force') && $this->option('force') === true;
$className = $this->qualifyClass($name);
$path = $this->getPath($this->qualifyClass($name));
// First we need to ensure that the given name is not a reserved word within the PHP
// language and that the class name will actually be valid. If it is not valid we
// can error now and prevent from polluting the filesystem using invalid files.
if ($this->isReservedName($name)) {
$this->components->error(\sprintf('The name "%s" is reserved by PHP.', $name));
return false;
}
// Next, We will check to see if the class already exists. If it does, we don't want
// to create the class and overwrite the user's code. So, we will bail out so the
// code is untouched. Otherwise, we will continue generating this class' files.
if (! $force && $this->alreadyExists($name)) {
return $this->codeAlreadyExists($className, $path);
}
// Next, we will generate the path to the location where this class' file should get
// written. Then, we will build the class and make the proper replacements on the
// stub files so that it gets the correctly formatted namespace and class name.
$this->makeDirectory($path);
$this->files->put(
$path, $this->sortImports($this->generatingCode($this->buildClass($className), $className))
);
if (\in_array(CreatesMatchingTest::class, class_uses_recursive($this))) {
$this->handleTestCreationUsingCanvas($path);
}
return tap($this->codeHasBeenGenerated($className, $path), function ($exitCode) use ($className, $path) {
$this->afterCodeHasBeenGenerated($className, $path);
});
}
/**
* Handle generating code.
*/
public function generatingCode(string $stub, string $className): string
{
return $stub;
}
/**
* Code already exists.
*/
public function codeAlreadyExists(string $className, string $path): bool
{
$this->components->error(
\sprintf(
'%s [%s] already exists!', $this->type, Str::after($path, $this->generatorPreset()->basePath().DIRECTORY_SEPARATOR)
)
);
return false;
}
/**
* Code successfully generated.
*/
public function codeHasBeenGenerated(string $className, string $path): bool
{
$this->components->info(
\sprintf(
'%s [%s] created successfully.', $this->type, Str::after($path, $this->generatorPreset()->basePath().DIRECTORY_SEPARATOR)
)
);
return true;
}
/**
* Run after code successfully generated.
*/
public function afterCodeHasBeenGenerated(string $className, string $path): void
{
//
}
}
@@ -0,0 +1,52 @@
<?php
namespace Orchestra\Canvas\Core\Concerns;
use Illuminate\Support\Str;
use Orchestra\Canvas\Core\PresetManager;
use Orchestra\Canvas\Core\Presets\Preset;
use Symfony\Component\Console\Input\InputOption;
trait CreatesUsingGeneratorPreset
{
/**
* Add the standard command options for generating with preset.
*
* @return void
*/
protected function addGeneratorPresetOptions()
{
$message = 'to running the command';
if (property_exists($this, 'type') && ! empty($this->type)) {
$message = 'when generating '.Str::lower($this->type);
}
$this->getDefinition()->addOption(new InputOption(
'preset',
null,
InputOption::VALUE_OPTIONAL,
\sprintf('Preset used %s', $message),
null,
));
}
/**
* Resolve the generator preset.
*/
protected function generatorPreset(): Preset
{
/** @var string|null $preset */
$preset = $this->option('preset');
return $this->laravel->make(PresetManager::class)->driver($preset);
}
/**
* Get the generator preset source path.
*/
protected function getGeneratorSourcePath(): string
{
return $this->generatorPreset()->sourcePath();
}
}
@@ -0,0 +1,30 @@
<?php
namespace Orchestra\Canvas\Core\Concerns;
use function Orchestra\Sidekick\Filesystem\join_paths;
trait MigrationGenerator
{
use CreatesUsingGeneratorPreset;
/**
* Create a base migration file for the table.
*/
protected function createBaseMigrationUsingCanvas(string $table): string
{
return $this->laravel->make('migration.creator')->create(
"create_{$table}_table", $this->generatorPreset()->migrationPath()
);
}
/**
* Determine whether a migration for the table already exists.
*/
protected function migrationExistsUsingCanvas(string $table): bool
{
return \count($this->files->glob(
join_paths($this->generatorPreset()->migrationPath(), '*_*_*_*_create_'.$table.'_table.php')
)) !== 0;
}
}
@@ -0,0 +1,34 @@
<?php
namespace Orchestra\Canvas\Core\Concerns;
use function Orchestra\Sidekick\Filesystem\join_paths;
trait ResolvesPresetStubs
{
/**
* Resolve the fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveStubPath($stub)
{
$preset = $this->generatorPreset();
return $preset->hasCustomStubPath() && file_exists($customPath = join_paths($preset->basePath(), trim($stub, '/')))
? $customPath
: $this->resolveDefaultStubPath($stub);
}
/**
* Resolve the default fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveDefaultStubPath($stub)
{
return $stub;
}
}
@@ -0,0 +1,29 @@
<?php
namespace Orchestra\Canvas\Core\Concerns;
use Illuminate\Support\Str;
trait TestGenerator
{
use CreatesUsingGeneratorPreset;
/**
* Create the matching test case if requested.
*/
protected function handleTestCreationUsingCanvas(string $path): bool
{
if (! $this->option('test') && ! $this->option('pest')) {
return false;
}
$sourcePath = $this->generatorPreset()->sourcePath();
return $this->call('make:test', array_merge([
'name' => Str::of($path)->after($sourcePath)->beforeLast('.php')->append('Test')->replace('\\', '/'),
'--pest' => $this->option('pest'),
], array_filter([
'--preset' => $this->hasOption('preset') ? $this->option('preset') : null,
]))) == 0;
}
}
@@ -0,0 +1,126 @@
<?php
namespace Orchestra\Canvas\Core\Concerns;
use Illuminate\Support\Str;
use Orchestra\Canvas\Core\Presets\Preset;
use Symfony\Component\Finder\Finder;
use function Orchestra\Sidekick\Filesystem\join_paths;
trait UsesGeneratorOverrides
{
/**
* Qualify the given model class base name.
*/
protected function qualifyModelUsingCanvas(string $model): string
{
$model = ltrim($model, '\\/');
$model = str_replace('/', '\\', $model);
if (str_starts_with($model, $this->rootNamespace())) {
return $model;
}
return $this->generatorPreset()->modelNamespace().$model;
}
/**
* Get the destination class path.
*/
protected function getPathUsingCanvas(string $name): string
{
$name = Str::replaceFirst($this->rootNamespace(), '', $name);
return join_paths($this->getGeneratorSourcePath(), str_replace('\\', '/', $name).'.php');
}
/**
* Get the root namespace for the class.
*/
protected function rootNamespaceUsingCanvas(): string
{
return $this->generatorPreset()->rootNamespace();
}
/**
* Get the model for the default guard's user provider.
*/
protected function userProviderModelUsingCanvas(?string $guard = null): ?string
{
return $this->generatorPreset()->userProviderModel($guard);
}
/**
* Get the first view directory path from the application configuration.
*/
protected function viewPathUsingCanvas(string $path = ''): string
{
$views = $this->generatorPreset()->viewPath();
return join_paths($views, $path);
}
/**
* Get a list of possible model names.
*
* @return array<int, string>
*/
protected function findAvailableModelsUsingCanvas(): array
{
$sourcePath = $this->generatorPreset()->sourcePath();
$modelPath = is_dir(join_paths($sourcePath, 'Models')) ? join_paths($sourcePath, 'Models') : $sourcePath;
return collect((new Finder)->files()->depth(0)->in($modelPath))
->map(fn ($file) => $file->getBasename('.php'))
->sort()
->values()
->all();
}
/**
* Get a list of possible model names.
*
* @return array<int, string>
*
* @deprecated 10.1.0 Use `findAvailableModelsUsingCanvas()` instead.
*/
protected function possibleModelsUsingCanvas(): array
{
return $this->findAvailableModelsUsingCanvas();
}
/**
* Get a list of possible event names.
*
* @return array<int, string>
*/
protected function possibleEventsUsingCanvas(): array
{
$eventPath = join_paths($this->generatorPreset()->sourcePath(), 'Events');
if (! is_dir($eventPath)) {
return [];
}
return collect((new Finder)->files()->depth(0)->in($eventPath))
->map(fn ($file) => $file->getBasename('.php'))
->sort()
->values()
->all();
}
/**
* Get the root namespace for the class.
*
* @return string
*/
abstract protected function rootNamespace();
/**
* Resolve the generator preset.
*/
abstract protected function generatorPreset(): Preset;
}
@@ -0,0 +1,26 @@
<?php
namespace Orchestra\Canvas\Core\Contracts;
interface GeneratesCode
{
/**
* Handle generating code.
*/
public function generatingCode(string $stub, string $className): string;
/**
* Code already exists.
*/
public function codeAlreadyExists(string $className, string $path): bool;
/**
* Code successfully generated.
*/
public function codeHasBeenGenerated(string $className, string $path): bool;
/**
* Run after code successfully generated.
*/
public function afterCodeHasBeenGenerated(string $className, string $path): void;
}
@@ -0,0 +1,33 @@
<?php
namespace Orchestra\Canvas\Core;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Composer;
use Illuminate\Support\ServiceProvider;
class LaravelServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register services.
*/
public function register(): void
{
$this->app->bind('canvas.composer', static fn () => new Composer(new Filesystem));
$this->app->singleton(PresetManager::class, static fn ($app) => new PresetManager($app));
}
/**
* Get the services provided by the provider.
*
* @return array<int, class-string>
*/
public function provides(): array
{
return [
PresetManager::class,
];
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace Orchestra\Canvas\Core;
use Illuminate\Support\Manager;
class PresetManager extends Manager
{
/**
* The default driver name.
*/
protected string $defaultPreset = 'laravel';
/**
* Create "laravel" driver.
*/
public function createLaravelDriver(): Presets\Laravel
{
/** @phpstan-ignore argument.type */
return new Presets\Laravel($this->container);
}
/**
* Set the default driver name.
*
* @param string $name
* @return void
*/
public function setDefaultDriver($name)
{
$this->defaultPreset = $name;
}
/**
* Get the default driver name.
*
* @return string
*/
public function getDefaultDriver()
{
return $this->defaultPreset;
}
}
+148
View File
@@ -0,0 +1,148 @@
<?php
namespace Orchestra\Canvas\Core\Presets;
use function Orchestra\Sidekick\Filesystem\join_paths;
class Laravel extends Preset
{
/**
* Preset name.
*/
public function name(): string
{
return 'laravel';
}
/**
* Get the path to the base working directory.
*/
public function basePath(): string
{
return $this->app->basePath();
}
/**
* Get the path to the source directory.
*/
public function sourcePath(): string
{
return $this->app->basePath('app');
}
/**
* Get the path to the testing directory.
*/
public function testingPath(): string
{
return $this->app->basePath('tests');
}
/**
* Get the path to the resource directory.
*/
public function resourcePath(): string
{
return $this->app->resourcePath();
}
/**
* Get the path to the view directory.
*/
public function viewPath(): string
{
return $this->app->make('config')->get('view.paths')[0] ?? $this->app->resourcePath('views');
}
/**
* Get the path to the factory directory.
*/
public function factoryPath(): string
{
return $this->app->databasePath('factories');
}
/**
* Get the path to the migration directory.
*/
public function migrationPath(): string
{
return $this->app->databasePath('migrations');
}
/**
* Get the path to the seeder directory.
*/
public function seederPath(): string
{
if (is_dir($seederPath = $this->app->databasePath('seeds'))) {
return $seederPath;
}
return $this->app->databasePath('seeders');
}
/**
* Preset namespace.
*/
public function rootNamespace(): string
{
return $this->app->getNamespace();
}
/**
* Command namespace.
*/
public function commandNamespace(): string
{
return "{$this->rootNamespace()}Console\Commands\\";
}
/**
* Model namespace.
*/
public function modelNamespace(): string
{
return is_dir(join_paths($this->sourcePath(), 'Models')) ? "{$this->rootNamespace()}Models\\" : $this->rootNamespace();
}
/**
* Provider namespace.
*/
public function providerNamespace(): string
{
return "{$this->rootNamespace()}Providers\\";
}
/**
* Testing namespace.
*/
public function testingNamespace(): string
{
return 'Tests\\';
}
/**
* Database factory namespace.
*/
public function factoryNamespace(): string
{
return 'Database\Factories\\';
}
/**
* Database seeder namespace.
*/
public function seederNamespace(): string
{
return 'Database\Seeders\\';
}
/**
* Preset has custom stub path.
*/
public function hasCustomStubPath(): bool
{
return true;
}
}
+132
View File
@@ -0,0 +1,132 @@
<?php
namespace Orchestra\Canvas\Core\Presets;
use Illuminate\Contracts\Foundation\Application;
use LogicException;
abstract class Preset
{
/**
* Construct a new preset.
*
* @return void
*/
public function __construct(
protected Application $app
) {}
/**
* Check if preset name equal to $name.
*/
public function is(string $name): bool
{
return $this->name() === $name;
}
/**
* Get the model for the default guard's user provider.
*
* @return class-string|null
*
* @throws \LogicException
*/
public function userProviderModel(?string $guard = null): ?string
{
/** @var \Illuminate\Contracts\Config\Repository $config */
$config = $this->app->make('config');
$guard = $guard ?: $config->get('auth.defaults.guard');
if (\is_null($provider = $config->get("auth.guards.{$guard}.provider"))) {
throw new LogicException(\sprintf('The [%s] guard is not defined in your "auth" configuration file.', $guard));
}
return $config->get("auth.providers.{$provider}.model");
}
/**
* Preset name.
*/
abstract public function name(): string;
/**
* Get the path to the base working directory.
*/
abstract public function basePath(): string;
/**
* Get the path to the source directory.
*/
abstract public function sourcePath(): string;
/**
* Get the path to the testing directory.
*/
abstract public function testingPath(): string;
/**
* Get the path to the resource directory.
*/
abstract public function resourcePath(): string;
/**
* Get the path to the view directory.
*/
abstract public function viewPath(): string;
/**
* Get the path to the factory directory.
*/
abstract public function factoryPath(): string;
/**
* Get the path to the migration directory.
*/
abstract public function migrationPath(): string;
/**
* Get the path to the seeder directory.
*/
abstract public function seederPath(): string;
/**
* Preset namespace.
*/
abstract public function rootNamespace(): string;
/**
* Command namespace.
*/
abstract public function commandNamespace(): string;
/**
* Model namespace.
*/
abstract public function modelNamespace(): string;
/**
* Provider namespace.
*/
abstract public function providerNamespace(): string;
/**
* Testing namespace.
*/
abstract public function testingNamespace(): string;
/**
* Database factory namespace.
*/
abstract public function factoryNamespace(): string;
/**
* Database seeder namespace.
*/
abstract public function seederNamespace(): string;
/**
* Preset has custom stub path.
*/
abstract public function hasCustomStubPath(): bool;
}
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env php
<?php
define('CANVAS_WORKING_PATH', $workingPath = getcwd());
define('TESTBENCH_WORKING_PATH', $workingPath);
require $_composer_autoload_path ?? __DIR__.'/vendor/autoload.php';
$config = Orchestra\Testbench\Foundation\Config::loadFromYaml(
workingPath: $workingPath,
defaults: [
'providers' => [],
'dont-discover' => [],
],
);
$commander = new Orchestra\Canvas\Console\Commander($config->getAttributes(), $workingPath);
$commander->handle();
+90
View File
@@ -0,0 +1,90 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "orchestra/canvas",
"description": "Code Generators for Laravel Applications and Packages",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
},
{
"name": "Mior Muhammad Zaki",
"email": "crynobone@gmail.com"
}
],
"bin": [
"canvas"
],
"autoload": {
"psr-4": {
"Orchestra\\Canvas\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Orchestra\\Canvas\\Tests\\": "tests/",
"Illuminate\\Tests\\Integration\\Generators\\": "workbench/tests/"
}
},
"require": {
"php": "^8.3",
"composer-runtime-api": "^2.2",
"composer/semver": "^3.0",
"illuminate/console": "^13.0.0",
"illuminate/database": "^13.0.0",
"illuminate/filesystem": "^13.0.0",
"illuminate/support": "^13.0.0",
"orchestra/canvas-core": "^11.0.0",
"orchestra/sidekick": "~1.1.23|~1.2.20",
"orchestra/testbench-core": "^11.0.0",
"symfony/yaml": "^7.4.0|^8.0.0"
},
"require-dev": {
"laravel/framework": "^13.0.0",
"laravel/pint": "^1.24",
"mockery/mockery": "^1.6.10",
"phpstan/phpstan": "^2.1.14",
"phpunit/phpunit": "^11.5.50|^12.5.8|^13.0.0"
},
"config": {
"preferred-install": {
"laravel/framework": "source",
"*": "auto"
},
"sort-packages": true
},
"extra": {
"laravel": {
"providers": [
"Orchestra\\Canvas\\LaravelServiceProvider"
]
}
},
"scripts": {
"post-autoload-dump": [
"@clear",
"@prepare"
],
"clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
"prepare": "@php vendor/bin/testbench package:discover --ansi",
"lint": [
"@php vendor/bin/pint --ansi --parallel",
"@php vendor/bin/phpstan analyse --verbose"
],
"test": [
"@composer dump-autoload",
"@php vendor/bin/phpunit --no-coverage --no-configuration --bootstrap vendor/autoload.php --color ./tests ./workbench/tests"
],
"sync": "@php bin/sync",
"ci": [
"@composer audit",
"@prepare",
"@lint",
"@test"
]
},
"prefer-stable": true,
"minimum-stability": "dev"
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace Orchestra\Canvas;
use Illuminate\Support\Arr;
use InvalidArgumentException;
class Canvas
{
/**
* Make Preset from configuration.
*
* @param array<string, mixed> $config
*/
public static function preset(array $config, string $basePath): Presets\Preset
{
/** @var array<string, mixed> $configuration */
$configuration = Arr::except($config, 'preset');
/** @var string|class-string<\Orchestra\Canvas\Presets\Preset> $preset */
$preset = $config['preset'] ?? 'laravel';
/** @phpstan-ignore return.type */
return match (true) {
$preset === 'package' => new Presets\Package($configuration, $basePath),
$preset === 'laravel' => new Presets\Laravel($configuration, $basePath),
class_exists($preset) => new $preset($configuration, $basePath),
default => throw new InvalidArgumentException(\sprintf('Unable to resolve %s preset', $preset)),
};
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace Orchestra\Canvas;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
use Orchestra\Canvas\Core\PresetManager;
use Symfony\Component\Yaml\Yaml;
use function Orchestra\Sidekick\join_paths;
class CanvasServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register services.
*/
public function register(): void
{
$this->callAfterResolving(PresetManager::class, static function ($manager, $app) {
$manager->extend('canvas', fn ($app) => new GeneratorPreset($app));
$manager->setDefaultDriver('canvas');
});
$this->app->singleton('orchestra.canvas', static function (Application $app) {
$workingPath = \defined('CANVAS_WORKING_PATH') ? CANVAS_WORKING_PATH : $app->basePath();
$filesystem = $app->make('files');
$config = ['preset' => 'laravel'];
if (file_exists(join_paths($workingPath, 'canvas.yaml'))) {
$config = Yaml::parseFile(join_paths($workingPath, 'canvas.yaml'));
} else {
Arr::set($config, 'testing.extends', [
'unit' => 'PHPUnit\Framework\TestCase',
'feature' => 'Tests\TestCase',
]);
$config['namespace'] = rescue(fn () => rtrim($app->getNamespace(), '\\'), null, false);
}
return Canvas::preset($config, $workingPath);
});
}
/**
* Bootstrap services.
*/
public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->commands([
Console\GeneratorMakeCommand::class,
Console\PresetMakeCommand::class,
]);
}
}
/**
* Get the services provided by the provider.
*
* @return array<int, string>
*/
public function provides()
{
return [
'orchestra.canvas',
];
}
}
@@ -0,0 +1,57 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\MigrationGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Queue/Console/BatchesTableCommand.php
*/
#[AsCommand(name: 'make:queue-batches-table', description: 'Create a migration for the batches database table', aliases: ['queue:batches-table'])]
class BatchesTableCommand extends \Illuminate\Queue\Console\BatchesTableCommand
{
use MigrationGenerator;
/**
* The console command name.
*
* @var string
*/
protected $name = 'make:queue-batches-table';
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Create a base migration file for the table.
*
* @param string $table
* @return string
*/
#[\Override]
protected function createBaseMigration($table)
{
return $this->createBaseMigrationUsingCanvas($table);
}
/**
* Determine whether a migration for the table already exists.
*
* @param string $table
* @return bool
*/
#[\Override]
protected function migrationExists($table)
{
return $this->migrationExistsUsingCanvas($table);
}
}
@@ -0,0 +1,50 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\MigrationGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Cache/Console/CacheTableCommand.php
*/
#[AsCommand(name: 'make:cache-table', description: 'Create a migration for the cache database table', aliases: ['cache:table'])]
class CacheTableCommand extends \Illuminate\Cache\Console\CacheTableCommand
{
use MigrationGenerator;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Create a base migration file for the table.
*
* @param string $table
* @return string
*/
#[\Override]
protected function createBaseMigration($table)
{
return $this->createBaseMigrationUsingCanvas($table);
}
/**
* Determine whether a migration for the table already exists.
*
* @param string $table
* @return bool
*/
#[\Override]
protected function migrationExists($table)
{
return $this->migrationExistsUsingCanvas($table);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/CastMakeCommand.php
*/
#[AsCommand(name: 'make:cast', description: 'Create a new custom Eloquent cast class')]
class CastMakeCommand extends \Illuminate\Foundation\Console\CastMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,67 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ChannelMakeCommand.php
*/
#[AsCommand(name: 'make:channel', description: 'Create a new channel class')]
class ChannelMakeCommand extends \Illuminate\Foundation\Console\ChannelMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ClassMakeCommand.php
*/
#[AsCommand(name: 'make:class', description: 'Create a new class')]
class ClassMakeCommand extends \Illuminate\Foundation\Console\ClassMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace Orchestra\Canvas\Console;
use Illuminate\Contracts\Console\Kernel as ConsoleKernel;
use Illuminate\Foundation\Application as LaravelApplication;
use Illuminate\Support\Collection;
use Orchestra\Canvas\CanvasServiceProvider;
use Orchestra\Canvas\Core\Concerns\CreatesUsingGeneratorPreset;
use Orchestra\Canvas\LaravelServiceProvider;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
/**
* @codeCoverageIgnore
*/
class Commander extends \Orchestra\Testbench\Console\Commander
{
/** {@inheritDoc} */
protected string $environmentFile = '.env';
/** {@inheritDoc} */
protected array $providers = [];
/** {@inheritDoc} */
#[\Override]
protected function resolveApplicationCallback()
{
return static function ($app) {
$app->register(CanvasServiceProvider::class);
};
}
/** {@inheritDoc} */
#[\Override]
public function laravel()
{
if (! $this->app instanceof LaravelApplication) {
$app = parent::laravel();
/** @var \Illuminate\Contracts\Console\Kernel $kernel */
$kernel = $app->make(ConsoleKernel::class);
$app->register(LaravelServiceProvider::class);
Collection::make($kernel->all())
->reject(
static fn (SymfonyCommand $command, string $name) => \in_array(CreatesUsingGeneratorPreset::class, class_uses_recursive($command))
)->each(static function (SymfonyCommand $command) {
$command->setHidden(true);
});
}
/** @phpstan-ignore return.type */
return $this->app;
}
}
@@ -0,0 +1,89 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ComponentMakeCommand.php
*/
#[AsCommand(name: 'make:component', description: 'Create a new view component class')]
class ComponentMakeCommand extends \Illuminate\Foundation\Console\ComponentMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Run after code successfully generated.
*/
public function afterCodeHasBeenGenerated(string $className, string $path): void
{
if ($this->option('view') || ! $this->option('inline')) {
$this->writeView();
}
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get the first view directory path from the application configuration.
*
* @param string $path
* @return string
*/
#[\Override]
protected function viewPath($path = '')
{
return $this->viewPathUsingCanvas($path);
}
}
@@ -0,0 +1,79 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ConsoleMakeCommand.php
*/
#[AsCommand(name: 'make:command', description: 'Create a new Artisan command')]
class ConsoleMakeCommand extends \Illuminate\Foundation\Console\ConsoleMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
#[\Override]
protected function getDefaultNamespace($rootNamespace)
{
return rtrim($this->generatorPreset()->commandNamespace(), '\\');
}
}
@@ -0,0 +1,184 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Routing/Console/ControllerMakeCommand.php
*/
#[AsCommand(name: 'make:controller', description: 'Create a new controller class')]
class ControllerMakeCommand extends \Illuminate\Routing\Console\ControllerMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Qualify the given model class base name.
*
* @return string
*/
#[\Override]
protected function qualifyModel(string $model)
{
return $this->qualifyModelUsingCanvas($model);
}
/**
* Build the replacements for a parent controller.
*
* @return array
*/
#[\Override]
protected function buildParentReplacements()
{
/** @var class-string|string $parentModelClass */
/** @phpstan-ignore argument.type */
$parentModelClass = $this->parseModel($this->option('parent'));
if (! class_exists($parentModelClass) &&
$this->components->confirm("A {$parentModelClass} model does not exist. Do you want to generate it?", true)) {
$this->call('make:model', ['name' => $parentModelClass, '--preset' => $this->option('preset')]);
}
return [
'ParentDummyFullModelClass' => $parentModelClass,
'{{ namespacedParentModel }}' => $parentModelClass,
'{{namespacedParentModel}}' => $parentModelClass,
'ParentDummyModelClass' => class_basename($parentModelClass),
'{{ parentModel }}' => class_basename($parentModelClass),
'{{parentModel}}' => class_basename($parentModelClass),
'ParentDummyModelVariable' => lcfirst(class_basename($parentModelClass)),
'{{ parentModelVariable }}' => lcfirst(class_basename($parentModelClass)),
'{{parentModelVariable}}' => lcfirst(class_basename($parentModelClass)),
];
}
/**
* Build the model replacement values.
*
* @return array
*/
#[\Override]
protected function buildModelReplacements(array $replace)
{
/** @var class-string|string $modelClass */
/** @phpstan-ignore argument.type */
$modelClass = $this->parseModel($this->option('model'));
if (! class_exists($modelClass) && $this->components->confirm("A {$modelClass} model does not exist. Do you want to generate it?", true)) {
$this->call('make:model', ['name' => $modelClass, '--preset' => $this->option('preset')]);
}
$replace = $this->buildFormRequestReplacements($replace, $modelClass);
if ($this->option('requests')) {
$namespace = $this->rootNamespace();
$replace['{{ namespacedRequests }}'] = str_replace('App\\', $namespace, $replace['{{ namespacedRequests }}']);
$replace['{{namespacedRequests}}'] = str_replace('App\\', $namespace, $replace['{{namespacedRequests}}']);
}
return array_merge($replace, [
'DummyFullModelClass' => $modelClass,
'{{ namespacedModel }}' => $modelClass,
'{{namespacedModel}}' => $modelClass,
'DummyModelClass' => class_basename($modelClass),
'{{ model }}' => class_basename($modelClass),
'{{model}}' => class_basename($modelClass),
'DummyModelVariable' => lcfirst(class_basename($modelClass)),
'{{ modelVariable }}' => lcfirst(class_basename($modelClass)),
'{{modelVariable}}' => lcfirst(class_basename($modelClass)),
]);
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get a list of possible model names.
*
* @return array<int, string>
*/
#[\Override]
protected function findAvailableModels()
{
return $this->findAvailableModelsUsingCanvas();
}
/**
* Generate the form requests for the given model and classes.
*
* @param string $modelClass
* @param string $storeRequestClass
* @param string $updateRequestClass
* @return array
*/
#[\Override]
protected function generateFormRequests($modelClass, $storeRequestClass, $updateRequestClass)
{
$storeRequestClass = 'Store'.class_basename($modelClass).'Request';
$this->call('make:request', [
'name' => $storeRequestClass,
'--preset' => $this->option('preset'),
]);
$updateRequestClass = 'Update'.class_basename($modelClass).'Request';
$this->call('make:request', [
'name' => $updateRequestClass,
'--preset' => $this->option('preset'),
]);
return [$storeRequestClass, $updateRequestClass];
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Routing/Console/EnumMakeCommand.php
*/
#[AsCommand(name: 'make:enum', description: 'Create a new enum')]
class EnumMakeCommand extends \Illuminate\Foundation\Console\EnumMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/EventMakeCommand.php
*/
#[AsCommand(name: 'make:event', description: 'Create a new event class')]
class EventMakeCommand extends \Illuminate\Foundation\Console\EventMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ExceptionMakeCommand.php
*/
#[AsCommand(name: 'make:exception', description: 'Create a new custom exception class')]
class ExceptionMakeCommand extends \Illuminate\Foundation\Console\ExceptionMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,127 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\ResolvesPresetStubs;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
use function Orchestra\Sidekick\Filesystem\join_paths;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Database/Console/Factories/FactoryMakeCommand.php
*/
#[AsCommand(name: 'make:factory', description: 'Create a new model factory')]
class FactoryMakeCommand extends \Illuminate\Database\Console\Factories\FactoryMakeCommand
{
use CodeGenerator;
use ResolvesPresetStubs;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Resolve the default fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveDefaultStubPath($stub)
{
return join_paths(__DIR__, $stub);
}
/**
* Get the full namespace for a given class, without the class name.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getNamespace($name)
{
return rtrim($this->generatorPreset()->factoryNamespace(), '\\');
}
/**
* Get the generator preset source path.
*/
protected function getGeneratorSourcePath(): string
{
return $this->generatorPreset()->factoryPath();
}
/**
* Guess the model name from the Factory name or return a default model name.
*
* @param string $name
* @return string
*/
#[\Override]
protected function guessModelName($name)
{
if (str_ends_with($name, 'Factory')) {
$name = substr($name, 0, -7);
}
return $this->qualifyModelUsingCanvas($name);
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get the model for the default guard's user provider.
*
* @return string|null
*/
#[\Override]
protected function userProviderModel()
{
return $this->userProviderModelUsingCanvas();
}
}
@@ -0,0 +1,50 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\MigrationGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Queue/Console/FailedTableCommand.php
*/
#[AsCommand(name: 'make:queue-failed-table', description: 'Create a migration for the failed queue jobs database table', aliases: ['queue:failed-table'])]
class FailedTableCommand extends \Illuminate\Queue\Console\FailedTableCommand
{
use MigrationGenerator;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Create a base migration file for the table.
*
* @param string $table
* @return string
*/
#[\Override]
protected function createBaseMigration($table)
{
return $this->createBaseMigrationUsingCanvas($table);
}
/**
* Determine whether a migration for the table already exists.
*
* @param string $table
* @return bool
*/
#[\Override]
protected function migrationExists($table)
{
return $this->migrationExistsUsingCanvas($table);
}
}
@@ -0,0 +1,90 @@
<?php
namespace Orchestra\Canvas\Console;
use Illuminate\Support\Str;
use Orchestra\Canvas\Core\Commands\GeneratorCommand;
use Orchestra\Canvas\Core\Concerns\ResolvesPresetStubs;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
use function Orchestra\Sidekick\Filesystem\join_paths;
#[AsCommand(name: 'make:generator', description: 'Create a new generator command')]
class GeneratorMakeCommand extends GeneratorCommand
{
use ResolvesPresetStubs;
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Generator';
/**
* Replace the class name for the given stub.
*
* @param string $stub
* @param string $name
* @return string
*/
#[\Override]
protected function replaceClass($stub, $name)
{
$stub = parent::replaceClass($stub, $name);
/** @var string $command */
$command = $this->option('command') ?: 'make:'.Str::of($name)->classBasename()->kebab()->value();
return str_replace(['dummy:command', '{{ command }}'], $command, $stub);
}
/**
* Get the stub file for the generator.
*
* @return string
*/
#[\Override]
protected function getStub()
{
return $this->resolveStubPath(join_paths('stubs', 'generator.stub'));
}
/**
* Resolve the default fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveDefaultStubPath($stub)
{
return join_paths(__DIR__, $stub);
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
#[\Override]
protected function getDefaultNamespace($rootNamespace)
{
return rtrim($this->generatorPreset()->commandNamespace(), '\\');
}
/**
* Get the console command options.
*
* @return array<int, array>
*/
#[\Override]
protected function getOptions()
{
return [
['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the generator already exists'],
['command', null, InputOption::VALUE_OPTIONAL, 'The terminal command that should be assigned', 'make:name'],
];
}
}
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/InterfaceMakeCommand.php
*/
#[AsCommand(name: 'make:interface', description: 'Create a new interface')]
class InterfaceMakeCommand extends \Illuminate\Foundation\Console\InterfaceMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/JobMakeCommand.php
*/
#[AsCommand(name: 'make:job', description: 'Create a new job class')]
class JobMakeCommand extends \Illuminate\Foundation\Console\JobMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,67 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/JobMiddlewareMakeCommand.php
*/
#[AsCommand(name: 'make:job-middleware', description: 'Create a new job middleware class')]
class JobMiddlewareMakeCommand extends \Illuminate\Foundation\Console\JobMiddlewareMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,78 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ListenerMakeCommand.php
*/
#[AsCommand(name: 'make:listener', description: 'Create a new event listener class')]
class ListenerMakeCommand extends \Illuminate\Foundation\Console\ListenerMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get a list of possible event names.
*
* @return array<int, string>
*/
#[\Override]
protected function possibleEvents()
{
return $this->possibleEventsUsingCanvas();
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/MailMakeCommand.php
*/
#[AsCommand(name: 'make:mail', description: 'Create a new email class')]
class MailMakeCommand extends \Illuminate\Foundation\Console\MailMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Run after code successfully generated.
*/
public function afterCodeHasBeenGenerated(string $className, string $path): void
{
if ($this->option('markdown') !== false) {
$this->writeMarkdownTemplate();
}
if ($this->option('view') !== false) {
$this->writeView();
}
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,67 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Routing/Console/MiddlewareMakeCommand.php
*/
#[AsCommand(name: 'make:middleware', description: 'Create a new middleware class')]
class MiddlewareMakeCommand extends \Illuminate\Routing\Console\MiddlewareMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,52 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Commands\Command;
use Orchestra\Canvas\Core\Concerns\CreatesUsingGeneratorPreset;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Database/Console/Migrations/MigrateMakeCommand.php
*/
#[AsCommand(name: 'make:migration', description: 'Create a new migration file')]
class MigrateMakeCommand extends \Illuminate\Database\Console\Migrations\MigrateMakeCommand
{
use CreatesUsingGeneratorPreset;
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Migration';
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return void
*/
#[\Override]
public function handle()
{
$preset = $this->generatorPreset();
if (! $preset->is('laravel')) {
$this->input->setOption('path', $preset->migrationPath());
$this->input->setOption('realpath', true);
}
parent::handle();
}
}
+219
View File
@@ -0,0 +1,219 @@
<?php
namespace Orchestra\Canvas\Console;
use Illuminate\Support\Str;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ModelMakeCommand.php
*/
#[AsCommand(name: 'make:model', description: 'Create a new Eloquent model class')]
class ModelMakeCommand extends \Illuminate\Foundation\Console\ModelMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Run after code successfully generated.
*/
protected function afterCodeHasBeenGenerated(): void
{
if ($this->option('all')) {
$this->input->setOption('factory', true);
$this->input->setOption('seed', true);
$this->input->setOption('migration', true);
$this->input->setOption('controller', true);
$this->input->setOption('policy', true);
$this->input->setOption('resource', true);
}
if ($this->option('factory')) {
$this->createFactory();
}
if ($this->option('migration')) {
$this->createMigration();
}
if ($this->option('seed')) {
$this->createSeeder();
}
if ($this->option('controller') || $this->option('resource') || $this->option('api')) {
$this->createController();
}
if ($this->option('policy')) {
$this->createPolicy();
}
}
/**
* Create a model factory for the model.
*
* @return void
*/
#[\Override]
protected function createFactory()
{
$factory = Str::studly($this->getNameInput());
$this->call('make:factory', [
'name' => "{$factory}Factory",
'--model' => $this->qualifyClass($this->getNameInput()),
'--preset' => $this->option('preset'),
]);
}
/**
* Create a migration file for the model.
*
* @return void
*/
#[\Override]
protected function createMigration()
{
$table = Str::snake(Str::pluralStudly(class_basename($this->getNameInput())));
if ($this->option('pivot')) {
$table = Str::singular($table);
}
$this->call('make:migration', [
'name' => "create_{$table}_table",
'--create' => $table,
'--fullpath' => true,
'--preset' => $this->option('preset'),
]);
}
/**
* Create a seeder file for the model.
*
* @return void
*/
#[\Override]
protected function createSeeder()
{
$seeder = Str::studly(class_basename($this->getNameInput()));
$this->call('make:seeder', [
'name' => "{$seeder}Seeder",
'--preset' => $this->option('preset'),
]);
}
/**
* Create a controller for the model.
*
* @return void
*/
#[\Override]
protected function createController()
{
$controller = Str::studly(class_basename($this->getNameInput()));
$modelName = $this->qualifyClass($this->getNameInput());
$this->call('make:controller', array_filter([
'name' => "{$controller}Controller",
'--model' => $this->option('resource') || $this->option('api') ? $modelName : null,
'--api' => $this->option('api'),
'--requests' => $this->option('requests') || $this->option('all'),
'--preset' => $this->option('preset'),
]));
}
/**
* Create a policy file for the model.
*
* @return void
*/
#[\Override]
protected function createPolicy()
{
$policy = Str::studly(class_basename($this->getNameInput()));
$this->call('make:policy', [
'name' => "{$policy}Policy",
'--model' => $this->qualifyClass($this->getNameInput()),
'--preset' => $this->option('preset'),
]);
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
#[\Override]
protected function getDefaultNamespace($rootNamespace)
{
return rtrim($this->generatorPreset()->modelNamespace(), '\\');
}
/**
* Qualify the given model class base name.
*
* @return string
*/
#[\Override]
protected function qualifyModel(string $model)
{
return $this->qualifyModelUsingCanvas($model);
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,77 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/NotificationMakeCommand.php
*/
#[AsCommand(name: 'make:notification', description: 'Create a new notification class')]
class NotificationMakeCommand extends \Illuminate\Foundation\Console\NotificationMakeCommand
{
use CodeGenerator;
use TestGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Run after code successfully generated.
*/
public function afterCodeHasBeenGenerated(string $className, string $path): void
{
if ($this->option('markdown')) {
$this->writeMarkdownTemplate();
}
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,50 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\MigrationGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Notifications/Console/NotificationTableCommand.php
*/
#[AsCommand(name: 'make:notifications-table', description: 'Create a migration for the notifications table', aliases: ['notifications:table'])]
class NotificationTableCommand extends \Illuminate\Notifications\Console\NotificationTableCommand
{
use MigrationGenerator;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Create a base migration file for the table.
*
* @param string $table
* @return string
*/
#[\Override]
protected function createBaseMigration($table)
{
return $this->createBaseMigrationUsingCanvas($table);
}
/**
* Determine whether a migration for the table already exists.
*
* @param string $table
* @return bool
*/
#[\Override]
protected function migrationExists($table)
{
return $this->migrationExistsUsingCanvas($table);
}
}
@@ -0,0 +1,87 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ObserverMakeCommand.php
*/
#[AsCommand(name: 'make:observer', description: 'Create a new observer class')]
class ObserverMakeCommand extends \Illuminate\Foundation\Console\ObserverMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Qualify the given model class base name.
*
* @return string
*/
#[\Override]
protected function qualifyModel(string $model)
{
return $this->qualifyModelUsingCanvas($model);
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get a list of possible model names.
*
* @return array<int, string>
*/
#[\Override]
protected function findAvailableModels()
{
return $this->findAvailableModelsUsingCanvas();
}
}
@@ -0,0 +1,101 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/PolicyMakeCommand.php
*/
#[AsCommand(name: 'make:policy', description: 'Create a new policy class')]
class PolicyMakeCommand extends \Illuminate\Foundation\Console\PolicyMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Qualify the given model class base name.
*
* @return string
*/
#[\Override]
protected function qualifyModel(string $model)
{
return $this->qualifyModelUsingCanvas($model);
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get the model for the default guard's user provider.
*
* @return string|null
*/
#[\Override]
protected function userProviderModel()
{
/** @var string|null $guard */
$guard = $this->option('guard');
return $this->userProviderModelUsingCanvas($guard);
}
/**
* Get a list of possible model names.
*
* @return array<int, string>
*/
#[\Override]
protected function findAvailableModels()
{
return $this->findAvailableModelsUsingCanvas();
}
}
@@ -0,0 +1,140 @@
<?php
namespace Orchestra\Canvas\Console;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Orchestra\Canvas\Core\Commands\GeneratorCommand;
use Orchestra\Canvas\Core\Concerns\ResolvesPresetStubs;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function Laravel\Prompts\select;
use function Orchestra\Sidekick\Filesystem\join_paths;
use function Orchestra\Testbench\package_path;
/**
* @codeCoverageIgnore
*/
#[AsCommand(name: 'preset', description: 'Create canvas.yaml for the project')]
class PresetMakeCommand extends GeneratorCommand
{
use ResolvesPresetStubs;
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Preset';
/**
* Interact with the user before validating the input.
*/
#[\Override]
protected function interact(InputInterface $input, OutputInterface $output): void
{
if (\is_null($input->getArgument('name'))) {
$input->setArgument('name', select(
label: 'The preset type?',
options: [
'laravel' => 'Application',
'package' => 'Package',
],
required: true,
));
}
if (\is_null($input->getOption('namespace'))) {
$files = new Filesystem;
$composer = $files->json(package_path('composer.json'));
$namespaces = Collection::make(Arr::wrap(data_get($composer, 'autoload.psr-4')))
->keys()
->transform(static fn ($namespace) => rtrim($namespace, '\\'))
->mapWithKeys(static fn ($namespace) => [$namespace => $namespace]);
if ($namespaces->isNotEmpty()) {
if ($namespaces->count() === 1) {
$input->setOption('namespace', $namespaces->first());
} else {
$input->setOption('namespace', select(
label: 'The root namespace for your package?',
options: $namespaces->all(),
required: true,
));
}
}
}
parent::interact($input, $output);
}
/**
* Get the stub file for the generator.
*
* @return string
*/
#[\Override]
protected function getStub()
{
$name = Str::lower($this->getNameInput());
$stub = join_paths(__DIR__, 'stubs', 'preset');
return $this->files->exists("{$stub}.{$name}.stub")
? "{$stub}.{$name}.stub"
: "{$stub}.laravel.stub";
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return join_paths($this->generatorPreset()->basePath(), 'canvas.yaml');
}
/**
* Get the root namespace for the class.
*/
#[\Override]
protected function rootNamespace(): string
{
/** @var string $namespace */
$namespace = transform(
$this->option('namespace'), static fn (string $namespace) => trim($namespace) /** @phpstan-ignore argument.type */
);
if (! empty($namespace)) {
return $namespace;
}
return match ($this->argument('name')) {
'package' => 'PackageName',
/* 'laravel' */
default => rtrim($this->laravel->getNamespace(), '\\'),
};
}
/**
* Get the console command options.
*
* @return array<int, array>
*/
#[\Override]
protected function getOptions()
{
return [
['namespace', null, InputOption::VALUE_OPTIONAL, 'Root namespace'],
];
}
}
@@ -0,0 +1,77 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ProviderMakeCommand.php
*/
#[AsCommand(name: 'make:provider', description: 'Create a new service provider class')]
class ProviderMakeCommand extends \Illuminate\Foundation\Console\ProviderMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
#[\Override]
protected function getDefaultNamespace($rootNamespace)
{
return rtrim($this->generatorPreset()->providerNamespace(), '\\');
}
}
@@ -0,0 +1,51 @@
<?php
namespace Orchestra\Canvas\Console;
use Illuminate\Queue\Console\TableCommand;
use Orchestra\Canvas\Core\Concerns\MigrationGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Queue/Console/TableCommand.php
*/
#[AsCommand(name: 'make:queue-table', description: 'Create a migration for the queue jobs database table', aliases: ['queue:table'])]
class QueueTableCommand extends TableCommand
{
use MigrationGenerator;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Create a base migration file for the table.
*
* @param string $table
* @return string
*/
#[\Override]
protected function createBaseMigration($table)
{
return $this->createBaseMigrationUsingCanvas($table);
}
/**
* Determine whether a migration for the table already exists.
*
* @param string $table
* @return bool
*/
#[\Override]
protected function migrationExists($table)
{
return $this->migrationExistsUsingCanvas($table);
}
}
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/RequestMakeCommand.php
*/
#[AsCommand(name: 'make:request', description: 'Create a new form request class')]
class RequestMakeCommand extends \Illuminate\Foundation\Console\RequestMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ResourceMakeCommand.php
*/
#[AsCommand(name: 'make:resource', description: 'Create a new resource')]
class ResourceMakeCommand extends \Illuminate\Foundation\Console\ResourceMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/RuleMakeCommand.php
*/
#[AsCommand(name: 'make:rule', description: 'Create a new validation rule')]
class RuleMakeCommand extends \Illuminate\Foundation\Console\RuleMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,76 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/ScopeMakeCommand.php
*/
#[AsCommand(name: 'make:scope', description: 'Create a new class')]
class ScopeMakeCommand extends \Illuminate\Foundation\Console\ScopeMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Qualify the given model class base name.
*
* @return string
*/
#[\Override]
protected function qualifyModel(string $model)
{
return $this->qualifyModelUsingCanvas($model);
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
}
@@ -0,0 +1,73 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Database/Console/Seeds/SeederMakeCommand.php
*/
#[AsCommand(name: 'make:seeder', description: 'Create a new seeder class')]
class SeederMakeCommand extends \Illuminate\Database\Console\Seeds\SeederMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Get the generator preset source path.
*/
protected function getGeneratorSourcePath(): string
{
return $this->generatorPreset()->seederPath();
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->generatorPreset()->seederNamespace();
}
}
@@ -0,0 +1,50 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\MigrationGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Session/Console/SessionTableCommand.php
*/
#[AsCommand(name: 'make:session-table', description: 'Create a migration for the session database table', aliases: ['session:table'])]
class SessionTableCommand extends \Illuminate\Session\Console\SessionTableCommand
{
use MigrationGenerator;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Create a base migration file for the table.
*
* @param string $table
* @return string
*/
#[\Override]
protected function createBaseMigration($table)
{
return $this->createBaseMigrationUsingCanvas($table);
}
/**
* Determine whether a migration for the table already exists.
*
* @param string $table
* @return bool
*/
#[\Override]
protected function migrationExists($table)
{
return $this->migrationExistsUsingCanvas($table);
}
}
+154
View File
@@ -0,0 +1,154 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Orchestra\Canvas\GeneratorPreset;
use Symfony\Component\Console\Attribute\AsCommand;
use function Orchestra\Sidekick\Filesystem\join_paths;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Console/TestMakeCommand.php
*/
#[AsCommand(name: 'make:test', description: 'Create a new test class')]
class TestMakeCommand extends \Illuminate\Foundation\Console\TestMakeCommand
{
use CodeGenerator;
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Replace the class name for the given stub.
*
* @param string $stub
* @param string $name
* @return string
*/
protected function generatingCode($stub, $name)
{
$preset = $this->generatorPreset();
if (! $preset instanceof GeneratorPreset) {
return $stub;
}
$testCase = $this->option('unit')
? $preset->canvas()->config('testing.extends.unit', 'PHPUnit\Framework\TestCase')
: $preset->canvas()->config(
'testing.extends.feature',
$preset->canvas()->is('laravel') ? 'Tests\TestCase' : 'Orchestra\Testbench\TestCase'
);
return $this->replaceTestCase($stub, $testCase);
}
/**
* Replace the model for the given stub.
*/
protected function replaceTestCase(string $stub, string $testCase): string
{
$namespaceTestCase = $testCase = str_replace('/', '\\', $testCase);
if (str_starts_with($testCase, '\\')) {
$stub = str_replace('NamespacedDummyTestCase', trim($testCase, '\\'), $stub);
} else {
$stub = str_replace('NamespacedDummyTestCase', $namespaceTestCase, $stub);
}
$stub = str_replace(
"use {$namespaceTestCase};\nuse {$namespaceTestCase};", "use {$namespaceTestCase};", $stub
);
$testCase = class_basename(trim($testCase, '\\'));
return str_replace('DummyTestCase', $testCase, $stub);
}
/**
* Resolve the fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
#[\Override]
protected function resolveStubPath($stub)
{
$preset = $this->generatorPreset();
if (! $preset instanceof GeneratorPreset) {
return parent::resolveStubPath($stub);
}
return $preset->hasCustomStubPath() && file_exists($customPath = join_paths($preset->basePath(), $stub))
? $customPath
: $this->resolveDefaultStubPath($stub);
}
/**
* Resolve the default fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveDefaultStubPath($stub)
{
return join_paths(__DIR__, $stub);
}
/**
* Get the generator preset source path.
*/
protected function getGeneratorSourcePath(): string
{
return $this->generatorPreset()->testingPath();
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
return $this->getPathUsingCanvas($name);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->generatorPreset()->testingNamespace();
}
}
@@ -0,0 +1,117 @@
<?php
namespace Orchestra\Canvas\Console;
use Orchestra\Canvas\Core\Commands\GeneratorCommand;
use Orchestra\Canvas\Core\Concerns\ResolvesPresetStubs;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
use function Orchestra\Sidekick\Filesystem\join_paths;
#[AsCommand(name: 'make:user-factory', description: 'Create the User factory class')]
class UserFactoryMakeCommand extends GeneratorCommand
{
use ResolvesPresetStubs;
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Factory';
/**
* Get the stub file for the generator.
*
* @return string
*/
#[\Override]
protected function getStub()
{
return $this->resolveStubPath(join_paths('stubs', 'user-factory.stub'));
}
/**
* Resolve the default fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveDefaultStubPath($stub)
{
return join_paths(__DIR__, $stub);
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->generatorPreset()->factoryNamespace();
}
/**
* Get the generator preset source path.
*/
protected function getGeneratorSourcePath(): string
{
return $this->generatorPreset()->factoryPath();
}
/**
* Handle generating code.
*/
public function generatingCode(string $stub, string $className): string
{
$preset = $this->generatorPreset();
return str_replace([
'{{ factoryNamespace }}',
'{{ namespacedModel }}',
'{{ model }}',
], [
rtrim($preset->factoryNamespace(), '\\'),
$preset->modelNamespace().'User',
'User',
], $stub);
}
/**
* Get the desired class name from the input.
*
* @return string
*/
#[\Override]
protected function getNameInput()
{
return 'UserFactory';
}
/**
* Get the console command arguments.
*
* @return array
*/
#[\Override]
protected function getArguments()
{
return [];
}
/**
* Get the console command options.
*
* @return array<int, array>
*/
#[\Override]
protected function getOptions()
{
return [
['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the generator already exists'],
];
}
}
@@ -0,0 +1,114 @@
<?php
namespace Orchestra\Canvas\Console;
use Composer\InstalledVersions;
use Orchestra\Canvas\Core\Commands\GeneratorCommand;
use Orchestra\Canvas\Core\Concerns\ResolvesPresetStubs;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
use function Orchestra\Sidekick\Filesystem\join_paths;
#[AsCommand(name: 'make:user-model', description: 'Create the User model class')]
class UserModelMakeCommand extends GeneratorCommand
{
use ResolvesPresetStubs;
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Model';
/**
* Build the class with the given name.
*
* @param string $name
* @return string
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
protected function buildClass($name)
{
$stub = parent::buildClass($name);
if (! InstalledVersions::isInstalled('laravel/sanctum')) {
$stub = str_replace(
' use HasApiTokens, HasFactory, Notifiable;'.PHP_EOL,
' use HasFactory, Notifiable;'.PHP_EOL,
$stub
);
}
return $stub;
}
/**
* Get the stub file for the generator.
*
* @return string
*/
#[\Override]
protected function getStub()
{
return $this->resolveStubPath(join_paths('stubs', 'user-model.stub'));
}
/**
* Resolve the default fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveDefaultStubPath($stub)
{
return join_paths(__DIR__, $stub);
}
/**
* Get the default namespace for the class.
*/
#[\Override]
public function getDefaultNamespace($rootNamespace)
{
return rtrim($this->generatorPreset()->modelNamespace(), '\\');
}
/**
* Get the desired class name from the input.
*
* @return string
*/
#[\Override]
protected function getNameInput()
{
return 'User';
}
/**
* Get the console command arguments.
*
* @return array
*/
#[\Override]
protected function getArguments()
{
return [];
}
/**
* Get the console command options.
*
* @return array<int, array>
*/
#[\Override]
protected function getOptions()
{
return [
['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the generator already exists'],
];
}
}
+206
View File
@@ -0,0 +1,206 @@
<?php
namespace Orchestra\Canvas\Console;
use Illuminate\Support\Str;
use Orchestra\Canvas\Core\Concerns\CodeGenerator;
use Orchestra\Canvas\Core\Concerns\TestGenerator;
use Orchestra\Canvas\Core\Concerns\UsesGeneratorOverrides;
use Orchestra\Canvas\GeneratorPreset;
use Symfony\Component\Console\Attribute\AsCommand;
use function Orchestra\Sidekick\Filesystem\join_paths;
#[AsCommand(name: 'make:view', description: 'Create a new view')]
class ViewMakeCommand extends \Illuminate\Foundation\Console\ViewMakeCommand
{
use CodeGenerator;
use TestGenerator {
handleTestCreationUsingCanvas as protected handleTestCreationUsingCanvasFromTrait;
}
use UsesGeneratorOverrides;
/**
* Configures the current command.
*/
#[\Override]
protected function configure(): void
{
parent::configure();
$this->addGeneratorPresetOptions();
}
/**
* Execute the console command.
*
* @return bool|null
*
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
*/
#[\Override]
public function handle()
{
/** @phpstan-ignore return.type */
return $this->generateCode() ? self::SUCCESS : self::FAILURE;
}
/**
* Resolve the fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
#[\Override]
protected function resolveStubPath($stub)
{
$preset = $this->generatorPreset();
if (! $preset instanceof GeneratorPreset) {
return parent::resolveStubPath($stub);
}
return $preset->hasCustomStubPath() && file_exists($customPath = join_paths($preset->basePath(), $stub))
? $customPath
: $this->resolveDefaultStubPath($stub);
}
/**
* Resolve the default fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveDefaultStubPath($stub)
{
return join_paths(__DIR__, $stub);
}
/**
* Get the destination view path.
*
* @param string $name
* @return string
*/
#[\Override]
protected function getPath($name)
{
/** @var string $extension */
/** @phpstan-ignore argument.type */
$extension = transform($this->option('extension'), fn (string $extension) => trim($extension));
return $this->viewPath(
$this->getNameInput().'.'.$extension,
);
}
/**
* Get the first view directory path from the application configuration.
*
* @param string $path
* @return string
*/
#[\Override]
protected function viewPath($path = '')
{
return $this->viewPathUsingCanvas($path);
}
/**
* Get the desired view name from the input.
*
* @return string
*/
#[\Override]
protected function getNameInput()
{
/** @phpstan-ignore argument.type, return.type */
return transform($this->argument('name'), function (string $name) {
return str_replace(['\\', '.'], '/', trim($name));
});
}
/**
* Create the matching test case if requested.
*
* @param string $path
*/
protected function handleTestCreationUsingCanvas($path): bool
{
if (! $this->option('test') && ! $this->option('pest')) {
return false;
}
$preset = $this->generatorPreset();
if (! $preset instanceof GeneratorPreset) {
return $this->handleTestCreationUsingCanvasFromTrait($path);
}
$namespaceTestCase = $testCase = $preset->canvas()->config(
'testing.extends.feature',
$preset->canvas()->is('laravel') ? 'Tests\TestCase' : 'Orchestra\Testbench\TestCase'
);
$stub = $this->files->get($this->getTestStub());
if (Str::startsWith($testCase, '\\')) {
$stub = str_replace('NamespacedDummyTestCase', trim($testCase, '\\'), $stub);
} else {
$stub = str_replace('NamespacedDummyTestCase', $namespaceTestCase, $stub);
}
$contents = str_replace(
['{{ namespace }}', '{{ class }}', '{{ name }}', 'DummyTestCase'],
[$this->testNamespace(), $this->testClassName(), $this->testViewName(), class_basename(trim($testCase, '\\'))],
$stub,
);
$this->files->ensureDirectoryExists(\dirname($this->getTestPath()), 0755, true);
return $this->files->put($this->getTestPath(), $contents) !== false;
}
/**
* Get the root namespace for the class.
*
* @return string
*/
#[\Override]
protected function rootNamespace()
{
return $this->rootNamespaceUsingCanvas();
}
/**
* Get the destination test case path.
*
* @return string
*/
#[\Override]
protected function getTestPath()
{
$preset = $this->generatorPreset();
$testPath = Str::of($this->testClassFullyQualifiedName())
->replace('\\', DIRECTORY_SEPARATOR)
->replaceFirst(join_paths('Tests', 'Feature'), join_paths(str_replace($preset->basePath(), '', $preset->testingPath()), 'Feature'))
->append('Test.php')
->value();
return $preset->basePath().$testPath;
}
/**
* Get the test stub file for the generator.
*
* @return string
*/
#[\Override]
protected function getTestStub()
{
$stubName = 'view.'.($this->option('pest') ? 'pest' : 'test').'.stub';
return $this->resolveStubPath(join_paths('stubs', $stubName));
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace DummyNamespace;
class DummyClass
{
//
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace {{ factoryNamespace }};
use {{ namespacedModel }};
use Illuminate\Database\Eloquent\Factories\Factory;
use {{ namespacedModel }};
/**
* @extends Factory<{{ model }}>
*/
class {{ factory }}Factory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var class-string<{{ model }}>
*/
protected $model = {{ model }}::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}
@@ -0,0 +1,46 @@
<?php
namespace DummyNamespace;
use Orchestra\Canvas\Core\Commands\GeneratorCommand;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'dummy:command', description: 'Create a new class')]
class DummyClass extends GeneratorCommand
{
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Class';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
// ...
}
/**
* Resolve the default fully-qualified path to the stub.
*
* @param string $stub
* @return string
*/
protected function resolveDefaultStubPath($stub)
{
return __DIR__.$stub;
}
/**
* Get the default namespace for the class.
*/
public function getDefaultNamespace($rootNamespace)
{
return $rootNamespace;
}
}
+7
View File
@@ -0,0 +1,7 @@
<?php
test('example', function () {
$response = $this->get('/');
$response->assertStatus(200);
});
@@ -0,0 +1,5 @@
<?php
test('example', function () {
expect(true)->toBeTrue();
});
@@ -0,0 +1,7 @@
preset: laravel
namespace: DummyNamespace
model:
namespace: DummyNamespace
@@ -0,0 +1,27 @@
preset: package
namespace: DummyNamespace
user-auth-model: App\Models\User
paths:
src: src
resource: resources
factory:
path: database/factories
migration:
path: database/migrations
prefix: ''
console:
namespace: DummyNamespace\Console
model:
namespace: DummyNamespace\Models
provider:
namespace: DummyNamespace\Providers
testing:
namespace: DummyNamespace\Tests
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace {{ namespace }};
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use NamespacedDummyTestCase;
class {{ class }} extends DummyTestCase
{
/**
* A basic feature test example.
*/
public function test_example(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
@@ -0,0 +1,16 @@
<?php
namespace {{ namespace }};
use NamespacedDummyTestCase;
class {{ class }} extends DummyTestCase
{
/**
* A basic unit test example.
*/
public function test_example(): void
{
$this->assertTrue(true);
}
}
@@ -0,0 +1,52 @@
<?php
namespace {{ factoryNamespace }};
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use {{ namespacedModel }};
/**
* @extends Factory<User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* The name of the factory's corresponding model.
*
* @var class-string<{{ model }}>
*/
protected $model = {{ model }}::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}
@@ -0,0 +1,32 @@
<?php
namespace {{ namespace }};
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
#[Fillable(['name', 'email', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}
@@ -0,0 +1,9 @@
<?php
it('can render', function () {
$contents = $this->view('{{ name }}', [
//
]);
$contents->assertSee('');
});
+3
View File
@@ -0,0 +1,3 @@
<div>
<!-- {{ quote }} -->
</div>
@@ -0,0 +1,23 @@
<?php
namespace {{ namespace }};
use NamespacedDummyTestCase;
use Illuminate\Foundation\Testing\Concerns\InteractsWithViews;
class {{ class }} extends DummyTestCase
{
use InteractsWithViews;
/**
* A basic view test example.
*/
public function test_it_can_render(): void
{
$contents = $this->view('{{ name }}', [
//
]);
$contents->assertSee('');
}
}
+167
View File
@@ -0,0 +1,167 @@
<?php
namespace Orchestra\Canvas;
use Orchestra\Canvas\Core\Presets\Preset;
use function Orchestra\Sidekick\join_paths;
class GeneratorPreset extends Preset
{
/**
* Preset name.
*/
public function name(): string
{
return 'canvas';
}
/**
* Get the path to the base working directory.
*/
public function basePath(): string
{
return $this->canvas()->basePath();
}
/**
* Get the path to the source directory.
*/
public function sourcePath(): string
{
return $this->canvas()->sourcePath();
}
/**
* Get the path to the testing directory.
*/
public function testingPath(): string
{
return $this->canvas()->testingPath();
}
/**
* Get the path to the resource directory.
*/
public function resourcePath(): string
{
return $this->canvas()->resourcePath();
}
/**
* Get the path to the view directory.
*/
public function viewPath(): string
{
return join_paths($this->resourcePath(), 'views');
}
/**
* Get the path to the factory directory.
*/
public function factoryPath(): string
{
return $this->canvas()->factoryPath();
}
/**
* Get the path to the migration directory.
*/
public function migrationPath(): string
{
return $this->canvas()->migrationPath();
}
/**
* Get the path to the seeder directory.
*/
public function seederPath(): string
{
return $this->canvas()->seederPath();
}
/**
* Preset namespace.
*/
public function rootNamespace(): string
{
return $this->canvas()->rootNamespace().'\\';
}
/**
* Command namespace.
*/
public function commandNamespace(): string
{
return $this->canvas()->commandNamespace().'\\';
}
/**
* Model namespace.
*/
public function modelNamespace(): string
{
return $this->canvas()->modelNamespace().'\\';
}
/**
* Provider namespace.
*/
public function providerNamespace(): string
{
return $this->canvas()->providerNamespace().'\\';
}
/**
* Database factory namespace.
*/
public function factoryNamespace(): string
{
return $this->canvas()->factoryNamespace().'\\';
}
/**
* Database seeder namespace.
*/
public function seederNamespace(): string
{
return $this->canvas()->seederNamespace().'\\';
}
/**
* Testing namespace.
*/
public function testingNamespace(): string
{
return $this->canvas()->testingNamespace().'\\';
}
/**
* Preset has custom stub path.
*/
public function hasCustomStubPath(): bool
{
return ! \is_null($this->canvas()->getCustomStubPath());
}
/** {@inheritDoc} */
#[\Override]
public function userProviderModel(?string $guard = null): ?string
{
if (\is_null($guard) || $guard === $this->app->make('config')->get('auth.defaults.guard')) {
return $this->canvas()->config('user-auth-model')
?? $this->canvas()->config('user-auth-provider')
?? parent::userProviderModel($guard);
}
return parent::userProviderModel($guard);
}
/**
* Get canvas preset.
*/
public function canvas(): Presets\Preset
{
return $this->app->make('orchestra.canvas');
}
}
+596
View File
@@ -0,0 +1,596 @@
<?php
namespace Orchestra\Canvas;
use Illuminate\Cache\Console\CacheTableCommand;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Database\Console\Factories\FactoryMakeCommand;
use Illuminate\Database\Console\Seeds\SeederMakeCommand;
use Illuminate\Foundation\Console\CastMakeCommand;
use Illuminate\Foundation\Console\ChannelMakeCommand;
use Illuminate\Foundation\Console\ClassMakeCommand;
use Illuminate\Foundation\Console\ComponentMakeCommand;
use Illuminate\Foundation\Console\ConsoleMakeCommand;
use Illuminate\Foundation\Console\EnumMakeCommand;
use Illuminate\Foundation\Console\EventMakeCommand;
use Illuminate\Foundation\Console\ExceptionMakeCommand;
use Illuminate\Foundation\Console\InterfaceMakeCommand;
use Illuminate\Foundation\Console\JobMakeCommand;
use Illuminate\Foundation\Console\JobMiddlewareMakeCommand;
use Illuminate\Foundation\Console\ListenerMakeCommand;
use Illuminate\Foundation\Console\MailMakeCommand;
use Illuminate\Foundation\Console\ModelMakeCommand;
use Illuminate\Foundation\Console\NotificationMakeCommand;
use Illuminate\Foundation\Console\ObserverMakeCommand;
use Illuminate\Foundation\Console\PolicyMakeCommand;
use Illuminate\Foundation\Console\ProviderMakeCommand;
use Illuminate\Foundation\Console\RequestMakeCommand;
use Illuminate\Foundation\Console\ResourceMakeCommand;
use Illuminate\Foundation\Console\RuleMakeCommand;
use Illuminate\Foundation\Console\ScopeMakeCommand;
use Illuminate\Foundation\Console\TestMakeCommand;
use Illuminate\Foundation\Console\ViewMakeCommand;
use Illuminate\Notifications\Console\NotificationTableCommand;
use Illuminate\Queue\Console\BatchesTableCommand;
use Illuminate\Queue\Console\FailedTableCommand;
use Illuminate\Queue\Console\TableCommand as QueueTableCommand;
use Illuminate\Routing\Console\ControllerMakeCommand;
use Illuminate\Routing\Console\MiddlewareMakeCommand;
use Illuminate\Session\Console\SessionTableCommand;
use Illuminate\Support\ServiceProvider;
/**
* @see https://github.com/laravel/framework/blob/11.x/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php
*/
class LaravelServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*/
public function boot(): void
{
$this->registerCastMakeCommand();
$this->registerChannelMakeCommand();
$this->registerClassMakeCommand();
$this->registerComponentMakeCommand();
$this->registerConsoleMakeCommand();
$this->registerControllerMakeCommand();
$this->registerEnumMakeCommand();
$this->registerEventMakeCommand();
$this->registerExceptionMakeCommand();
$this->registerFactoryMakeCommand();
$this->registerJobMakeCommand();
$this->registerJobMiddlewareMakeCommand();
$this->registerInterfaceMakeCommand();
$this->registerListenerMakeCommand();
$this->registerMailMakeCommand();
$this->registerMiddlewareMakeCommand();
$this->registerMigrateMakeCommand();
$this->registerModelMakeCommand();
$this->registerNotificationMakeCommand();
$this->registerObserverMakeCommand();
$this->registerPolicyMakeCommand();
$this->registerProviderMakeCommand();
$this->registerRequestMakeCommand();
$this->registerResourceMakeCommand();
$this->registerRuleMakeCommand();
$this->registerScopeMakeCommand();
$this->registerSeederMakeCommand();
$this->registerTestMakeCommand();
$this->registerViewMakeCommand();
$this->registerCacheTableCommand();
$this->registerNotificationTableCommand();
$this->registerQueueBatchesTableCommand();
$this->registerQueueFailedTableCommand();
$this->registerQueueTableCommand();
$this->registerSessionTableCommand();
$this->registerUserFactoryMakeCommand();
$this->registerUserModelMakeCommand();
$this->commands([
Console\CastMakeCommand::class,
Console\ChannelMakeCommand::class,
Console\ComponentMakeCommand::class,
Console\ConsoleMakeCommand::class,
Console\ClassMakeCommand::class,
Console\ControllerMakeCommand::class,
Console\EnumMakeCommand::class,
Console\EventMakeCommand::class,
Console\ExceptionMakeCommand::class,
Console\FactoryMakeCommand::class,
Console\JobMakeCommand::class,
Console\InterfaceMakeCommand::class,
Console\ListenerMakeCommand::class,
Console\MailMakeCommand::class,
Console\MiddlewareMakeCommand::class,
Console\MigrateMakeCommand::class,
Console\ModelMakeCommand::class,
Console\NotificationMakeCommand::class,
Console\ObserverMakeCommand::class,
Console\PolicyMakeCommand::class,
Console\ProviderMakeCommand::class,
Console\RequestMakeCommand::class,
Console\ResourceMakeCommand::class,
Console\RuleMakeCommand::class,
Console\ScopeMakeCommand::class,
Console\SeederMakeCommand::class,
Console\TestMakeCommand::class,
Console\ViewMakeCommand::class,
Console\BatchesTableCommand::class,
Console\CacheTableCommand::class,
Console\FailedTableCommand::class,
Console\NotificationTableCommand::class,
Console\QueueTableCommand::class,
Console\SessionTableCommand::class,
Console\UserFactoryMakeCommand::class,
Console\UserModelMakeCommand::class,
]);
}
/**
* Register the command.
*/
protected function registerCastMakeCommand(): void
{
$this->app->singleton(
CastMakeCommand::class, static fn ($app) => new Console\CastMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerChannelMakeCommand(): void
{
$this->app->singleton(
ChannelMakeCommand::class, static fn ($app) => new Console\ChannelMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerClassMakeCommand(): void
{
$this->app->singleton(
ClassMakeCommand::class, static fn ($app) => new Console\ClassMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerComponentMakeCommand(): void
{
$this->app->singleton(
ComponentMakeCommand::class, static fn ($app) => new Console\ComponentMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerConsoleMakeCommand(): void
{
$this->app->singleton(
ConsoleMakeCommand::class, static fn ($app) => new Console\ConsoleMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerControllerMakeCommand(): void
{
$this->app->singleton(
ControllerMakeCommand::class, static fn ($app) => new Console\ControllerMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerEnumMakeCommand(): void
{
$this->app->singleton(
EnumMakeCommand::class, fn ($app) => new Console\EnumMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerEventMakeCommand(): void
{
$this->app->singleton(
EventMakeCommand::class, static fn ($app) => new Console\EventMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerExceptionMakeCommand(): void
{
$this->app->singleton(
ExceptionMakeCommand::class, static fn ($app) => new Console\ExceptionMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerFactoryMakeCommand(): void
{
$this->app->singleton(
FactoryMakeCommand::class, static fn ($app) => new Console\FactoryMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerJobMakeCommand(): void
{
$this->app->singleton(
JobMakeCommand::class, static fn ($app) => new Console\JobMakeCommand($app['files'])
);
}
/**
* Register the command.
*
* @return void
*/
protected function registerJobMiddlewareMakeCommand()
{
$this->app->singleton(
JobMiddlewareMakeCommand::class, static fn ($app) => new Console\JobMiddlewareMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerInterfaceMakeCommand(): void
{
$this->app->singleton(
InterfaceMakeCommand::class, static fn ($app) => new Console\InterfaceMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerListenerMakeCommand(): void
{
$this->app->singleton(
ListenerMakeCommand::class, static fn ($app) => new Console\ListenerMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerMailMakeCommand(): void
{
$this->app->singleton(
MailMakeCommand::class, static fn ($app) => new Console\MailMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerMiddlewareMakeCommand(): void
{
$this->app->singleton(
MiddlewareMakeCommand::class, static fn ($app) => new Console\MiddlewareMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerMigrateMakeCommand(): void
{
$this->app->singleton(Console\MigrateMakeCommand::class, static function ($app) {
// Once we have the migration creator registered, we will create the command
// and inject the creator. The creator is responsible for the actual file
// creation of the migrations, and may be extended by these developers.
$creator = $app['migration.creator'];
$composer = $app['composer'];
return new Console\MigrateMakeCommand($creator, $composer);
});
}
/**
* Register the command.
*/
protected function registerModelMakeCommand(): void
{
$this->app->singleton(
ModelMakeCommand::class, static fn ($app) => new Console\ModelMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerNotificationMakeCommand(): void
{
$this->app->singleton(
NotificationMakeCommand::class, static fn ($app) => new Console\NotificationMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerObserverMakeCommand(): void
{
$this->app->singleton(
ObserverMakeCommand::class, static fn ($app) => new Console\ObserverMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerPolicyMakeCommand(): void
{
$this->app->singleton(
PolicyMakeCommand::class, static fn ($app) => new Console\PolicyMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerProviderMakeCommand(): void
{
$this->app->singleton(
ProviderMakeCommand::class, static fn ($app) => new Console\ProviderMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerRequestMakeCommand(): void
{
$this->app->singleton(
RequestMakeCommand::class, static fn ($app) => new Console\RequestMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerResourceMakeCommand(): void
{
$this->app->singleton(
ResourceMakeCommand::class, static fn ($app) => new Console\ResourceMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerRuleMakeCommand(): void
{
$this->app->singleton(
RuleMakeCommand::class, static fn ($app) => new Console\RuleMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerScopeMakeCommand(): void
{
$this->app->singleton(
ScopeMakeCommand::class, static fn ($app) => new Console\ScopeMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerSeederMakeCommand(): void
{
$this->app->singleton(
SeederMakeCommand::class, static fn ($app) => new Console\SeederMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerTestMakeCommand(): void
{
$this->app->singleton(
TestMakeCommand::class, static fn ($app) => new Console\TestMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerViewMakeCommand(): void
{
$this->app->singleton(
ViewMakeCommand::class, static fn ($app) => new Console\ViewMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerCacheTableCommand(): void
{
$this->app->singleton(
CacheTableCommand::class, static fn ($app) => new Console\CacheTableCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerNotificationTableCommand(): void
{
$this->app->singleton(
NotificationTableCommand::class, static fn ($app) => new Console\NotificationTableCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerQueueBatchesTableCommand(): void
{
$this->app->singleton(
BatchesTableCommand::class, static fn ($app) => new Console\BatchesTableCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerQueueFailedTableCommand(): void
{
$this->app->singleton(
FailedTableCommand::class, static fn ($app) => new Console\FailedTableCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerQueueTableCommand(): void
{
$this->app->singleton(
QueueTableCommand::class, static fn ($app) => new Console\QueueTableCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerSessionTableCommand(): void
{
$this->app->singleton(
SessionTableCommand::class, static fn ($app) => new Console\SessionTableCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerUserFactoryMakeCommand(): void
{
$this->app->singleton(
Console\UserFactoryMakeCommand::class, static fn ($app) => new Console\UserFactoryMakeCommand($app['files'])
);
}
/**
* Register the command.
*/
protected function registerUserModelMakeCommand(): void
{
$this->app->singleton(
Console\UserModelMakeCommand::class, static fn ($app) => new Console\UserModelMakeCommand($app['files'])
);
}
/**
* Get the services provided by the provider.
*
* @return array<int, class-string>
*/
public function provides()
{
return [
CastMakeCommand::class,
Console\CastMakeCommand::class,
ChannelMakeCommand::class,
Console\ChannelMakeCommand::class,
ClassMakeCommand::class,
Console\ClassMakeCommand::class,
ComponentMakeCommand::class,
Console\ComponentMakeCommand::class,
ConsoleMakeCommand::class,
Console\ConsoleMakeCommand::class,
ControllerMakeCommand::class,
Console\ControllerMakeCommand::class,
EnumMakeCommand::class,
Console\EnumMakeCommand::class,
EventMakeCommand::class,
Console\EventMakeCommand::class,
ExceptionMakeCommand::class,
Console\ExceptionMakeCommand::class,
FactoryMakeCommand::class,
Console\FactoryMakeCommand::class,
InterfaceMakeCommand::class,
Console\InterfaceMakeCommand::class,
JobMakeCommand::class,
Console\JobMakeCommand::class,
JobMiddlewareMakeCommand::class,
Console\JobMiddlewareMakeCommand::class,
ListenerMakeCommand::class,
Console\ListenerMakeCommand::class,
MailMakeCommand::class,
Console\MailMakeCommand::class,
MiddlewareMakeCommand::class,
Console\MiddlewareMakeCommand::class,
Console\MigrateMakeCommand::class,
ModelMakeCommand::class,
Console\ModelMakeCommand::class,
NotificationMakeCommand::class,
Console\NotificationMakeCommand::class,
ObserverMakeCommand::class,
Console\ObserverMakeCommand::class,
PolicyMakeCommand::class,
Console\PolicyMakeCommand::class,
ProviderMakeCommand::class,
Console\ProviderMakeCommand::class,
RequestMakeCommand::class,
Console\RequestMakeCommand::class,
ResourceMakeCommand::class,
Console\ResourceMakeCommand::class,
RuleMakeCommand::class,
Console\RuleMakeCommand::class,
ScopeMakeCommand::class,
Console\ScopeMakeCommand::class,
SeederMakeCommand::class,
Console\SeederMakeCommand::class,
TestMakeCommand::class,
Console\TestMakeCommand::class,
ViewMakeCommand::class,
Console\ViewMakeCommand::class,
BatchesTableCommand::class,
Console\BatchesTableCommand::class,
CacheTableCommand::class,
Console\CacheTableCommand::class,
FailedTableCommand::class,
Console\FailedTableCommand::class,
NotificationTableCommand::class,
Console\NotificationTableCommand::class,
QueueTableCommand::class,
Console\QueueTableCommand::class,
SessionTableCommand::class,
Console\SessionTableCommand::class,
Console\UserFactoryMakeCommand::class,
Console\UserModelMakeCommand::class,
];
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace Orchestra\Canvas\Presets;
use function Orchestra\Sidekick\join_paths;
class Laravel extends Preset
{
/**
* Preset name.
*/
public function name(): string
{
return 'laravel';
}
/**
* Get the path to the source directory.
*/
public function sourcePath(): string
{
return join_paths($this->basePath(), $this->config('paths.src', 'app'));
}
/**
* Preset namespace.
*/
public function rootNamespace(): string
{
return $this->config['namespace'] ?? 'App';
}
/**
* Command namespace.
*/
public function commandNamespace(): string
{
return $this->config('console.namespace', $this->rootNamespace().'\Console\Commands');
}
/**
* Model namespace.
*/
public function modelNamespace(): string
{
return $this->config('model.namespace', $this->rootNamespace().'\Models');
}
/**
* Provider namespace.
*/
public function providerNamespace(): string
{
return $this->config('provider.namespace', $this->rootNamespace().'\Providers');
}
/**
* Testing namespace.
*/
public function testingNamespace(): string
{
return $this->config('testing.namespace', 'Tests');
}
/**
* Get custom stub path.
*/
public function getCustomStubPath(): ?string
{
return join_paths($this->basePath(), 'stubs');
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace Orchestra\Canvas\Presets;
use InvalidArgumentException;
use function Orchestra\Sidekick\join_paths;
class Package extends Preset
{
/**
* Preset name.
*/
public function name(): string
{
return 'package';
}
/**
* Get the path to the source directory.
*/
public function sourcePath(): string
{
return join_paths(
$this->basePath(),
$this->config('paths.src', 'src')
);
}
/**
* Preset namespace.
*/
public function rootNamespace(): string
{
$namespace = trim($this->config['namespace'] ?? '');
if (empty($namespace)) {
throw new InvalidArgumentException("Please configure namespace configuration under 'canvas.yaml'");
}
return $namespace;
}
/**
* Command namespace.
*/
public function commandNamespace(): string
{
return $this->config('console.namespace', $this->rootNamespace().'\Console');
}
/**
* Model namespace.
*/
public function modelNamespace(): string
{
return $this->config('model.namespace', $this->rootNamespace());
}
/**
* Provider namespace.
*/
public function providerNamespace(): string
{
return $this->config('provider.namespace', $this->rootNamespace());
}
/**
* Testing namespace.
*/
public function testingNamespace(): string
{
return $this->config('testing.namespace', $this->rootNamespace().'\Tests');
}
/**
* Get custom stub path.
*/
public function getCustomStubPath(): ?string
{
return null;
}
}
+160
View File
@@ -0,0 +1,160 @@
<?php
namespace Orchestra\Canvas\Presets;
use Illuminate\Support\Arr;
use function Orchestra\Sidekick\join_paths;
abstract class Preset
{
/**
* Construct a new preset.
*
* @param array<string, mixed> $config
*/
public function __construct(
protected array $config,
protected string $basePath
) {
//
}
/**
* Check if preset name equal to $name.
*/
public function is(string $name): bool
{
return $this->name() === $name;
}
/**
* Get configuration.
*
* @param mixed|null $default
*/
public function config(?string $key = null, $default = null): mixed
{
if (\is_null($key)) {
return $this->config;
}
return Arr::get($this->config, $key, $default);
}
/**
* Get the path to the base working directory.
*/
public function basePath(): string
{
return $this->basePath;
}
/**
* Get the path to the testing directory.
*/
public function testingPath(): string
{
return join_paths($this->basePath, 'tests');
}
/**
* Get the path to the resource directory.
*/
public function resourcePath(): string
{
return join_paths(
$this->basePath(),
$this->config('paths.resource', 'resources')
);
}
/**
* Get the path to the factory directory.
*/
public function factoryPath(): string
{
return join_paths(
$this->basePath(),
$this->config('factory.path', join_paths('database', 'factories'))
);
}
/**
* Get the path to the migration directory.
*/
public function migrationPath(): string
{
return join_paths(
$this->basePath(),
$this->config('migration.path', join_paths('database', 'migrations'))
);
}
/**
* Get the path to the seeder directory.
*/
public function seederPath(): string
{
return join_paths(
$this->basePath(),
$this->config('seeder.path', join_paths('database', 'seeders'))
);
}
/**
* Database factory namespace.
*/
public function factoryNamespace(): string
{
return $this->config('factory.namespace', 'Database\Factories');
}
/**
* Database seeder namespace.
*/
public function seederNamespace(): string
{
return $this->config('seeder.namespace', 'Database\Seeders');
}
/**
* Preset name.
*/
abstract public function name(): string;
/**
* Get the path to the source directory.
*/
abstract public function sourcePath(): string;
/**
* Preset namespace.
*/
abstract public function rootNamespace(): string;
/**
* Command namespace.
*/
abstract public function commandNamespace(): string;
/**
* Model namespace.
*/
abstract public function modelNamespace(): string;
/**
* Provider namespace.
*/
abstract public function providerNamespace(): string;
/**
* Testing namespace.
*/
abstract public function testingNamespace(): string;
/**
* Get custom stub path.
*/
abstract public function getCustomStubPath(): ?string;
}
+66
View File
@@ -0,0 +1,66 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "orchestra/sidekick",
"description": "Packages Toolkit Utilities and Helpers for Laravel",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Mior Muhammad Zaki",
"email": "crynobone@gmail.com"
}
],
"autoload": {
"psr-4": {
"Orchestra\\Sidekick\\": "src/"
},
"files": [
"src/Eloquent/functions.php",
"src/Filesystem/functions.php",
"src/Http/functions.php",
"src/functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Orchestra\\Sidekick\\Tests\\": "tests/",
"App\\": "workbench/app/",
"Database\\Factories\\": "workbench/database/factories/"
}
},
"require": {
"php": "^8.1",
"composer-runtime-api": "^2.2",
"composer/semver": "^3.0",
"symfony/polyfill-php83": "^1.32"
},
"require-dev": {
"fakerphp/faker": "^1.21",
"laravel/framework": "^10.48.29|^11.44.7|^12.1.1|^13.0",
"laravel/pint": "^1.4",
"mockery/mockery": "^1.5.1",
"orchestra/testbench-core": "^8.37.0|^9.14.0|^10.2.0|^11.0",
"phpstan/phpstan": "^2.1.14",
"phpunit/phpunit": "^10.0|^11.0|^12.0",
"symfony/process": "^6.0|^7.0"
},
"config": {
"audit": {
"block-insecure": false
},
"sort-packages": true
},
"scripts": {
"lint": [
"@php vendor/bin/pint",
"@php vendor/bin/phpstan analyse --verbose"
],
"test": "@php vendor/bin/phpunit",
"ci": [
"@lint",
"@test"
]
},
"minimum-stability": "dev",
"prefer-stable": true
}
@@ -0,0 +1,59 @@
<?php
namespace Orchestra\Sidekick\Eloquent\Concerns;
use function Orchestra\Sidekick\laravel_version_compare;
/**
* Polyfill for Eloquent Model to get previous attributes.
*
* @see https://github.com/laravel/framework/pull/55729
*
* @codeCoverageIgnore
*/
if (laravel_version_compare('12.15.0', '>=')) {
trait HasPreviousAttributes
{
// ...
}
} else {
trait HasPreviousAttributes
{
/**
* The previous state of the changed model attributes.
*
* @var array<string, mixed>
*/
protected $previous = [];
/** {@inheritDoc} */
#[\Override]
public function syncChanges()
{
parent::syncChanges();
$this->previous = array_intersect_key($this->getRawOriginal(), $this->changes);
return $this;
}
/** {@inheritDoc} */
#[\Override]
public function discardChanges()
{
$this->previous = [];
return parent::discardChanges();
}
/**
* Get the attributes that were previously original before the model was last saved.
*
* @return array<string, mixed>
*/
public function getPrevious()
{
return $this->previous;
}
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace Orchestra\Sidekick\Eloquent;
use Illuminate\Database\Eloquent\Model;
use WeakMap;
/**
* @internal
*/
class Watcher
{
/**
* The cache instance.
*
* @var \WeakMap<\Illuminate\Database\Eloquent\Model, array<string, mixed>>|null
*/
protected static ?WeakMap $cache = null;
/**
* Get the watcher store.
*
* @return \WeakMap<\Illuminate\Database\Eloquent\Model, array<string, mixed>>
*/
public static function store(): WeakMap
{
/** @phpstan-ignore assign.propertyType,return.type */
return static::$cache ??= new WeakMap;
}
/**
* Submit a model snapshot.
*
* @return array<string, mixed>
*/
public static function snapshot(Model $model): ?array
{
$original = $model->getRawOriginal();
$response = match (true) {
$model->isDirty() => $original,
isset(static::store()[$model]) => static::store()[$model],
default => null,
};
static::store()[$model] = $original;
return $response;
}
/**
* Flush the instance states.
*/
public static function flushState(): void
{
static::$cache = null;
}
}
+303
View File
@@ -0,0 +1,303 @@
<?php
namespace Orchestra\Sidekick\Eloquent;
use BackedEnum;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\AsPivot;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Support\Arr;
use InvalidArgumentException;
use JsonSerializable;
use Orchestra\Sidekick\SensitiveValue;
use Stringable;
use Throwable;
if (! \function_exists('Orchestra\Sidekick\Eloquent\column_name')) {
/**
* Get qualify column name from Eloquent model.
*
* @api
*
* @param \Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model> $model
*
* @throws \InvalidArgumentException
*/
function column_name(Model|string $model, string $attribute): string
{
if (\is_string($model)) {
$model = new $model;
}
if (! $model instanceof Model) {
throw new InvalidArgumentException(\sprintf('Given $model is not an instance of [%s].', Model::class));
}
return $model->qualifyColumn($attribute);
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\is_pivot_model')) {
/**
* Determine if the given model is a pivot model.
*
* @api
*
* @template TPivotModel of (\Illuminate\Database\Eloquent\Model&\Illuminate\Database\Eloquent\Relations\Concerns\AsPivot)|\Illuminate\Database\Eloquent\Relations\Pivot
*
* @param TPivotModel|class-string<TPivotModel> $model
*
* @throws \InvalidArgumentException
*/
function is_pivot_model(Pivot|Model|string $model): bool
{
if (\is_string($model)) {
$model = new $model;
}
if (! $model instanceof Model) {
throw new InvalidArgumentException(\sprintf('Given $model is not an instance of [%s|%s].', Model::class, Pivot::class));
}
if ($model instanceof Pivot) {
return true;
}
return \in_array(AsPivot::class, class_uses_recursive($model), true);
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\model_exists')) {
/**
* Check whether given $model exists.
*
* @api
*/
function model_exists(mixed $model): bool
{
return $model instanceof Model && $model->exists === true;
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\model_from')) {
/**
* Check whether given $model exists.
*
* @api
*
* @param \Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model> $model
* @param array<string, mixed> $attributes
*/
function model_from(Model|string $model, array $attributes): Model
{
return $model::unguarded(function () use ($model, $attributes) {
if (\is_string($model)) {
$model = new $model;
}
return $model->newInstance()->setRawAttributes($attributes);
});
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\model_key_type')) {
/**
* Check whether given $model key type.
*
* @api
*
* @param \Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model> $model
*
* @throws \InvalidArgumentException
*/
function model_key_type(Model|string $model): string
{
if (\is_string($model)) {
$model = new $model;
}
if (! $model instanceof Model) {
throw new InvalidArgumentException(\sprintf('Given $model is not an instance of [%s].', Model::class));
}
$uses = class_uses_recursive($model);
if (\in_array(HasUlids::class, $uses, true)) {
return 'ulid';
} elseif (\in_array(HasUuids::class, $uses, true)) {
return 'uuid';
}
return $model->getKeyType();
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\model_diff')) {
/**
* Get attributes diff state from a model.
*
* @api
*
* @param array<int, string> $excludes
* @return array<string, mixed>
*/
function model_diff(Model $model, array $excludes = [], bool $withTimestamps = true): array
{
$hiddens = $model->getHidden();
$excludedAttributes = array_merge($excludes, $model->getAppends());
$timestamps = [$model->getCreatedAtColumn(), $model->getUpdatedAtColumn()];
if (! model_exists($model) || $model->wasRecentlyCreated === true) {
$rawChanges = $model->getAttributes();
$changes = model_from($model, $rawChanges)->setHidden($excludedAttributes)->attributesToArray();
return Arr::except(
summarize_changes($changes, hiddens: $hiddens),
$withTimestamps === false ? $timestamps : [$model->getUpdatedAtColumn()]
);
}
$rawChanges = $model->isDirty() ? $model->getDirty() : $model->getChanges();
$changes = array_intersect_key(
model_from($model, $rawChanges)->setHidden($excludedAttributes)->setAppends([])->attributesToArray(),
$rawChanges
);
return Arr::except(
summarize_changes($changes, hiddens: $hiddens),
$withTimestamps === false ? $timestamps : []
);
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\model_snapshot')) {
/**
* Store a snapshot for a model and return the original attributes.
*
* @api
*
* @return array<string, mixed>|null
*/
function model_snapshot(Model $model): ?array
{
return Watcher::snapshot($model);
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\model_state')) {
/**
* Get attributes original and changed state from a model.
*
* @api
*
* @param array<int, string> $excludes
* @return array{0: array<string, mixed>|null, 1: array<string, mixed>}
*/
function model_state(Model $model, array $excludes = [], bool $withTimestamps = true): array
{
$changes = model_diff($model, $excludes, $withTimestamps);
$excludedAttributes = array_merge($excludes, $model->getAppends());
if (! model_exists($model) || $model->wasRecentlyCreated == true) {
return [null, $changes];
}
/** @phpstan-ignore function.alreadyNarrowedType */
$previous = method_exists($model, 'getPrevious') ? $model->getPrevious() : null;
if (empty($previous)) {
$previous = Watcher::snapshot($model);
}
$original = summarize_changes(
array_intersect_key(model_from($model, $previous ?? [])->setHidden($excludedAttributes)->setAppends([])->attributesToArray(), $changes),
hiddens: $model->getHidden(),
);
return [$original, $changes];
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\normalize_value')) {
/**
* Normalize the given value to be store to database as scalar.
*
* @api
*
* @return scalar
*/
function normalize_value(mixed $value): mixed
{
$value = match (true) {
$value instanceof CarbonInterface => $value->toISOString(),
$value instanceof JsonSerializable => $value->jsonSerialize(),
$value instanceof BackedEnum => $value->value,
default => $value,
};
if (\is_object($value) && $value instanceof Stringable) {
return (string) $value;
} elseif (\is_object($value) || \is_array($value)) {
try {
return json_encode($value);
} catch (Throwable $e) { // @phpstan-ignore catch.neverThrown
return $value;
}
}
return $value;
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\summarize_changes')) {
/**
* Get table name from Eloquent model.
*
* @api
*
* @param array<string, mixed> $changes
* @param array<int, string> $hiddens
* @return array<string, \Orchestra\Sidekick\SensitiveValue<mixed>|scalar>
*/
function summarize_changes(array $changes, array $hiddens = []): array
{
$summaries = [];
foreach ($changes as $attribute => $value) {
$summaries[$attribute] = \in_array($attribute, $hiddens, true)
? new SensitiveValue($value)
: normalize_value($value);
}
return $summaries;
}
}
if (! \function_exists('Orchestra\Sidekick\Eloquent\table_name')) {
/**
* Get table name from Eloquent model.
*
* @api
*
* @param \Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model> $model
*
* @throws \InvalidArgumentException
*/
function table_name(Model|string $model): string
{
if (\is_string($model)) {
$model = new $model;
}
if (! $model instanceof Model) {
throw new InvalidArgumentException(\sprintf('Given $model is not an instance of [%s].', Model::class));
}
return $model->getTable();
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
namespace Orchestra\Sidekick;
/**
* @api
*/
class Env extends \Illuminate\Support\Env
{
/**
* Determine if environmemt variable is available.
*/
public static function has(string $key): bool
{
return static::get($key, new UndefinedValue) instanceof UndefinedValue === false;
}
/**
* Set an environment value.
*/
public static function set(string $key, string $value): void
{
static::getRepository()->set($key, $value);
}
/**
* Forget an environment variable.
*
*
* @throws \InvalidArgumentException
*/
public static function forget(string $key): bool
{
return static::getRepository()->clear($key);
}
/**
* Forward environment value.
*
* @param \Orchestra\Sidekick\UndefinedValue|mixed|null $default
* @return mixed
*/
public static function forward(string $key, $default = new UndefinedValue)
{
$value = static::get($key, $default);
if ($value instanceof UndefinedValue) {
return false;
}
return static::encode($value);
}
/**
* Encode environment variable value.
*
* @param mixed $value
* @return mixed
*/
public static function encode($value)
{
if (\is_null($value)) {
return '(null)';
}
if (\is_bool($value)) {
return $value === true ? '(true)' : '(false)';
}
if (empty($value)) {
return '(empty)';
}
return $value;
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace Orchestra\Sidekick\Filesystem;
use ReflectionClass;
if (! \function_exists('Orchestra\Sidekick\Filesystem\filename_from_classname')) {
/**
* Resolve filename from classname.
*
* @api
*
* @param class-string $className
*/
function filename_from_classname(string $className): string|false
{
if (! class_exists($className, false)) {
return false;
}
$classFileName = (new ReflectionClass($className))->getFileName();
if (
$classFileName === false
|| (! is_file($classFileName) && ! str_ends_with(strtolower($classFileName), '.php'))
) {
return false;
}
return realpath($classFileName);
}
}
if (! \function_exists('Orchestra\Sidekick\Filesystem\is_symlink')) {
/**
* Determine if the path is a symlink for both Unix and Windows environments.
*
* @api
*/
function is_symlink(string $path): bool
{
if (\Orchestra\Sidekick\windows_os() && is_dir($path) && readlink($path) !== $path) {
return true;
} elseif (is_link($path)) {
return true;
}
return false;
}
}
if (! \function_exists('Orchestra\Sidekick\Filesystem\join_paths')) {
/**
* Join the given paths together.
*
* @api
*/
function join_paths(?string $basePath, string ...$paths): string
{
foreach ($paths as $index => $path) {
if (empty($path) && $path !== '0') {
unset($paths[$index]);
} else {
$paths[$index] = DIRECTORY_SEPARATOR.ltrim($path, DIRECTORY_SEPARATOR);
}
}
return $basePath.implode('', $paths);
}
}
+176
View File
@@ -0,0 +1,176 @@
<?php
namespace Orchestra\Sidekick;
use ArrayAccess;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Support\Fluent;
use Illuminate\Support\Traits\ForwardsCalls;
use JsonSerializable;
/**
* @api
*
* @template TKey of array-key
* @template TValue
*
* @implements \Illuminate\Contracts\Support\Arrayable<TKey, TValue>
* @implements \ArrayAccess<TKey, TValue>
*/
abstract class FluentDecorator implements Arrayable, ArrayAccess, Jsonable, JsonSerializable
{
use ForwardsCalls;
/**
* The Fluent instance.
*
* @var \Illuminate\Support\Fluent<TKey, TValue>
*/
protected Fluent $fluent;
/**
* Create a new fluent instance.
*
* @param iterable<TKey, TValue> $attributes
*/
public function __construct($attributes = [])
{
$this->fluent = new Fluent($attributes);
}
/**
* Convert the fluent instance to an array.
*
* @return array<TKey, TValue>
*/
public function toArray()
{
return $this->fluent->getAttributes();
}
/**
* Convert the object into something JSON serializable.
*
* @return array<TKey, TValue>
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* Convert the fluent instance to JSON.
*
* @param int $options
* @return string
*/
public function toJson($options = 0)
{
return (string) json_encode($this->jsonSerialize(), $options);
}
/**
* Determine if the given offset exists.
*
* @param TKey $offset
*/
public function offsetExists($offset): bool
{
return $this->fluent->offsetExists($offset);
}
/**
* Get the value for a given offset.
*
* @param TKey $offset
* @return TValue|null
*/
public function offsetGet($offset): mixed
{
return $this->fluent->offsetGet($offset);
}
/**
* Set the value at the given offset.
*
* @param TKey $offset
* @param TValue $value
*/
public function offsetSet($offset, $value): void
{
$this->fluent->offsetSet($offset, $value);
}
/**
* Unset the value at the given offset.
*
* @param TKey $offset
*/
public function offsetUnset($offset): void
{
$this->fluent->offsetUnset($offset);
}
/**
* Handle dynamic calls to the fluent instance to set attributes.
*
* @param TKey $method
* @param array{0: ?TValue} $parameters
* @return $this
*/
public function __call($method, $parameters)
{
return $this->forwardDecoratedCallTo($this->fluent, $method, $parameters);
}
/**
* Dynamically retrieve the value of an attribute.
*
* @param TKey $key
* @return TValue|null
*/
public function __get($key)
{
/** @phpstan-ignore function.alreadyNarrowedType */
if (method_exists($this->fluent, 'value')) {
return $this->fluent->value($key);
}
return $this->fluent->get($key);
}
/**
* Dynamically set the value of an attribute.
*
* @param TKey $key
* @param TValue $value
* @return void
*/
public function __set($key, $value)
{
$this->fluent->offsetSet($key, $value);
}
/**
* Dynamically check if an attribute is set.
*
* @param TKey $key
* @return bool
*/
public function __isset($key)
{
return $this->fluent->offsetExists($key);
}
/**
* Dynamically unset an attribute.
*
* @param TKey $key
* @return void
*/
public function __unset($key)
{
$this->fluent->offsetUnset($key);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Orchestra\Sidekick\Http;
if (! \function_exists('Orchestra\Sidekick\Http\safe_int')) {
/**
* Convert large id higher than Number.MAX_SAFE_INTEGER to string.
*
* https://stackoverflow.com/questions/47188449/json-max-int-number/47188576
*
* @api
*/
function safe_int(mixed $value): mixed
{
$jsonMaxInt = 9007199254740991;
if (\is_int($value) && abs($value) >= $jsonMaxInt) {
return (string) $value;
} elseif (filter_var($value, FILTER_VALIDATE_INT) && abs($value) < $jsonMaxInt) {
return (int) $value;
}
return $value;
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Orchestra\Sidekick;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\PhpExecutableFinder as SymfonyPhpExecutableFinder;
/**
* @internal
*/
class PhpExecutableFinder extends SymfonyPhpExecutableFinder
{
/**
* {@inheritDoc}
*
* @codeCoverageIgnore
*/
#[\Override]
public function find(bool $includeArgs = true): string|false
{
if ($herdPath = getenv('HERD_HOME')) {
return (new ExecutableFinder)->find(name: 'php', extraDirs: [join_paths($herdPath, 'bin')]) ?? false;
}
return parent::find($includeArgs);
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace Orchestra\Sidekick;
use JsonSerializable;
use Stringable;
/**
* @api
*
* @template TValue of mixed
*/
final class SensitiveValue implements JsonSerializable, Stringable
{
/**
* Construct a new sensitive value.
*
* @param TValue $value
*/
public function __construct(
private readonly mixed $value
) {
//
}
/**
* Get the original value.
*
* @return TValue
*/
public function getValue(): mixed
{
return $this->value;
}
/**
* Transform the value for debugging.
*/
public function __debugInfo(): array
{
return [];
}
/**
* Get the value for string serialization.
*/
public function __toString(): string
{
return $this->jsonSerialize();
}
/**
* Get the value for JSON serialization.
*/
public function jsonSerialize(): string
{
return '******';
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Orchestra\Sidekick;
use JsonSerializable;
/**
* @api
*/
final class UndefinedValue implements JsonSerializable
{
/**
* Determine if value is equivalent to "undefined" or "null".
*
* @param mixed $value
*
* @phpstan-assert-if-true \Orchestra\Sidekick\UndefinedValue|null $value
*/
public static function equalsTo($value): bool
{
return $value instanceof UndefinedValue || \is_null($value);
}
/**
* Get the value for JSON serialization.
*
* @return null
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return null;
}
}
+415
View File
@@ -0,0 +1,415 @@
<?php
namespace Orchestra\Sidekick;
use BackedEnum;
use Closure;
use Composer\InstalledVersions;
use Composer\Semver\VersionParser;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;
use Illuminate\Foundation\Application;
use Illuminate\Support\Arr;
use OutOfBoundsException;
use PHPUnit\Runner\Version;
use RuntimeException;
use UnitEnum;
if (! \function_exists('Orchestra\Sidekick\enum_name')) {
/**
* Get the proper name from enum.
*
* @api
*
* @throws \RuntimeException
*/
function enum_name(BackedEnum|UnitEnum $enum): string
{
return mb_convert_case(str_replace('_', ' ', $enum->name), MB_CASE_TITLE, 'UTF-8');
}
}
if (! \function_exists('Orchestra\Sidekick\enum_value')) {
/**
* Get the proper name from enum.
*
* @api
*
* @template TValue
* @template TDefault
*
* @param TValue $value
* @param TDefault|callable(TValue): TDefault $default
* @return ($value is empty ? TDefault : mixed)
*
* @throws \RuntimeException
*/
function enum_value(mixed $value, mixed $default = null): mixed
{
return match (true) {
$value instanceof BackedEnum => $value->value,
$value instanceof UnitEnum => $value->name,
default => $value ?? value($default),
};
}
}
if (! \function_exists('Orchestra\Sidekick\after_resolving')) {
/**
* Register after resolving callback.
*
* @api
*
* @template TLaravel of \Illuminate\Contracts\Foundation\Application
*
* @param TLaravel $app
* @param class-string|string $name
* @param (\Closure(object, TLaravel):(mixed))|null $callback
*/
function after_resolving(ApplicationContract $app, string $name, ?Closure $callback = null): void
{
$app->afterResolving($name, $callback);
if ($app->resolved($name)) {
value($callback, $app->make($name), $app);
}
}
}
if (! \function_exists('Orchestra\Sidekick\join_paths')) {
/**
* Join the given paths together.
*
* @api
*
* @deprecated
*/
function join_paths(?string $basePath, string ...$paths): string
{
return Filesystem\join_paths($basePath, ...$paths);
}
}
if (! \function_exists('Orchestra\Sidekick\once')) {
/**
* Run callback only once.
*
* @api
*
* @param mixed $callback
* @return \Closure():mixed
*/
function once($callback): Closure
{
$response = new UndefinedValue;
return function () use ($callback, &$response) {
if ($response instanceof UndefinedValue) {
$response = value($callback) ?? null;
}
return $response;
};
}
}
if (! \function_exists('Orchestra\Sidekick\is_safe_callable')) {
/**
* Determine if the value is a callable and not a string matching an available function name.
*
* @api
*/
function is_safe_callable(mixed $value): bool
{
if ($value instanceof Closure) {
return true;
}
if (! \is_callable($value)) {
return false;
}
if (\is_array($value)) {
return \count($value) === 2 && array_is_list($value) && method_exists(...$value);
}
return ! \is_string($value);
}
}
if (! \function_exists('Orchestra\Sidekick\is_symlink')) {
/**
* Determine if the path is a symlink for both Unix and Windows environments.
*
* @api
*
* @deprecated
*/
function is_symlink(string $path): bool
{
return Filesystem\is_symlink($path);
}
}
if (! \function_exists('Orchestra\Sidekick\is_testbench_cli')) {
/**
* Determine if command executed via Testbench CLI.
*
* @api
*/
function is_testbench_cli(?bool $dusk = null): bool
{
$usingTestbench = \defined('TESTBENCH_CORE');
$usingTestbenchDusk = \defined('TESTBENCH_DUSK');
return match ($dusk) {
false => $usingTestbench === true && $usingTestbenchDusk === false,
true => $usingTestbench === true && $usingTestbenchDusk === true,
default => $usingTestbench === true,
};
}
}
if (! \function_exists('Orchestra\Sidekick\transform_relative_path')) {
/**
* Transform relative path.
*
* @api
*/
function transform_relative_path(string $path, string $workingPath): string
{
return str_starts_with($path, './')
? rtrim($workingPath, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.mb_substr($path, 2)
: $path;
}
}
if (! \function_exists('Orchestra\Sidekick\package_path')) {
/**
* Get the package path.
*
* @api
*
* @no-named-arguments
*
* @param array<int, string|null>|string ...$path
*/
function package_path(array|string $path = ''): string
{
$packagePath = once(function () {
$workingPath = realpath(match (true) {
\defined('TESTBENCH_WORKING_PATH') => TESTBENCH_WORKING_PATH,
Env::has('TESTBENCH_WORKING_PATH') => Env::get('TESTBENCH_WORKING_PATH'),
default => InstalledVersions::getRootPackage()['install_path'],
});
return $workingPath !== false ? $workingPath : getcwd();
});
return join_paths($packagePath(), ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path));
}
}
if (! \function_exists('Orchestra\Sidekick\working_path')) {
/**
* Get the working path.
*
* @api
*
* @no-named-arguments
*
* @param array<int, string|null>|string ...$path
*/
function working_path(array|string $path = ''): string
{
return is_testbench_cli()
? package_path($path)
: base_path(join_paths(...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path)));
}
}
if (! \function_exists('Orchestra\Sidekick\laravel_normalize_version')) {
/**
* Laravel normalize version.
*
* @api
*
* @throws \OutOfBoundsException
*/
function laravel_normalize_version(): string
{
if (! class_exists(Application::class)) {
throw new OutOfBoundsException('Unable to verify "laravel/framework" version');
}
/** @var string $version */
$version = transform(
Application::VERSION,
fn (string $version) => match ($version) {
'13.x-dev' => '13.0.0',
default => $version,
}
);
return (new VersionParser)->normalize($version);
}
}
if (! \function_exists('Orchestra\Sidekick\phpunit_normalize_version')) {
/**
* PHPUnit normalize version.
*
* @api
*
* @throws \OutOfBoundsException
*/
function phpunit_normalize_version(): string
{
if (! class_exists(Version::class)) {
throw new OutOfBoundsException('Unable to verify "phpunit/phpunit" version');
}
/** @var string $version */
$version = transform(
Version::id(),
fn (string $version) => match (true) {
str_starts_with($version, '13.0-') => '13.0.0',
default => $version,
}
);
return (new VersionParser)->normalize($version);
}
}
if (! \function_exists('Orchestra\Sidekick\laravel_version_compare')) {
/**
* Laravel version compare.
*
* @api
*
* @template TOperator of string|null
*
* @param TOperator $operator
* @return (TOperator is null ? int : bool)
*
* @throws \RuntimeException
*
* @codeCoverageIgnore
*/
function laravel_version_compare(string $version, ?string $operator = null): int|bool
{
if (! class_exists(Application::class)) {
return package_version_compare('laravel/framework', $version, $operator);
}
$laravel = laravel_normalize_version();
$version = (new VersionParser)->normalize($version);
if (\is_null($operator)) {
return version_compare($laravel, $version);
}
return version_compare($laravel, $version, $operator);
}
}
if (! \function_exists('Orchestra\Sidekick\package_version_compare')) {
/**
* Package version compare.
*
* @api
*
* @template TOperator of string|null
*
* @phpstan-param TOperator $operator
*
* @phpstan-return (TOperator is null ? int : bool)
*
* @throws \OutOfBoundsException
* @throws \RuntimeException
*
* @codeCoverageIgnore
*/
function package_version_compare(string $package, string $version, ?string $operator = null): int|bool
{
$prettyVersion = InstalledVersions::getPrettyVersion($package);
if (\is_null($prettyVersion)) {
throw new RuntimeException(\sprintf('Unable to compare "%s" version', $package));
}
$versionParser = new VersionParser;
$package = $versionParser->normalize($prettyVersion);
$version = $versionParser->normalize($version);
if (\is_null($operator)) {
return version_compare($package, $version);
}
return version_compare($package, $version, $operator);
}
}
if (! \function_exists('Orchestra\Sidekick\phpunit_version_compare')) {
/**
* PHPUnit version compare.
*
* @api
*
* @template TOperator of string|null
*
* @param TOperator $operator
* @return (TOperator is null ? int : bool)
*
* @throws \OutOfBoundsException
* @throws \RuntimeException
*
* @codeCoverageIgnore
*/
function phpunit_version_compare(string $version, ?string $operator = null): int|bool
{
if (! class_exists(Version::class)) {
return package_version_compare('phpunit/phpunit', $version, $operator);
}
$phpunit = phpunit_normalize_version();
$version = (new VersionParser)->normalize($version);
if (\is_null($operator)) {
return version_compare($phpunit, $version);
}
return version_compare($phpunit, $version, $operator);
}
}
if (! \function_exists('Orchestra\Sidekick\php_binary')) {
/**
* Determine the PHP Binary.
*
* @api
*
* @codeCoverageIgnore
*/
function php_binary(): string
{
return (new PhpExecutableFinder)->find(false) ?: 'php';
}
}
if (! \function_exists('Orchestra\Sidekick\windows_os')) {
/**
* Determine whether the current environment is Windows-based.
*
* @api
*
* @codeCoverageIgnore
*/
function windows_os(): bool
{
return PHP_OS_FAMILY === 'Windows';
}
}
+127
View File
@@ -0,0 +1,127 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "orchestra/testbench-core",
"description": "Testing Helper for Laravel Development",
"homepage": "https://packages.tools/testbench",
"keywords": ["laravel", "laravel-packages", "testing", "tdd", "bdd", "dev"],
"license": "MIT",
"support": {
"issues": "https://github.com/orchestral/testbench/issues",
"source": "https://github.com/orchestral/testbench-core"
},
"authors": [
{
"name": "Mior Muhammad Zaki",
"email": "crynobone@gmail.com",
"homepage": "https://github.com/crynobone"
}
],
"autoload": {
"psr-4": {
"Orchestra\\Testbench\\": "src/"
},
"files": [
"src/functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Orchestra\\Testbench\\Tests\\": "tests/",
"Workbench\\App\\": "workbench/app/",
"Workbench\\Database\\Factories\\": "workbench/database/factories/",
"Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
}
},
"bin": [
"testbench"
],
"require": {
"php": "^8.3",
"composer-runtime-api": "^2.2",
"orchestra/sidekick": "~1.1.23|~1.2.20",
"symfony/deprecation-contracts": "^2.5|^3.0",
"symfony/polyfill-php84": "^1.34.0"
},
"require-dev": {
"fakerphp/faker": "^1.24",
"laravel/framework": "^13.9.0",
"laravel/pint": "^1.24",
"laravel/serializable-closure": "^2.0.10",
"mockery/mockery": "^1.6.10",
"phpstan/phpstan": "^2.1.38",
"phpunit/phpunit": "^11.5.50|^12.5.8|^13.0.0",
"spatie/laravel-ray": "^1.43.6",
"symfony/process": "^7.4.5|^8.0.5",
"symfony/yaml": "^7.4.0|^8.0.0",
"vlucas/phpdotenv": "^5.6.1"
},
"conflict": {
"brianium/paratest": "<7.3.0|>=8.0.0",
"laravel/framework": "<13.9.0|>=14.0.0",
"laravel/serializable-closure": ">=2.0.0 <2.0.10|>=3.0.0",
"nunomaduro/collision": "<8.9.0|>=9.0.0",
"phpunit/phpunit": "<11.5.50|>=12.0.0 <12.5.8|>=13.2.0"
},
"suggest": {
"ext-pcntl": "Required to use all features of the console signal trapping.",
"brianium/paratest": "Allow using parallel testing (^7.3).",
"fakerphp/faker": "Allow using Faker for testing (^1.23).",
"laravel/framework": "Required for testing (^13.9.0).",
"mockery/mockery": "Allow using Mockery for testing (^1.6).",
"nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^8.9).",
"orchestra/testbench-dusk": "Allow using Laravel Dusk for testing (^11.0).",
"phpunit/phpunit": "Allow using PHPUnit for testing (^11.5.50|^12.5.8|^13.0.0).",
"symfony/process": "Required to use Orchestra\\Testbench\\remote function (^7.4|^8.0).",
"symfony/yaml": "Required for Testbench CLI (^7.4|^8.0).",
"vlucas/phpdotenv": "Required for Testbench CLI (^5.6.1)."
},
"config": {
"audit": {
"ignore": []
},
"preferred-install": {
"laravel/framework": "source",
"*": "auto"
},
"sort-packages": true
},
"scripts": {
"post-autoload-dump": [
"@clear",
"@prepare"
],
"clear": "@php testbench package:purge-skeleton --ansi",
"prepare": "@php testbench package:discover --ansi",
"serve": [
"Composer\\Config::disableProcessTimeout",
"@clear",
"@putenv PHP_CLI_SERVER_WORKERS=5",
"@php testbench serve --ansi"
],
"lint": [
"@php vendor/bin/pint --ansi",
"@php vendor/bin/phpstan analyse --verbose"
],
"test": [
"@php vendor/bin/phpunit --no-coverage --no-configuration --bootstrap vendor/autoload.php --exclude-group phpunit-configuration --color tests"
],
"sync": [
"@clear",
"@php bin/sync",
"@lint"
],
"sync-dev": [
"@clear",
"@php bin/sync --dev",
"@lint"
],
"ci": [
"@composer audit",
"@post-autoload-dump",
"@lint",
"@test"
]
},
"prefer-stable": true,
"minimum-stability": "dev"
}
+65
View File
@@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=AckfSECXIvnK5r28GVIWUAxmbBSjTsmF
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
@@ -0,0 +1,6 @@
.env
.env.*
!.env.example
testbench.yaml
testbench.yaml.backup

Some files were not shown because too many files have changed in this diff Show More