- New code generation made for CLI

This commit is contained in:
Dave M. 2026-01-05 12:39:41 +00:00
parent ae212a4377
commit c3e3d6a73a
22 changed files with 1067 additions and 17 deletions

View File

@ -2,7 +2,7 @@
use Ulmus\ConnectionAdapter;
putenv('LEAN_API_PROJECT_PATH=' . $path = dirname(__DIR__, 2));
define('LEAN_API_PROJECT_PATH', $path = dirname(__DIR__, 2));
return [
'lean.api' => [
@ -24,6 +24,10 @@ return [
],
],
'cli' => [
'Lean\\Api\\Cli' => implode(DIRECTORY_SEPARATOR, [ $path, "src", "Cli", "" ]),
],
'ulmus' => [
'entities' => [ 'Lean\\Api\\Entity' => implode(DIRECTORY_SEPARATOR, [ $path, 'src', 'Entity', '' ]) ],
'adapters' => [

View File

@ -0,0 +1,63 @@
<?php
namespace %NAMESPACE%\%API_NS%;
use Notes\Attribute\Ignore;
use Notes\Route\Attribute\Method\Route;
use Psr\Http\Message\{ ServerRequestInterface, ResponseInterface };
use %NAMESPACE%\{Lib, %USE_ENTITY_NS%, %USE_FORM_NS%, };
use function %NAMESPACE%\View\{_, lang, url, route, form};
#[\Notes\Route\Attribute\Object\Route(base:"%API_ROUTE_BASE%/%CLASSNAME_LC%")]
class %CLASSNAME% {
use Lib\ApiTrait;
#[Route(route: "/documentation", name: "api.%CLASSNAME_LC%:docs", method: "GET")]
public function index(ServerRequestInterface $request, array $attributes): ResponseInterface
{
return $this->renderMarkdown(getenv("PROJECT_PATH") . "/meta/docs/%CLASSNAME_LC%.md", [ %ENTITY_NS%\%CLASSNAME%::class, ], [ %FORM_NS%\%SAVE_FORM_CONTEXT_CLASSNAME%::class, %FORM_NS%\%DELETE_FORM_CONTEXT_CLASSNAME%::class ]);
}
#[Route("/", name: "api.%CLASSNAME_LC%:list", method: "GET")]
public function list(ServerRequestInterface $request, array $attributes) : ResponseInterface
{
$request = $this->searchEntitiesFromRequest($request, %ENTITY_NS%\%CLASSNAME%::class);
$result = $request->getAttribute("lean.searchRequest")[%ENTITY_NS%\%CLASSNAME%::class];
return $this->outputSearchResult($result);
}
#[Route("/", name: "api.%CLASSNAME_LC%:add", method: "POST")]
public function add(ServerRequestInterface $request, array $attributes) : ResponseInterface
{
$entity = new %ENTITY_NS%\%CLASSNAME%();
form($this->formFactory->%SAVE_FORM_CLASSNAME_LC%($entity), $this->formFactory->%SAVE_FORM_CONTEXT_CLASSNAME_LC%($request));
return $this->output($entity, (int) $entity->isLoaded());
}
#[Route("/{id:\d+}", name: "api.%CLASSNAME_LC%:single", method: "GET")]
#[Route("/{id:\d+}", name: "api.%CLASSNAME_LC%:edit", method: "PATCH")]
public function single(ServerRequestInterface $request, array $attributes) : ResponseInterface
{
$request = $this->searchEntityFromRequest($request, %ENTITY_NS%\%CLASSNAME%::class);
$result = $request->getAttribute("lean.searchRequest")[%ENTITY_NS%\%CLASSNAME%::class];
form($this->formFactory->%SAVE_FORM_CLASSNAME_LC%($result->getResult()), $this->formFactory->%SAVE_FORM_CONTEXT_CLASSNAME_LC%($request));
return $this->outputSearchResult($result);
}
#[Route("/{id:\d+}", name: "api.%CLASSNAME_LC%:delete", method: "DELETE")]
public function delete(ServerRequestInterface $request, array $attributes) : ResponseInterface
{
$request = $this->searchEntityFromRequest($request, %ENTITY_NS%\%CLASSNAME%::class);
$result = $request->getAttribute("lean.searchRequest")[%ENTITY_NS%\%CLASSNAME%::class];
form($this->formFactory->%DELETE_FORM_CLASSNAME_LC%($result->getResult()), $this->formFactory->%DELETE_FORM_CONTEXT_CLASSNAME_LC%($request));
return $this->outputSearchResult($result);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace %NAMESPACE%\%WEB_NS%;
use Notes\Breadcrumb\Attribute\Method\Breadcrumb;
use Notes\Route\Attribute\Method\Route;
use Psr\Http\Message\ServerRequestInterface,
Psr\Http\Message\ResponseInterface;
use %NAMESPACE%\{Lib, %USE_ENTITY_NS%, %USE_FORM_NS%, };
use function %NAMESPACE%\View\{_, lang, url, route, form};
#[\Notes\Route\Attribute\Object\Route(base:"%WEB_ROUTE_BASE%/%CLASSNAME_LC%")]
class %CLASSNAME% {
use Lib\ControllerTrait;
#[Route("/", name: "%CLASSNAME_LC%:index")]
##[Breadcrumb(parent: "", icon: "", lang: "app.lang.breadcrumb")]
public function index(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
$search = %ENTITY_NS%\%CLASSNAME%::searchRequest()->fromRequest($request) ;
$list = %ENTITY_NS%\%CLASSNAME%::repository()->filterServerRequest($search)->loadAll();
return $this->renderView('%CLASSNAME_LC%/index', get_defined_vars());
}
#[Route("/create", name: "%CLASSNAME_LC%:create", method: ["GET", "POST"])]
##[Breadcrumb(parent: "%CLASSNAME_LC%:index", icon: "", lang: "app.lang.breadcrumb")]
public function create(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
$entity = new %ENTITY_NS%\%CLASSNAME%();
form($this->formFactory->%SAVE_FORM_CLASSNAME_LC%($entity), $this->pushContext($this->formFactory->%SAVE_FORM_CONTEXT_CLASSNAME_LC%($request, "%CLASSNAME_LC%.create")));
if ($entity->isLoaded()) {
return $this->redirect(route("%CLASSNAME_LC%:edit", [ 'id' => $entity->id ]) );
}
return $this->renderView('%CLASSNAME_LC%/create', get_defined_vars());
}
#[Route("/{id:\d+}/edit", name: "%CLASSNAME_LC%:edit", method: ["GET", "POST"])]
##[Breadcrumb(parent: "%CLASSNAME_LC%:index", icon: "", lang: "app.lang.breadcrumb")]
public function edit(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
$search = %ENTITY_NS%\%CLASSNAME%::searchRequest()->fromRequest($request);
$entity = %ENTITY_NS%\%CLASSNAME%::repository()->filterServerRequest($search)->loadOne();
form($this->formFactory->%SAVE_FORM_CLASSNAME_LC%($entity), $this->pushContext($this->formFactory->%SAVE_FORM_CONTEXT_CLASSNAME_LC%($request, "%CLASSNAME_LC%.edit")));
return $this->renderView('%CLASSNAME_LC%/edit', get_defined_vars());
}
#[Route("/{id:\d+}/delete", name: "%CLASSNAME_LC%:delete", method: ["POST", "DELETE"])]
public function delete(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
$search = %ENTITY_NS%\%CLASSNAME%::searchRequest()->fromRequest($request);
$entity = %ENTITY_NS%\%CLASSNAME%::repository()->filterServerRequest($search)->loadOne();
form($this->formFactory->%DELETE_FORM_CLASSNAME_LC%($entity), $this->pushContext($this->formFactory->%DELETE_FORM_CONTEXT_CLASSNAME_LC%($request, "%CLASSNAME_LC%.delete")));
return $this->redirect(route("%CLASSNAME_LC%:index"));
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace %NAMESPACE%\%FORM_NS%;
use Picea\Ui\Method\{ FormContextInterface };
use Lean\Api\Factory\MessageFactoryInterface;
use Lean\LanguageHandler;
use %NAMESPACE%\{%USE_ENTITY_NS%, };
class %DELETE_FORM_CLASSNAME% extends \Lean\Api\Form\Delete
{
protected string $contextClass = %DELETE_FORM_CONTEXT_CLASSNAME%::class;
public function __construct(
protected LanguageHandler $languageHandler,
protected MessageFactoryInterface $message,
protected %ENTITY_NS%\%CLASSNAME% $entity,
) {}
}

View File

@ -0,0 +1,22 @@
<?php
namespace %NAMESPACE%\%FORM_NS%;
use Lean\Api\Attribute\ContextField;
use %NAMESPACE%\{ %USE_ENTITY_NS% };
#[ContextField]
class %DELETE_FORM_CONTEXT_CLASSNAME% extends \Lean\Api\Lib\FormContext
{
# #[ContextField]
# public string $name;
public function valid(? %ENTITY_NS%\%CLASSNAME% $entity = null) : bool
{
if ( ! $this->getEntity()->isLoaded() ) {
$this->pushErrorMessage('lean.api.form.delete.error.entity');
}
return parent::valid();
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace %NAMESPACE%\%ENTITY_NS%;
use Ulmus\{
Attribute\IndexTypeEnum,
Attribute\Obj\Table,
Attribute\Property\Field,
Attribute\Property\Index,
Attribute\Property\OrderBy,
Attribute\Property\Relation,
Attribute\Property\Virtual,
Entity\EntityInterface,
EntityCollection,
Entity\Field\Datetime,
Repository,
SearchRequest,
};
use Ulmus\SearchRequest\Attribute\SearchWhere;
use Notes\Attribute\Ignore;
use %NAMESPACE%\Lib;
#[Table(name: "%CLASSNAME_LC%")]
class %CLASSNAME% implements \JsonSerializable, EntityInterface
{
use Lib\EntityTrait;
#[Field\Id]
public int $id;
#[Ignore]
public static function searchRequest(...$arguments) : SearchRequest\SearchRequestInterface
{
return new #[SearchRequest\Attribute\SearchRequestParameter(%CLASSNAME%::class)] class(... $arguments) extends SearchRequest\SearchRequest {
#[SearchWhere(parameter: [ '%CLASSNAME_LC%_id', 'id' ], source: SearchRequest\Attribute\PropertyValueSource::RequestAttribute)]
public int $id;
};
}
}

View File

@ -0,0 +1,20 @@
public function %SAVE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface
{
return new %FORM_NS%\%SAVE_FORM_CLASSNAME%($this->languageHandler, $this->message, $entity);
}
public function %SAVE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface
{
return new %FORM_NS%\%SAVE_FORM_CONTEXT_CLASSNAME%($this->languageHandler, $this->message, $request, $formName);
}
public function %DELETE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface
{
return new %FORM_NS%\%DELETE_FORM_CLASSNAME%($this->languageHandler, $this->message, $entity);
}
public function %DELETE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface
{
return new %FORM_NS%\%DELETE_FORM_CONTEXT_CLASSNAME%($this->languageHandler, $this->message, $request, $formName);
}
}

View File

@ -0,0 +1,5 @@
public function %SAVE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface;
public function %SAVE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface;
public function %DELETE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface;
public function %DELETE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface;
}

View File

@ -0,0 +1,32 @@
<?php
namespace %NAMESPACE%\%FACTORY_TRAIT_NS%;
use %NAMESPACE%\{ %USE_ENTITY_NS%, %USE_FORM_NS%, };
use Picea\Ui\Method\FormContextInterface;
use Picea\Ui\Method\FormInterface;
use Psr\Http\Message\ServerRequestInterface;
use Ulmus\Entity\EntityInterface;
trait %CLASSNAME%Trait
{
public function %SAVE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface
{
return new %FORM_NS%\%SAVE_FORM_CLASSNAME%($this->languageHandler, $this->messageFactory, $entity);
}
public function %SAVE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface
{
return new %FORM_NS%\%SAVE_FORM_CONTEXT_CLASSNAME%($this->languageHandler, $this->messageFactory, $request, $formName);
}
public function %DELETE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface
{
return new %FORM_NS%\%DELETE_FORM_CLASSNAME%($this->languageHandler, $this->messageFactory, $entity);
}
public function %DELETE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface
{
return new %FORM_NS%\%DELETE_FORM_CONTEXT_CLASSNAME%($this->languageHandler, $this->messageFactory, $request, $formName);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace %NAMESPACE%\%FORM_NS%;
use Picea\Ui\Method\{ FormContextInterface };
use Lean\Api\Factory\MessageFactoryInterface;
use Lean\LanguageHandler;
use %NAMESPACE%\{%USE_ENTITY_NS%, };
class %SAVE_FORM_CLASSNAME% extends \Lean\Api\Form\Save
{
protected string $contextClass = %SAVE_FORM_CONTEXT_CLASSNAME%::class;
public function __construct(
protected LanguageHandler $languageHandler,
protected MessageFactoryInterface $message,
protected %ENTITY_NS%\%CLASSNAME% $entity,
) {}
}

View File

@ -0,0 +1,18 @@
<?php
namespace %NAMESPACE%\%FORM_NS%;
use Lean\Api\Attribute\ContextField;
use %NAMESPACE%\{ %USE_ENTITY_NS% };
#[ContextField]
class %SAVE_FORM_CONTEXT_CLASSNAME% extends \Lean\Api\Lib\FormContext
{
# #[ContextField]
# public string $name;
public function valid(? %ENTITY_NS%\%CLASSNAME% $entity = null) : bool
{
return parent::valid();
}
}

View File

@ -0,0 +1,21 @@
# Title
## API 1.0
[APP](../) / %CLASSNAME%
--------------------------------------------------------------------------------
### Routes
{route:descriptor}
{request:debugger}
### Forms
{form:descriptor}
### Entities
{entity:descriptor}

View File

@ -0,0 +1,599 @@
<?php
namespace Lean\Api\Action\Console;
use Lean\Application;
use Lean\Composer;
use Mcnd\CLI\Terminal;
class EntityGenerate
{
# public string $applicationPath;
public string $namespace;
public string $entityNamespace;
public string $formNamespace;
public string $saveForm;
public string $saveContext;
public string $deleteForm;
public string $deleteContext;
public string $apiNamespace;
public string $apiBase;
public string $webNamespace;
public string $webBase;
public string $factoryAction;
public string $factoryNamespace;
public string $factoryTraitNamespace;
public string $factoryFile;
public string $factoryInterfaceFile;
public bool $includeDocs = false;
public readonly string $compiledEntity;
public readonly string $compiledSaveForm;
public readonly string $compiledSaveFormContext;
public readonly string $compiledDeleteForm;
public readonly string $compiledDeleteFormContext;
public readonly string $compiledFactory;
public readonly string $compiledFactoryTrait;
public readonly string $compiledFactoryInterface;
public readonly string $compiledApi;
public readonly string $compiledWeb;
public readonly string $compiledDocs;
public function __construct(
public Application $application,
public string $name,
public bool $overwrite,
public bool $default,
) {}
public function writeToDisc() : void
{
$entityPath = $this->pathFromNamespace($this->entityNamespace, $this->application->entities);
if (empty($entityPath)) {
throw new \InvalidArgumentException("Could not find entities path from given namespaces.");
}
if (isset($this->webNamespace)) {
$webPath = $this->pathFromNamespace($this->webNamespace, $this->application->routes);
if (empty($webPath)) {
throw new \InvalidArgumentException("Could not find web path routes from given namespaces.");
}
}
if (isset($this->apiNamespace)) {
$apiPath = $this->pathFromNamespace($this->apiNamespace, $this->application->routes);
if (empty($apiPath)) {
throw new \InvalidArgumentException("Could not find web path routes from given namespaces.");
}
if ($this->includeDocs) {
$docPath = implode(DIRECTORY_SEPARATOR, [ getenv('META_PATH'), "docs" ]);
$docPath = $this->readline(sprintf("Path of API's documentation output (default: %s) : ", $docPath), $docPath);
}
}
if (isset($this->formNamespace)) {
$formPath = implode(DIRECTORY_SEPARATOR, [ $this->pathFromPSR($this->formNamespace), $this->formNamespace ]);
$formPath = $this->readline(sprintf("Path of Forms (default: %s) : ", $formPath), $formPath);
}
if (isset($this->factoryNamespace)) {
$factoryPath = implode(DIRECTORY_SEPARATOR, [ $this->pathFromPSR($this->factoryNamespace), $this->factoryNamespace ]);
$factoryPath = $this->readline(sprintf("Path of Factory (default: %s) : ", $factoryPath), $factoryPath);
}
if ( Terminal::proposeListChoice("Input completed, write to files ?", [ true => 'Yes', false => 'No', ]) ) {
$this->writeToFile($entityPath, $this->name, $this->compiledEntity, );
if (isset($webPath)) {
$this->writeToFile($webPath, $this->name, $this->compiledWeb, );
}
if (isset($apiPath)) {
$this->writeToFile($apiPath, $this->name, $this->compiledApi, );
if ($this->includeDocs) {
$this->writeToFile($docPath , strtolower($this->name), $this->compiledDocs, ".md");
}
}
if (isset($formPath)) {
if ($this->saveForm) {
$this->writeToFile($formPath, $this->saveForm, $this->compiledSaveForm,);
$this->writeToFile($formPath, $this->saveContext, $this->compiledSaveFormContext,);
}
if ($this->deleteForm) {
$this->writeToFile($formPath, $this->deleteForm, $this->compiledDeleteForm,);
$this->writeToFile($formPath, $this->deleteContext, $this->compiledDeleteFormContext,);
}
if (isset($this->factoryNamespace)) {
if (isset($this->factoryTraitNamespace)) {
$traitName = $this->name . "Trait";
$this->writeToFile(implode(DIRECTORY_SEPARATOR, [$factoryPath, "Factories"]), $traitName, $this->compiledFactoryTrait,);
$this->appendToFile($factoryPath, $this->factoryFile, "\tuse Factories\\$traitName;\n}", "");
}
else {
$this->appendToFile($factoryPath, $this->factoryFile, $this->compiledFactory, "");
}
$this->appendToFile($factoryPath, $this->factoryInterfaceFile, $this->compiledFactoryInterface, "");
}
}
}
}
protected function appendToFile(string $path, string $filename, string $content, string $extension = ".php"): void
{
$fullpath = implode(DIRECTORY_SEPARATOR, [ rtrim($path, DIRECTORY_SEPARATOR), $filename . "$extension" ]);
$existingFileContent = file_get_contents($fullpath);
$this->writeToFile($path, $filename, rtrim($existingFileContent, '} ') . $content, $extension);
}
protected function writeToFile(string $path, string $filename, string $content, string $extension = ".php"): void
{
$fullpath = implode(DIRECTORY_SEPARATOR, [ rtrim($path, DIRECTORY_SEPARATOR), $filename . "$extension" ]);
if (file_exists($fullpath)) {
if ((! $this->overwrite) && Terminal::proposeListChoice(sprintf("Trying to write over an existing file '%s',\nOverwrite file ?", $fullpath), [ false => 'Yes', true => 'No', ])) {
return;
}
}
elseif (! file_exists(dirname($fullpath))) {
mkdir(dirname($fullpath), 0755, true);
}
file_put_contents($fullpath, $content, \LOCK_EX);
Terminal::sprintfline("File successfully written '%s'", $fullpath);
}
protected function pathFromNamespace(string $namespace, array $source) : false|string
{
$split = explode('\\', $namespace);
while($split) {
$ns = $this->namespace . "\\" . implode('\\', $split);
if ( isset($source[$ns]) ) {
return rtrim($source[$ns], DIRECTORY_SEPARATOR);
}
array_pop($split);
}
return false;
}
protected function pathFromPSR(string $namespace) : false|string
{
$source = $this->readComposerPsr4();
$split = explode('\\', $namespace);
while($split) {
$ns = $this->namespace . "\\" . implode('\\', $split);
if (! str_ends_with($ns, '\\')) {
$ns .= "\\";
}
if ( isset($source[$ns]) ) {
return $source[$ns][0];
}
array_pop($split);
}
return $source[$this->namespace . "\\"][0] ?? false;
}
public function outputResults() : void
{
}
public function readline(? string $prompt, mixed $default = null): string|false
{
$result = Terminal::readline($prompt);
if ($this->default && $default !== null) {
return $default;
}
return $result ?: $default;
}
public function readNamespace() : void
{
$namespace = $this->readComposerNamespace();
$this->namespace = $this->readline(sprintf('Namespace (default: %s) : ', $namespace), $namespace);
}
public function readApi() : void
{
$this->apiNamespace = $this->readline('API namespace (default: Api\\) : ', 'Api');
$this->apiBase = $this->readline('API URL base (default: /api) : ', '/api');
}
public function readWeb() : void
{
$this->webNamespace = $this->readline('Controller namespace (default: Controller\\) : ', 'Controller');
$this->webBase = $this->readline('Controller URL base (default: /) : ', "");
}
public function readEntity() : void
{
$this->entityNamespace = $this->readline('Entity namespace (default: Entity\\) : ', 'Entity');
}
public function readFactory() : void
{
$this->factoryNamespace ??= $this->readline('Factory namespace (default: Factory\\) : ', 'Factory');
$this->factoryFile = $this->readline('Form factory interface file (default: FormFactory.php) : ', 'FormFactory.php');
$this->factoryInterfaceFile = $this->readline('Form factory interface file (default: FormFactoryInterface.php) : ', 'FormFactoryInterface.php');
$this->factoryAction = Terminal::proposeListChoice("Use new trait or append to file ?", [ 'trait' => 'Use trait', 'append' => 'Append to file', ]);
if ($this->factoryAction === 'trait') {
$this->factoryTraitNamespace = implode('\\', [ $this->factoryNamespace, 'Factories' ]);
$this->factoryTraitNamespace = $this->readline(sprintf("Factory trait namespace (default: %s) : ", $this->factoryTraitNamespace), $this->factoryTraitNamespace);
}
}
public function readSaveForm() : void
{
$this->formNamespace ??= $this->readline('Form namespace (default: Form\\) : ', 'Form');
$this->saveForm = ucfirst($this->name) . "Save";
$this->saveForm = $this->readline(sprintf('Form "create/update" name (default: %s) : ', $this->saveForm), $this->saveForm);
$this->saveContext = $this->saveForm . "Context";
$this->saveContext = $this->readline(sprintf('Context "create/update" (default: %s) : ', $this->saveContext), $this->saveContext);
}
public function readDeleteForm() : void
{
$this->formNamespace ??= $this->readline('Form namespace (default: Form\\) : ') ?: 'Form';
$this->deleteForm = ucfirst($this->name) . "Delete";
$this->deleteForm = $this->readline(sprintf('Form "delete" name (default: %s) : ', $this->deleteForm), $this->deleteForm);
$this->deleteContext = $this->deleteForm . "Context";
$this->deleteContext = $this->readline(sprintf('Context "delete" (default: %s) : ', $this->deleteContext), $this->deleteContext);
}
/*public function readApplicationPath(string $default) : void
{
$this->applicationPath = $this->readline(sprintf('Application path base (default: %s) : ', $default)) ?: $default;
}*/
public function generate() : void
{
$this->namespace = trim($this->namespace, '\\');
$this->formNamespace = trim($this->formNamespace, '\\');
$this->entityNamespace = trim($this->entityNamespace, '\\');
$this->compiledEntity = $this->defineEntityBoilerplate();
if (isset($this->saveForm)) {
$this->compiledSaveForm = $this->defineSaveFormBoilerplate();
$this->compiledSaveFormContext = $this->defineSaveFormContextBoilerplate();
}
if (isset($this->deleteForm)) {
$this->compiledDeleteForm = $this->defineDeleteFormBoilerplate();
$this->compiledDeleteFormContext = $this->defineDeleteFormContextBoilerplate();
}
if (isset($this->apiNamespace)) {
$this->compiledApi = $this->defineApiBoilerplate();
if ($this->includeDocs) {
$this->compiledDocs = $this->defineDocsBoilerplate();
}
}
if (isset($this->webNamespace)) {
$this->compiledWeb = $this->defineWebBoilerplate();
}
if (isset($this->factoryNamespace)) {
if (isset($this->factoryTraitNamespace)) {
$this->compiledFactoryTrait = $this->defineFactoryTraitBoilerplate();
}
else {
$this->compiledFactory = $this->defineFactoryBoilerplate();
}
$this->compiledFactoryInterface = $this->defineFactoryInterfaceBoilerplate();
}
}
protected function defineEntityBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('Entity'));
}
protected function defineSaveFormBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('SaveForm'));
}
protected function defineSaveFormContextBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('SaveFormContext'));
}
protected function defineDeleteFormBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('DeleteForm'));
}
protected function defineDeleteFormContextBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('DeleteFormContext'));
}
protected function defineFactoryInterfaceBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('FactoryInterfaceAppend'));
}
protected function defineFactoryTraitBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('FactoryTrait'));
}
protected function defineFactoryBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('FactoryAppend'));
}
protected function defineApiBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('Api'));
}
protected function defineDocsBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('api-docs', '.md'));
}
protected function defineWebBoilerplate() : string
{
return $this->replaceBoilerplateCode($this->getBoilerplateFile('Controller'));
}
protected function getBoilerplateFile(string $fileName, string $extension = ".php") : string
{
$path = implode(DIRECTORY_SEPARATOR, [ LEAN_API_PROJECT_PATH , 'skeleton', 'EntityGenerate', "{$fileName}{$extension}" ]);
$bp = file_get_contents($path);
if (! $bp ) {
throw new \InvalidArgumentException("Boilerplate file not found in path " . $path);
}
return $bp;
}
protected function getEntityBoilerplate() : string
{
$path = implode(DIRECTORY_SEPARATOR, [ LEAN_API_PROJECT_PATH , 'skeleton', 'EntityGenerate', 'Entity.php' ]);
$bp = file_get_contents($path);
if (! $bp ) {
throw new \InvalidArgumentException("Entity boilerplate file not found in path " . $path);
}
return $bp;
}
protected function replaceBoilerplateCode(string $boilerplate) : string
{
# to be reworked with the |> operator in 8.5+
$boilerplate = $this->replaceNamespace($boilerplate);
$boilerplate = $this->replaceClassname($boilerplate);
$boilerplate = $this->replaceEntityNamespace($boilerplate);
$boilerplate = $this->replaceFormNamespace($boilerplate);
$boilerplate = $this->replaceSaveFormClassname($boilerplate);
$boilerplate = $this->replaceSaveFormContextClassname($boilerplate);
$boilerplate = $this->replaceDeleteFormClassname($boilerplate);
$boilerplate = $this->replaceDeleteFormContextClassname($boilerplate);
$boilerplate = $this->replaceFactoryNamespace($boilerplate);
$boilerplate = $this->replaceFactoryTraitNamespace($boilerplate);
$boilerplate = $this->replaceApiNamespace($boilerplate);
$boilerplate = $this->replaceApiBase($boilerplate);
$boilerplate = $this->replaceWebNamespace($boilerplate);
$boilerplate = $this->replaceWebBase($boilerplate);
return $boilerplate;
}
protected function replaceNamespace(string $boilerplate) : string
{
return str_replace('%NAMESPACE%', $this->namespace, $boilerplate);
}
protected function replaceEntityNamespace(string $boilerplate) : string
{
return match($this->entityNamespace ?? false) {
false => $boilerplate,
default => str_replace(
[ '%USE_ENTITY_NS%', '%ENTITY_NS%' ],
[ strstr($this->entityNamespace, '\\', true) ?: $this->entityNamespace, $this->entityNamespace ],
$boilerplate
)
};
}
protected function replaceSaveFormClassname(string $boilerplate) : string
{
return match($this->saveForm ?? false) {
false => $boilerplate,
default => str_replace(
[ '%SAVE_FORM_CLASSNAME%', '%SAVE_FORM_CLASSNAME_LC%' ],
[ $this->saveForm, lcfirst($this->saveForm) ],
$boilerplate
)
};
}
protected function replaceSaveFormContextClassname(string $boilerplate) : string
{
return match($this->saveContext ?? false) {
false => $boilerplate,
default => str_replace(
[ '%SAVE_FORM_CONTEXT_CLASSNAME%', '%SAVE_FORM_CONTEXT_CLASSNAME_LC%' ],
[ $this->saveContext, lcfirst($this->saveContext) ],
$boilerplate
)
};
}
protected function replaceDeleteFormClassname(string $boilerplate) : string
{
return match($this->deleteForm ?? false) {
false => $boilerplate,
default => str_replace(
[ '%DELETE_FORM_CLASSNAME%', '%DELETE_FORM_CLASSNAME_LC%' ],
[ $this->deleteForm, lcfirst($this->deleteForm) ],
$boilerplate
)
};
}
protected function replaceDeleteFormContextClassname(string $boilerplate) : string
{
return match($this->deleteContext ?? false) {
false => $boilerplate,
default => str_replace(
[ '%DELETE_FORM_CONTEXT_CLASSNAME%', '%DELETE_FORM_CONTEXT_CLASSNAME_LC%' ],
[ $this->deleteContext, lcfirst($this->deleteContext) ],
$boilerplate
)
};
}
protected function replaceFormNamespace(string $boilerplate) : string
{
return match($this->formNamespace ?? false) {
false => $boilerplate,
default => str_replace(
[ '%USE_FORM_NS%', '%FORM_NS%', ],
[ strstr($this->formNamespace, '\\', true) ?: $this->formNamespace, $this->formNamespace ],
$boilerplate
)
};
}
protected function replaceApiNamespace(string $boilerplate) : string
{
return match($this->apiNamespace ?? false) {
false => $boilerplate,
default => str_replace(
[ '%API_NS%', ],
[ rtrim($this->apiNamespace, '\\') ],
$boilerplate
)
};
}
protected function replaceApiBase(string $boilerplate) : string
{
return match($this->apiBase ?? false) {
false => $boilerplate,
default => str_replace(
[ '%API_ROUTE_BASE%', ],
[ $this->apiBase ],
$boilerplate
)
};
}
protected function replaceWebNamespace(string $boilerplate) : string
{
return match($this->webNamespace ?? false) {
false => $boilerplate,
default => str_replace(
[ '%WEB_NS%', ],
[ rtrim($this->webNamespace, '\\') ],
$boilerplate
)
};
}
protected function replaceWebBase(string $boilerplate) : string
{
return match($this->webBase ?? false) {
false => $boilerplate,
default => str_replace(
[ '%WEB_ROUTE_BASE%', ],
[ $this->webBase ],
$boilerplate
)
};
}
protected function replaceFactoryNamespace(string $boilerplate) : string
{
return match($this->factoryNamespace ?? false) {
false => $boilerplate,
default => str_replace(
[ '%FACTORY_NS%', ],
[ rtrim($this->factoryNamespace, '\\') ],
$boilerplate
)
};
}
protected function replaceFactoryTraitNamespace(string $boilerplate) : string
{
return match($this->factoryTraitNamespace ?? false) {
false => $boilerplate,
default => str_replace(
[ '%FACTORY_TRAIT_NS%', ],
[ rtrim($this->factoryTraitNamespace, '\\') ],
$boilerplate
)
};
}
protected function replaceClassname(string $boilerplate) : string
{
return match($this->apiBase ?? false) {
false => $boilerplate,
default => str_replace(
[ '%CLASSNAME_LC%', '%CLASSNAME%' ],
[ strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $this->name)), $this->name ],
$boilerplate
)
};
}
protected function readComposerNamespace() : ? string
{
if ( false !== $composerJson = Composer::readComposerJson() ) {
if ($psr4 = $composerJson['autoload']['psr-4'] ?? false) {
foreach ($psr4 as $ns => $directory) {
if ($directory === 'src/') {
return $ns;
}
}
}
}
return false;
}
protected function readComposerPsr4() : array
{
return include(implode(DIRECTORY_SEPARATOR, [ getenv('PROJECT_PATH'), 'vendor' , 'composer', 'autoload_psr4.php' ]));
}
}

