Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,27 @@ Run the example module command:
php shift.php health
```

Generate module scaffolding:

```sh
php shift.php create:module Billing
```

Generate module-owned classes:

```sh
php shift.php create:controller --module=Billing InvoiceController
php shift.php create:controller Billing:InvoiceController
php shift.php create:model Billing:Invoice
php shift.php create:service Billing:Invoice
php shift.php create:command Billing:SyncInvoices
php shift.php create:middleware Billing:Audit
php shift.php create:dto Billing:CreateInvoice
```

Generator commands normalize module and class names to PHP class conventions. Missing suffixes are added for controllers, services, middleware, and DTOs.
Generator templates live in `src/Console/Generator/stubs`.

## Modules

ShiftPHP supports a modular monolith structure under `application/modules`.
Expand All @@ -276,6 +297,8 @@ application/modules/Health/
└── Commands/
```

Generated models, middleware, and DTOs live under `Models/`, `Middleware/`, and `Dto/` inside the target module.

A module registers itself through `Module.php`:

```php
Expand Down
1 change: 1 addition & 0 deletions REFACTORING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ShiftPHP is moving toward an API-only modular monolith. View templates, compiled
- [x] Module configuration loading.
- [x] Module lifecycle hooks, for example `boot()` after service registration.
- [x] Framework source moved to `src/` for package split preparation.
- [x] CLI create generators for modules and module-owned classes with file-based stubs.
- [x] Removal of view storage and example page assets from runtime.
- [x] Removal of legacy `application/controllers` and `application/routes.php`.
- [x] Domain-oriented framework namespaces:
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"autoload": {
"psr-4": {
"Shift\\": "src",
"Console\\Commands\\": "src/Console/Commands",
"Modules\\": "application/modules"
}
}
Expand Down
18 changes: 17 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ <h2>Modules</h2>
|-- Services/
`-- Commands/</code></pre>

<p>Generator commands can also create <code>Models/</code>, <code>Middleware/</code>, and <code>Dto/</code> directories when those artifacts are added.</p>

<p>Every module boundary implements <code>Shift\Modules\ModuleInterface</code>. Most modules can extend <code>Shift\Modules\AbstractModule</code> and override only the methods they need.</p>

<pre><code>namespace Modules\Health;
Expand Down Expand Up @@ -386,11 +388,25 @@ <h2>Service Container</h2>

<section id="cli">
<h2>CLI</h2>
<p>The CLI entry point is <code>shift.php</code>. Built-in commands live under <code>Shift\Console\Commands</code>, and module commands are loaded from module command mappings.</p>
<p>The CLI entry point is <code>shift.php</code>. Built-in commands live under <code>Console\Commands</code>, and module commands are loaded from module command mappings.</p>

<pre><code>php shift.php route:list
php shift.php health</code></pre>

<p>Create commands scaffold modules and module-owned classes:</p>

<pre><code>php shift.php create:module Billing
php shift.php create:controller --module=Billing InvoiceController
php shift.php create:controller Billing:InvoiceController
php shift.php create:model Billing:Invoice
php shift.php create:service Billing:Invoice
php shift.php create:command Billing:SyncInvoices
php shift.php create:middleware Billing:Audit
php shift.php create:dto Billing:CreateInvoice</code></pre>

<p>Controller, service, middleware, and DTO generators add the expected class suffix when it is missing. Commands accept either <code>--module=Billing Name</code> or <code>Billing:Name</code>.</p>
<p>Generator templates live in <code>src/Console/Generator/stubs</code>.</p>

<p>Commands implement <code>Shift\Console\CommandInterface</code>.</p>
</section>

Expand Down
42 changes: 42 additions & 0 deletions src/Console/Commands/CreateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Console\Commands;

use Shift\Console\CommandInterface;
use Shift\Console\Generator\GeneratesFiles;
use Shift\Console\Generator\NameFormatter;

class CreateCommand implements CommandInterface
{
use GeneratesFiles;

public function execute(mixed ...$args): void
{
try {
[$module, $rawName] = $this->moduleAndClassFromArgs($args);
} catch (\InvalidArgumentException) {
$this->cli->error($this->getHelp());
return;
}

$class = NameFormatter::className($rawName);
$path = $this->modulePath($module) . '/Commands/' . $class . '.php';

$this->writeAndReport($path, $this->renderStub('command', [
'module' => $module,
'class' => $class,
'command' => NameFormatter::commandName($class),
]));
}

public function getHelp(): string
{
return 'Usage: php shift.php create:command --module={name} {CommandName}';
}

public function getDescription(): string
{
return 'Create a module CLI command.';
}

}
41 changes: 41 additions & 0 deletions src/Console/Commands/CreateController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Console\Commands;

use Shift\Console\CommandInterface;
use Shift\Console\Generator\GeneratesFiles;
use Shift\Console\Generator\NameFormatter;

class CreateController implements CommandInterface
{
use GeneratesFiles;

public function execute(mixed ...$args): void
{
try {
[$module, $rawName] = $this->moduleAndClassFromArgs($args);
} catch (\InvalidArgumentException) {
$this->cli->error($this->getHelp());
return;
}

$class = NameFormatter::className($rawName, 'Controller');
$path = $this->modulePath($module) . '/Controllers/' . $class . '.php';

$this->writeAndReport($path, $this->renderStub('controller', [
'module' => $module,
'class' => $class,
]));
}

public function getHelp(): string
{
return 'Usage: php shift.php create:controller --module={name} {ControllerName}';
}

public function getDescription(): string
{
return 'Create a module controller.';
}

}
41 changes: 41 additions & 0 deletions src/Console/Commands/CreateDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Console\Commands;

