diff --git a/src/Phaseolies/Console/Commands/MakeAuthCommand.php b/src/Phaseolies/Console/Commands/MakeAuthCommand.php index 46ebcb3c..d6525170 100644 --- a/src/Phaseolies/Console/Commands/MakeAuthCommand.php +++ b/src/Phaseolies/Console/Commands/MakeAuthCommand.php @@ -216,7 +216,7 @@ protected function listExistingAuthFiles(): void $this->line("{$category}:"); foreach ($paths as $path) { if (file_exists($path)) { - $this->line('- ' . str_replace(base_path(), '', $path)); + $this->line('- ' . $this->relativePath($path)); } } $this->newLine(); diff --git a/src/Phaseolies/Console/Commands/MakeAuthorizerCommand.php b/src/Phaseolies/Console/Commands/MakeAuthorizerCommand.php index 18fe7940..add18ecf 100644 --- a/src/Phaseolies/Console/Commands/MakeAuthorizerCommand.php +++ b/src/Phaseolies/Console/Commands/MakeAuthorizerCommand.php @@ -28,18 +28,15 @@ class MakeAuthorizerCommand extends Command public function handle(): int { return $this->executeWithTiming(function() { - $name = $this->argument('name'); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $model = $this->option('model') ?? $this->option('m'); - $parts = explode('/', $name); - $className = array_pop($parts); - $namespace = 'App\\Authorizers' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Authorizers/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Authorizers', $name); if (file_exists($filePath)) { $this->displayError('Authorizer already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -52,7 +49,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Authorizer created successfully'); - $this->line('🛡️ File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('🛡️ File: ' . $this->relativePath($filePath) . ''); $this->newLine(); return Command::SUCCESS; diff --git a/src/Phaseolies/Console/Commands/MakeConsoleCommand.php b/src/Phaseolies/Console/Commands/MakeConsoleCommand.php index 642e9c68..01266a37 100644 --- a/src/Phaseolies/Console/Commands/MakeConsoleCommand.php +++ b/src/Phaseolies/Console/Commands/MakeConsoleCommand.php @@ -28,15 +28,13 @@ class MakeConsoleCommand extends Command public function handle(): int { return $this->executeWithTiming(function() { - $name = $this->argument('name'); - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $namespace = 'App\\Schedule\\Commands' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Schedule/Commands/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Schedule/Commands', $name); if (file_exists($filePath)) { $this->displayError('Command already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -49,7 +47,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Command created successfully'); - $this->line('📁 File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('📁 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('📌 Command Name: doppar:' . $this->convertToKebabCase($className) . ''); diff --git a/src/Phaseolies/Console/Commands/MakeControllerCommand.php b/src/Phaseolies/Console/Commands/MakeControllerCommand.php index dc278f35..f8b47720 100644 --- a/src/Phaseolies/Console/Commands/MakeControllerCommand.php +++ b/src/Phaseolies/Console/Commands/MakeControllerCommand.php @@ -42,7 +42,7 @@ public function handle(): int if (file_exists($filePath)) { $this->displayError('Controller already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -176,7 +176,10 @@ protected function extractSubNamespace(string $namespace): string */ protected function parseFlags(): array { - $name = str()->suffixAppend($this->argument('name'), 'Controller'); + $name = str()->suffixAppend( + $this->normalizeGeneratedName((string) $this->argument('name')), + 'Controller' + ); $routeName = strtolower(str()->removeSuffix($name, 'Controller')); $isInvokable = $this->option('invokable'); $isResource = $this->option('bundle'); @@ -193,14 +196,14 @@ protected function parseFlags(): array */ protected function resolveNamespacesAndPaths(string $name, bool $isApi): array { - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName($name); $baseNamespace = 'App\\Http\\Controllers' . ($isApi ? '\\API' : ''); $namespace = $baseNamespace . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Http/Controllers/' . - ($isApi ? 'API/' : '') . - str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath( + 'app/Http/Controllers' . ($isApi ? '/API' : ''), + $name + ); return [$namespace, $filePath, $className]; } @@ -230,7 +233,11 @@ protected function generateLayout(string $namespace, string $className, string $ { $layoutStub = $this->getLayoutStub('complete.stub'); $layoutContent = $this->replacePlaceholders($layoutStub, $namespace, $className, $routeName); - $layoutDir = base_path('resources/views/' . $routeName); + $layoutDir = base_path(str_replace( + ['/', '\\'], + DIRECTORY_SEPARATOR, + 'resources/views/' . $this->normalizeGeneratedName($routeName) + )); $this->createDirIfMissing($layoutDir); $layoutPath = $layoutDir . '/default.odo.php'; $this->writeFile($layoutPath, $layoutContent); @@ -242,7 +249,7 @@ protected function generateLayout(string $namespace, string $className, string $ */ protected function outputFilePath(string $label, string $path): void { - $this->line('' . $label . ': ' . str_replace(base_path('/'), '', $path) . ''); + $this->line('' . $label . ': ' . $this->relativePath($path) . ''); } /** diff --git a/src/Phaseolies/Console/Commands/MakeHookCommand.php b/src/Phaseolies/Console/Commands/MakeHookCommand.php index c134b7f4..72bb8d5d 100644 --- a/src/Phaseolies/Console/Commands/MakeHookCommand.php +++ b/src/Phaseolies/Console/Commands/MakeHookCommand.php @@ -28,16 +28,14 @@ class MakeHookCommand extends Command public function handle(): int { return $this->executeWithTiming(function() { - $name = $this->argument('name'); - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $namespace = 'App\\Hooks' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Hooks/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Hooks', $name); // Check if hook already exists if (file_exists($filePath)) { $this->displayError('Hook already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -52,7 +50,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Hook created successfully'); - $this->line('📁 File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('📁 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('📌 Hook Class: ' . $className . ''); @@ -88,4 +86,4 @@ public function handle(Model \$model): void EOT; } -} \ No newline at end of file +} diff --git a/src/Phaseolies/Console/Commands/MakeMailCommand.php b/src/Phaseolies/Console/Commands/MakeMailCommand.php index a19eb22f..a95bb680 100644 --- a/src/Phaseolies/Console/Commands/MakeMailCommand.php +++ b/src/Phaseolies/Console/Commands/MakeMailCommand.php @@ -28,9 +28,7 @@ class MakeMailCommand extends Command public function handle(): int { return $this->executeWithTiming(function() { - $name = $this->argument('name'); - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); // Ensure class name ends with Mail if (!str_ends_with($className, 'Mail')) { @@ -38,12 +36,12 @@ public function handle(): int } $namespace = 'App\\Mail' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Mail/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Mail', $name); // Check if Mailable already exists if (file_exists($filePath)) { $this->displayError('Mailable already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -58,7 +56,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Mailable created successfully'); - $this->line('📧 File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('📧 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('✉️ Class: ' . $className . ''); diff --git a/src/Phaseolies/Console/Commands/MakeMiddlewareCommand.php b/src/Phaseolies/Console/Commands/MakeMiddlewareCommand.php index 66cf4e42..86f2ca58 100644 --- a/src/Phaseolies/Console/Commands/MakeMiddlewareCommand.php +++ b/src/Phaseolies/Console/Commands/MakeMiddlewareCommand.php @@ -28,16 +28,14 @@ class MakeMiddlewareCommand extends Command public function handle(): int { return $this->executeWithTiming(function() { - $name = $this->argument('name'); - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $namespace = 'App\\Http\\Middleware' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Http/Middleware/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Http/Middleware', $name); // Check if middleware already exists if (file_exists($filePath)) { $this->displayError('Middleware already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -52,7 +50,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Middleware created successfully'); - $this->line('🛡️ File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('🛡️ File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('🔒 Class: ' . $className . ''); @@ -92,4 +90,4 @@ public function __invoke(Request \$request, Closure \$next): Response EOT; } -} \ No newline at end of file +} diff --git a/src/Phaseolies/Console/Commands/MakeModelCommand.php b/src/Phaseolies/Console/Commands/MakeModelCommand.php index c79f214d..8ee5bf63 100644 --- a/src/Phaseolies/Console/Commands/MakeModelCommand.php +++ b/src/Phaseolies/Console/Commands/MakeModelCommand.php @@ -48,17 +48,15 @@ public function __construct(MigrationCreator $creator) public function handle(): int { return $this->executeWithTiming(function() { - $name = $this->argument('name'); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $withMigration = $this->option('m'); - $parts = explode('/', $name); - $className = array_pop($parts); $namespace = 'App\\Models' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Models/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Models', $name); // Check if model already exists if (file_exists($filePath)) { $this->displayError('Model already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -73,7 +71,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Model created successfully'); - $this->line('📦 File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('📦 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('📌 Class: ' . $className . ''); @@ -86,7 +84,7 @@ public function handle(): int $this->newLine(); $this->line(' MIGRATION Created migration:'); $this->newLine(); - $this->line('' . str_replace(base_path('/'), '', $migrationFile) . ''); + $this->line('' . $this->relativePath($migrationFile) . ''); } return Command::SUCCESS; @@ -133,4 +131,4 @@ protected function getMigrationPath(): string { return base_path('database/migrations'); } -} \ No newline at end of file +} diff --git a/src/Phaseolies/Console/Commands/MakeProviderCommand.php b/src/Phaseolies/Console/Commands/MakeProviderCommand.php index f4643049..42d3d2e4 100644 --- a/src/Phaseolies/Console/Commands/MakeProviderCommand.php +++ b/src/Phaseolies/Console/Commands/MakeProviderCommand.php @@ -27,15 +27,15 @@ class MakeProviderCommand extends Command */ public function handle(): int { - return $this->executeWithTiming(function() { - $name = $this->argument('name'); - $namespace = 'App\\Providers'; - $filePath = base_path('app/Providers/' . $name . '.php'); + return $this->executeWithTiming(function () { + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); + $namespace = 'App\\Providers' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); + $filePath = $this->generatedFilePath('app/Providers', $name); // Check if provider already exists if (file_exists($filePath)) { $this->displayError('Provider already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -46,13 +46,13 @@ public function handle(): int } // Generate and save provider class - $content = $this->generateProviderContent($namespace, $name); + $content = $this->generateProviderContent($namespace, $className); file_put_contents($filePath, $content); $this->displaySuccess('Provider created successfully'); - $this->line('📦 File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('📦 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); - $this->line('📌 Class: ' . $name . ''); + $this->line('📌 Class: ' . $className . ''); return Command::SUCCESS; }); @@ -69,6 +69,7 @@ protected function generateProviderContent(string $namespace, string $className) namespace {$namespace}; use Phaseolies\Providers\ServiceProvider; +// use Phaseolies\Providers\GhostableProvider; class {$className} extends ServiceProvider { diff --git a/src/Phaseolies/Console/Commands/MakeRequestCommand.php b/src/Phaseolies/Console/Commands/MakeRequestCommand.php index 8432bba3..024ea32d 100644 --- a/src/Phaseolies/Console/Commands/MakeRequestCommand.php +++ b/src/Phaseolies/Console/Commands/MakeRequestCommand.php @@ -28,9 +28,7 @@ class MakeRequestCommand extends Command public function handle(): int { return $this->executeWithTiming(function() { - $name = $this->argument('name'); - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); // Ensure class name ends with Request if (!str_ends_with($className, 'Request')) { @@ -38,12 +36,12 @@ public function handle(): int } $namespace = 'App\\Http\\Validations' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Http/Validations/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Http/Validations', $name); // Check if request already exists if (file_exists($filePath)) { $this->displayError('Request already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -58,7 +56,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Request created successfully'); - $this->line('📁 File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('📁 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('📌 Class: ' . $className . ''); diff --git a/src/Phaseolies/Console/Commands/MakeRuleCommand.php b/src/Phaseolies/Console/Commands/MakeRuleCommand.php index ccce222d..a498329f 100644 --- a/src/Phaseolies/Console/Commands/MakeRuleCommand.php +++ b/src/Phaseolies/Console/Commands/MakeRuleCommand.php @@ -28,16 +28,14 @@ class MakeRuleCommand extends Command public function handle(): int { return $this->executeWithTiming(function () { - $name = $this->argument('name'); - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $namespace = 'App\\Http\\Validations\\Rules' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Http/Validations/Rules/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Http/Validations/Rules', $name); // Check if rule already exists if (file_exists($filePath)) { $this->displayError('Rule already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -52,7 +50,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Rule created successfully'); - $this->line('🛡️ File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('🛡️ File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('🔒 Class: ' . $className . ''); diff --git a/src/Phaseolies/Console/Commands/MakeWatcherCommand.php b/src/Phaseolies/Console/Commands/MakeWatcherCommand.php index f1b2579c..ef382bff 100644 --- a/src/Phaseolies/Console/Commands/MakeWatcherCommand.php +++ b/src/Phaseolies/Console/Commands/MakeWatcherCommand.php @@ -28,15 +28,13 @@ class MakeWatcherCommand extends Command public function handle(): int { return $this->executeWithTiming(function () { - $name = $this->argument('name'); - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $namespace = 'App\\Watchers' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Watchers/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Watchers', $name); if (file_exists($filePath)) { $this->displayError('Watcher already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -48,7 +46,7 @@ public function handle(): int file_put_contents($filePath, $this->generateWatcherContent($namespace, $className)); $this->displaySuccess('Watcher created successfully'); - $this->line('👁️ File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('👁️ File: ' . $this->relativePath($filePath) . ''); $this->newLine(); $this->line('📌 Class: ' . $className . ''); diff --git a/src/Phaseolies/Console/Commands/Presenter/BundleCommand.php b/src/Phaseolies/Console/Commands/Presenter/BundleCommand.php index 1c0fb3bc..51812a46 100644 --- a/src/Phaseolies/Console/Commands/Presenter/BundleCommand.php +++ b/src/Phaseolies/Console/Commands/Presenter/BundleCommand.php @@ -28,16 +28,13 @@ class BundleCommand extends Command public function handle(): int { return $this->executeWithTiming(function () { - $name = $this->argument('name'); - - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $namespace = 'App\\Http\\Presenters' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Http/Presenters/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Http/Presenters', $name); if (file_exists($filePath)) { $this->displayError('Bundle class already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -50,7 +47,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess("Bundle class created successfully"); - $this->line('📁 File: ' . str_replace(base_path(), '', $filePath) . ''); + $this->line('📁 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); return Command::SUCCESS; diff --git a/src/Phaseolies/Console/Commands/Presenter/PresenterCommand.php b/src/Phaseolies/Console/Commands/Presenter/PresenterCommand.php index 4a4419bd..873e874b 100644 --- a/src/Phaseolies/Console/Commands/Presenter/PresenterCommand.php +++ b/src/Phaseolies/Console/Commands/Presenter/PresenterCommand.php @@ -28,16 +28,13 @@ class PresenterCommand extends Command public function handle(): int { return $this->executeWithTiming(function () { - $name = $this->argument('name'); - - $parts = explode('/', $name); - $className = array_pop($parts); + [$name, $parts, $className] = $this->splitGeneratedName((string) $this->argument('name')); $namespace = 'App\\Http\\Presenters' . (count($parts) > 0 ? '\\' . implode('\\', $parts) : ''); - $filePath = base_path('app/Http/Presenters/' . str_replace('/', DIRECTORY_SEPARATOR, $name) . '.php'); + $filePath = $this->generatedFilePath('app/Http/Presenters', $name); if (file_exists($filePath)) { $this->displayError('Presenters already exists at:'); - $this->line('' . str_replace(base_path(), '', $filePath) . ''); + $this->line('' . $this->relativePath($filePath) . ''); return Command::FAILURE; } @@ -50,7 +47,7 @@ public function handle(): int file_put_contents($filePath, $content); $this->displaySuccess('Presenters created successfully'); - $this->line('📁 File: ' . str_replace(base_path('/'), '', $filePath) . ''); + $this->line('📁 File: ' . $this->relativePath($filePath) . ''); $this->newLine(); return Command::SUCCESS; diff --git a/src/Phaseolies/Console/Commands/ViewCacheCommand.php b/src/Phaseolies/Console/Commands/ViewCacheCommand.php index 4760e98e..376970ee 100644 --- a/src/Phaseolies/Console/Commands/ViewCacheCommand.php +++ b/src/Phaseolies/Console/Commands/ViewCacheCommand.php @@ -77,7 +77,7 @@ protected function compileViews(array $viewFiles): array $results = ['success' => [], 'failed' => []]; foreach ($viewFiles as $file) { - $relativePath = str_replace(base_path('resources/views') . '/', '', $file); + $relativePath = $this->relativePath($file, base_path('resources/views')); $viewName = str_replace(['/', '.odo.php'], ['.', ''], $relativePath); try { diff --git a/src/Phaseolies/Console/Schedule/Command.php b/src/Phaseolies/Console/Schedule/Command.php index fbb64082..1713c4be 100644 --- a/src/Phaseolies/Console/Schedule/Command.php +++ b/src/Phaseolies/Console/Schedule/Command.php @@ -353,6 +353,78 @@ protected function withTiming(callable $operation, ?string $successMessage = nul }); } + /** + * Normalize a generated class/path name so both "/" and "\" work across platforms. + * + * @param string $name + * @return string + */ + protected function normalizeGeneratedName(string $name): string + { + $normalized = trim(str_replace('\\', '/', $name)); + $normalized = preg_replace('#/+#', '/', $normalized) ?? $normalized; + + return trim($normalized, '/'); + } + + /** + * Split a generated class/path name into normalized name, path parts and final class name. + * + * @param string $name + * @return array{string,array,string} + */ + protected function splitGeneratedName(string $name): array + { + $normalized = $this->normalizeGeneratedName($name); + $parts = $normalized === '' ? [] : explode('/', $normalized); + $className = array_pop($parts) ?? ''; + + return [$normalized, $parts, $className]; + } + + /** + * Build an absolute file path for a generated class file. + * + * @param string $baseDirectory + * @param string $name + * @param string $extension + * @return string + */ + protected function generatedFilePath(string $baseDirectory, string $name, string $extension = '.php'): string + { + $normalizedName = $this->normalizeGeneratedName($name); + $relativePath = trim($baseDirectory, '/\\'); + + if ($normalizedName !== '') { + $relativePath .= '/' . $normalizedName; + } + + return base_path(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $relativePath . $extension)); + } + + /** + * Convert an absolute path into a forward-slash relative path with no leading slash. + * + * @param string $path + * @param string|null $basePath + * @return string + */ + protected function relativePath(string $path, ?string $basePath = null): string + { + $normalizedPath = str_replace('\\', '/', $path); + $normalizedBase = rtrim(str_replace('\\', '/', $basePath ?? base_path()), '/'); + + if ($normalizedPath === $normalizedBase) { + return ''; + } + + if (str_starts_with($normalizedPath, $normalizedBase . '/')) { + return substr($normalizedPath, strlen($normalizedBase) + 1); + } + + return ltrim($normalizedPath, '/'); + } + /** * Prompt the user for confirmation. diff --git a/tests/Console/CommandBehaviorCoverageTest.php b/tests/Console/CommandBehaviorCoverageTest.php index 744c69c2..638a6305 100644 --- a/tests/Console/CommandBehaviorCoverageTest.php +++ b/tests/Console/CommandBehaviorCoverageTest.php @@ -51,7 +51,7 @@ protected function setUp(): void parent::setUp(); Env::reset(); - Env::$appInstance = $this->createMock(Application::class); + Env::$appInstance = $this->createStub(Application::class); Env::bind('session', new FakeSessionStore()); Env::bind('db', new FakeDatabaseInspector()); } @@ -125,6 +125,82 @@ public function testMakeControllerCommandValidatesConflictingFlagsAndBuildsPlace $this->assertStringContainsString('path app/Http/Controllers/Admin/UserController.php', $content); } + public function testCommandHelpersNormalizeGeneratedNamesAndRelativePaths(): void + { + $command = new class extends MakeProviderCommand + { + use InteractsWithFakeCommandIO; + }; + + [$normalized, $parts, $className] = $this->invokeMethod($command, 'splitGeneratedName', [ + '\\Admin//Hello\\TestProvider/', + ]); + + $this->assertSame('Admin/Hello/TestProvider', $normalized); + $this->assertSame(['Admin', 'Hello'], $parts); + $this->assertSame('TestProvider', $className); + $this->assertSame( + 'app/Providers/Hello/TestProvider.php', + $this->invokeMethod($command, 'relativePath', [Env::path('app/Providers/Hello/TestProvider.php')]) + ); + } + + public function testMakeProviderCommandAcceptsBackslashesAndOutputsRelativePathWithoutLeadingSlash(): void + { + $command = new class extends MakeProviderCommand + { + use InteractsWithFakeCommandIO; + }; + + $command->fakeArguments['name'] = 'Hello\\TestProvider'; + + $result = $command->handle(); + $file = Env::path('app/Providers/Hello/TestProvider.php'); + $contents = (string) file_get_contents($file); + $lines = array_map(static fn(array $line): string => $line[0], $command->capturedLines); + + $this->assertSame(0, $result); + $this->assertFileExists($file); + $this->assertStringContainsString('namespace App\\Providers\\Hello;', $contents); + $this->assertStringContainsString('class TestProvider extends ServiceProvider', $contents); + $this->assertContains( + '📦 File: app/Providers/Hello/TestProvider.php', + $lines + ); + } + + public function testMakeControllerCommandNormalizesBackslashesForResolvedPathsAndLayouts(): void + { + $command = new class extends MakeControllerCommand + { + use InteractsWithFakeCommandIO; + + protected function getLayoutStub(string $stubName): string + { + return '
layout
'; + } + }; + + [$namespace, $filePath, $className] = $this->invokeMethod($command, 'resolveNamespacesAndPaths', [ + 'Admin\\UserController', + false, + ]); + + $this->invokeMethod($command, 'generateLayout', [ + $namespace, + $className, + 'admin\\users', + ]); + + $this->assertSame('App\\Http\\Controllers\\Admin', $namespace); + $this->assertSame( + str_replace(['/', '\\'], DIRECTORY_SEPARATOR, Env::path('app/Http/Controllers/Admin/UserController.php')), + $filePath + ); + $this->assertSame('UserController', $className); + $this->assertFileExists(Env::path('resources/views/admin/users/default.odo.php')); + } + public function testMakeAuthorizerCommandGeneratesModelAwarePolicyMethods(): void { $command = new MakeAuthorizerCommand(); diff --git a/tests/Console/CommandSignatureCoverageTest.php b/tests/Console/CommandSignatureCoverageTest.php index 63969500..eb9c0be1 100644 --- a/tests/Console/CommandSignatureCoverageTest.php +++ b/tests/Console/CommandSignatureCoverageTest.php @@ -23,8 +23,8 @@ protected function setUp(): void parent::setUp(); Env::reset(); - Env::$appInstance = $this->createMock(Application::class); - Env::bind('migrator', $this->createMock(Migrator::class)); + Env::$appInstance = $this->createStub(Application::class); + Env::bind('migrator', $this->createStub(Migrator::class)); Env::bind('config', new class { public int $clearCount = 0; @@ -136,7 +136,7 @@ private static function commandFiles(string $root): array private function instantiateCommand(string $className): object { if ($className === VendorPublishCommand::class) { - Env::$appInstance = $this->createMock(Application::class); + Env::$appInstance = $this->createStub(Application::class); } $reflection = new \ReflectionClass($className); @@ -156,7 +156,7 @@ private function instantiateCommand(string $className): object $typeName = $parameter->getType()?->getName(); $dependencies[] = match ($typeName) { - MigrationCreator::class => $this->createMock(MigrationCreator::class), + MigrationCreator::class => $this->createStub(MigrationCreator::class), default => throw new \RuntimeException('Unhandled constructor dependency for ' . $className . ': ' . $typeName), }; } diff --git a/tests/Console/Support/CommandTestEnvironment.php b/tests/Console/Support/CommandTestEnvironment.php index fddcb7b5..6c4c39c2 100644 --- a/tests/Console/Support/CommandTestEnvironment.php +++ b/tests/Console/Support/CommandTestEnvironment.php @@ -16,7 +16,10 @@ final class CommandTestEnvironment public static function reset(): void { - self::$root = sys_get_temp_dir() . '/doppar-command-tests-' . bin2hex(random_bytes(5)); + self::$root = rtrim(sys_get_temp_dir(), '/\\') + . DIRECTORY_SEPARATOR + . 'doppar-command-tests-' + . bin2hex(random_bytes(5)); self::$config = [ 'app.name' => 'Doppar Demo', 'app.timezone' => 'UTC', @@ -61,13 +64,13 @@ public static function cleanup(): void public static function path(string $path = ''): string { - $base = rtrim(self::$root, '/'); + $base = rtrim(self::$root, '/\\'); if ($path === '') { return $base; } - return $base . '/' . ltrim($path, '/'); + return $base . DIRECTORY_SEPARATOR . ltrim($path, '/\\'); } public static function config(?string $key = null, mixed $default = null): mixed @@ -358,6 +361,18 @@ function app(?string $key = null): mixed } } +namespace Phaseolies\Console\Schedule { + + use Tests\Unit\Console\Support\CommandTestEnvironment; + + if (!function_exists(__NAMESPACE__ . '\base_path')) { + function base_path(string $path = ''): string + { + return CommandTestEnvironment::path($path); + } + } +} + namespace Phaseolies\Console\Commands\Cron { use Tests\Unit\Console\Support\CommandTestEnvironment; diff --git a/tests/StreamCollectionTest.php b/tests/StreamCollectionTest.php index 2d72f3df..7cbca7db 100644 --- a/tests/StreamCollectionTest.php +++ b/tests/StreamCollectionTest.php @@ -511,7 +511,7 @@ public function testLazyEvaluation() $this->assertEquals(1, $executionCount); $all = $collection->all(); - echo "All items: " . json_encode($all) . "\n"; + $this->assertSame([1, 2, 3], $all); $this->assertEquals(4, $executionCount); }