120
src/Cli/CodeGeneration.php Normal file
View File

@ -0,0 +1,120 @@
<?php
namespace Lean\Api\Cli;
use DI\Attribute\Inject;
use Lean\Lean;
use Lean\Api\Action;
use Mcnd\CLI\{Terminal, OptionValueTypeEnum, OptionStack};
use Notes\CLI\Attribute\{Option, Command};
use Psr\Http\Message\{ServerRequestInterface, ResponseInterface};
#[Command(description: PHP_EOL . <<<CLI
#[color:gray, bg: black]╔═══════════════════════════════════════════════════════════════════╗[#]
#[color:gray, bg: black]║ ║[#]
#[color:gray, bg: black]║ ║[#]
#[color:gray, bg: black]║ ║[#]
#[color:gray, bg: black]║ ║[#]
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓████████▓▒░░▒▓██████▓▒░░▒▓███████▓▒░ ║[#]
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓██████▓▒░ ░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
#[color:gray, bg: black]║ ░▒▓████████▓▒░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
#[color:gray, bg: black]║ ║[#]
#[color:gray, bg: black]║ ║[#]
#[color:gray, bg: black]║ A P I ║[#]
#[color:gray, bg: black]║ ║[#]
#[color:gray, bg: black]╚═══════════════════════════════════════════════════════════════════╝[#]
Try #[color:gray, bg: black]'lean api --help'[#] for more information.
CLI
, command: 'lean-api')]
#[Option(switch: ['-h', '--help'], description: "Display help for the given command. When no command is given display help for the list command")]
class CodeGeneration
{
use \Lean\ControllerTrait;
#[Inject]
public Lean $lean;
#[Command(description: "Create an entity and it's related boilerplate", command: "entity:generate")]
#[Option(switch: ['-p', '--application'], description: "Application to target", value: OptionValueTypeEnum::RequiredValue)]
#[Option(switch: ['-n', '--name'], description: "Write generated content into project", value: OptionValueTypeEnum::RequiredValue)]
#[Option(switch: ['-m', '--namespace'], description: "Which PSR-4 namespace should be used", value: OptionValueTypeEnum::OptionalValue)]
#[Option(switch: ['-f', '--form'], description: "Form and context name", value: OptionValueTypeEnum::OptionalValue)]
#[Option(switch: ['-s', '--saveForm'], description: "Include or not a save form", value: OptionValueTypeEnum::OptionalValue)]
#[Option(switch: ['-d', '--deleteForm'], description: "Include or not a delete form", value: OptionValueTypeEnum::OptionalValue)]
#[Option(switch: ['-a', '--api'], description: "Include or not an API controller", value: OptionValueTypeEnum::OptionalValue)]
#[Option(switch: ['-c', '--api-docs'], description: "Include or not an API documentation page", value: OptionValueTypeEnum::OptionalValue)]
#[Option(switch: ['-w', '--web'], description: "Include or not a web controller", value: OptionValueTypeEnum::OptionalValue)]
#[Option(switch: ['-f', '--factory'], description: "Factory file to add to", value: OptionValueTypeEnum::OptionalValue)]
#[Option(switch: ['-w', '--write'], description: "Write generated content into project's folder path",)]
#[Option(switch: ['-o', '--overwrite'], description: "Overwrite every files by default", value: OptionValueTypeEnum::NoValue)]
#[Option(switch: ['-t', '--default-answers'], description: "Auto-accept every default answers", value: OptionValueTypeEnum::NoValue)]
##[Option(switch: ['-p', '--path'], description: "Base project save path", value: OptionValueTypeEnum::OptionalValue)]
public function generate(ServerRequestInterface $request, array $arguments, OptionStack $options, string $rest): ResponseInterface
{
$name = $options->get('name')->value() ?: readline('Entity name : ');
if ( empty($name)) {
throw new \InvalidArgumentException("An entity name must be provided");
}
$applicationList = array_reverse(array_map(fn($e) => $e->name, $this->lean->applications));
$app = $options->get('application')->value() ?? Terminal::proposeListChoice("Select an application : ", $applicationList);
$entityGenerate = new Action\Console\EntityGenerate(
application: $this->lean->getApplication($applicationList[$app]),
name: $name,
overwrite: $options->get('overwrite')->value() ?? false,
default: $options->get('default-answers')->value() ?? false,
);
if ("" === ( $entityGenerate->namespace = $options->get('namespace')->value() ?: "" )) {
$entityGenerate->readNamespace();
}
$entityGenerate->readEntity();
if ($options->get('saveForm')->value() ?? Terminal::proposeListChoice("Add a save form ?", [ true => 'Yes', false => 'No', ])) {
$entityGenerate->readSaveForm();
$hasFactory = true;
}
if ($options->get('deleteForm')->value() ?? Terminal::proposeListChoice("Add a delete form ?", [ true => 'Yes', false => 'No', ])) {
$entityGenerate->readDeleteForm();
$hasFactory = true;
}
if (isset($hasFactory) && (
$options->get('factory')->value() ?? Terminal::proposeListChoice("Add to form factory ?", [ true => 'Yes', false => 'No', ]))
) {
$entityGenerate->readFactory();
}
if ($options->get('web')->value() ?? Terminal::proposeListChoice("Add a Web controller ?", [ true => 'Yes', false => 'No', ])) {
$entityGenerate->readWeb();
}
if ($options->get('api')->value() ?? Terminal::proposeListChoice("Add an API controller ?", [ true => 'Yes', false => 'No', ])) {
$entityGenerate->readApi();
if ($options->get('api-docs')->value() ?? Terminal::proposeListChoice("Provides documentation for this route ?", [ true => 'Yes', false => 'No', ])) {
$entityGenerate->includeDocs = true;
}
}
$entityGenerate->generate();
if ($options->get('write')->value() ?? Terminal::proposeListChoice("Write to disc or output results only ?", [ true => 'Write to disc', false => 'Output results only', ])) {
$entityGenerate->writeToDisc();
}
else {
$entityGenerate->outputResults();
}
return $this->renderCLI($request, "done");
}
}