use Shift\Console\CommandInterface;
use Shift\Console\Generator\GeneratesFiles;
use Shift\Console\Generator\NameFormatter;

class CreateDto implements CommandInterface
{
use GeneratesFiles;

public function execute(mixed ...$args): void
{
try {
[$module, $rawName] = $this->moduleAndClassFromArgs($args);
} catch (\InvalidArgumentException) {
$this->cli->error($this->getHelp());
return;
}

$class = NameFormatter::className($rawName, 'Dto');
$path = $this->modulePath($module) . '/Dto/' . $class . '.php';

$this->writeAndReport($path, $this->renderStub('dto', [
'module' => $module,
'class' => $class,
]));
}

public function getHelp(): string
{
return 'Usage: php shift.php create:dto --module={name} {DtoName}';
}

public function getDescription(): string
{
return 'Create a module request DTO.';
}

}
41 changes: 41 additions & 0 deletions src/Console/Commands/CreateMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Console\Commands;

use Shift\Console\CommandInterface;
use Shift\Console\Generator\GeneratesFiles;
use Shift\Console\Generator\NameFormatter;

class CreateMiddleware implements CommandInterface
{
use GeneratesFiles;

public function execute(mixed ...$args): void
{
try {
[$module, $rawName] = $this->moduleAndClassFromArgs($args);
} catch (\InvalidArgumentException) {
$this->cli->error($this->getHelp());
return;
}

$class = NameFormatter::className($rawName, 'Middleware');
$path = $this->modulePath($module) . '/Middleware/' . $class . '.php';

$this->writeAndReport($path, $this->renderStub('middleware', [
'module' => $module,
'class' => $class,
]));
}

public function getHelp(): string
{
return 'Usage: php shift.php create:middleware --module={name} {MiddlewareName}';
}

public function getDescription(): string
{
return 'Create a module middleware.';
}

}
41 changes: 41 additions & 0 deletions src/Console/Commands/CreateModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Console\Commands;

use Shift\Console\CommandInterface;
use Shift\Console\Generator\GeneratesFiles;
use Shift\Console\Generator\NameFormatter;

class CreateModel implements CommandInterface
{
use GeneratesFiles;

public function execute(mixed ...$args): void
{
try {
[$module, $rawName] = $this->moduleAndClassFromArgs($args);
} catch (\InvalidArgumentException) {
$this->cli->error($this->getHelp());
return;
}

$class = NameFormatter::className($rawName);
$path = $this->modulePath($module) . '/Models/' . $class . '.php';

$this->writeAndReport($path, $this->renderStub('model', [
'module' => $module,
'class' => $class,
]));
}

public function getHelp(): string
{
return 'Usage: php shift.php create:model --module={name} {ModelName}';
}

public function getDescription(): string
{
return 'Create a module model.';
}

}
49 changes: 49 additions & 0 deletions src/Console/Commands/CreateModule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Console\Commands;

use Shift\Console\CommandInterface;
use Shift\Console\Generator\GeneratesFiles;
use Shift\Console\Generator\NameFormatter;

class CreateModule implements CommandInterface
{
use GeneratesFiles;

public function execute(mixed ...$args): void
{
$rawName = $args[0] ?? null;

if (!is_string($rawName) || $rawName === '') {
$this->cli->error($this->getHelp());
return;
}

$module = NameFormatter::moduleName($rawName);
$slug = NameFormatter::slug($rawName);
$path = $this->modulePath($module);

foreach (['Commands', 'Controllers', 'Services'] as $directory) {
$this->files->ensureDirectory($path . '/' . $directory);
}

$this->writeAndReport($path . '/Module.php', $this->renderStub('module', [
'module' => $module,
'slug' => $slug,
]));
$this->writeAndReport($path . '/config.php', $this->renderStub('config', [
'slug' => $slug,
]));
}

public function getHelp(): string
{
return 'Usage: php shift.php create:module {name}';
}

public function getDescription(): string
{
return 'Create a new Shift module.';
}

}
41 changes: 41 additions & 0 deletions src/Console/Commands/CreateService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Console\Commands;

use Shift\Console\CommandInterface;
use Shift\Console\Generator\GeneratesFiles;
use Shift\Console\Generator\NameFormatter;

class CreateService implements CommandInterface
{
use GeneratesFiles;

public function execute(mixed ...$args): void
{
try {
[$module, $rawName] = $this->moduleAndClassFromArgs($args);
} catch (\InvalidArgumentException) {
$this->cli->error($this->getHelp());
return;
}

$class = NameFormatter::className($rawName, 'Service');
$path = $this->modulePath($module) . '/Services/' . $class . '.php';

$this->writeAndReport($path, $this->renderStub('service', [
'module' => $module,
'class' => $class,
]));
}

public function getHelp(): string
{
return 'Usage: php shift.php create:service --module={name} {ServiceName}';
}

public function getDescription(): string
{
return 'Create a module service.';
}

}
Loading
Loading