View File

@ -18,6 +18,6 @@ class Debug {
#[Route("/", name: "lean.api:debug-doc", method: "GET", description: "Documentation section")]
public function documentation(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
return $this->renderMarkdown(getenv("LEAN_API_PROJECT_PATH") . "/meta/docs/lean-api.md", [ Entity\Error::class, ], [ Form\Debug\ErrorContext::class ]);
return $this->renderMarkdown(LEAN_API_PROJECT_PATH . "/meta/docs/lean-api.md", [ Entity\Error::class, ], [ Form\Debug\ErrorContext::class ]);
}
}

View File

@ -19,7 +19,7 @@ class Errors {
#[Route("/documentation", name: "lean.api:errors-doc", method: "GET", description: "Documentation section")]
public function documentation(ServerRequestInterface $request, array $arguments) : ResponseInterface
{
return $this->renderMarkdown(getenv("LEAN_API_PROJECT_PATH") . "/meta/docs/debug/errors.md", [ Entity\Error::class, ], [ Form\Debug\ErrorContext::class ]);
return $this->renderMarkdown(LEAN_API_PROJECT_PATH . "/meta/docs/debug/errors.md", [ Entity\Error::class, ], [ Form\Debug\ErrorContext::class ]);
}
#[Route("/", name: "lean.api:errors-list", method: [ "GET" ], description: "List every errors received")]

View File

@ -8,17 +8,6 @@ use Ulmus\Entity\EntityInterface;
abstract class Delete extends Form implements \Picea\Ui\Method\FormInterface {
public function validate(FormContextInterface $context) : bool
{
if ( ! $this->getEntity()->isLoaded() ) {
$context->pushMessage($this->message::generateError(
$this->lang('lean.api.form.delete.error.entity')
));
}
return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null);
}
public function execute(FormContextInterface $context) : void
{
try {

View File

@ -10,12 +10,19 @@ use Ulmus\EntityCollection;
abstract class Form
{
protected string $contextClass;
public function __construct(
protected LanguageHandler $languageHandler,
protected MessageFactoryInterface $message,
# public EntityInterface $entity,
) {}
public function validate(FormContextInterface $context) : bool
{
return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null);
}
public function initialize(FormContextInterface $context) : void
{
if(isset($this->contextClass) && ! $context instanceof $this->contextClass ) {

View File

@ -19,8 +19,6 @@ abstract class Save extends Form implements \Picea\Ui\Method\FormInterface {
protected array $reflectedProperties;
protected string $contextClass;
public function initialize(FormContextInterface $context) : void
{
if ( ! $this->getEntity()->isLoaded() ) {
@ -74,7 +72,7 @@ abstract class Save extends Form implements \Picea\Ui\Method\FormInterface {
$context->pushErrorMessage($ex->getMessage());
}
return $saved;
return $saved ?? false;
}
protected function validateContextFields(FormContextInterface $context) : void

View File

@ -0,0 +1 @@
@todo!

View File

@ -0,0 +1 @@
@todo!

View File

@ -0,0 +1 @@
@todo!