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
9 changes: 8 additions & 1 deletion bin/ef
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
#!/usr/bin/env php
<?php

require __DIR__ . '/../vendor/autoload.php';
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
require __DIR__ . '/../vendor/autoload.php';
} elseif (file_exists(__DIR__ . '/../../../autoload.php')) {
require __DIR__ . '/../../../autoload.php';
} else {
fwrite(STDERR, "Error: cannot find autoload.php — run 'composer install' first\n");
exit(1);
}

use Symfony\Component\Console\Application;
use EntityForge\Console\GenerateCommand;
Expand Down
15 changes: 9 additions & 6 deletions src/Console/GenerateAllCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;

class GenerateAllCommand extends Command
{
Expand Down Expand Up @@ -99,11 +100,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return Command::FAILURE;
}

$pkMap = [];
foreach ($configs as $entityName => $config) {
$fields = $config['fields'] ?? [];
$candidate = strtolower($entityName) . '_id';
$pkMap[$entityName] = isset($fields['id']) ? 'id' : (isset($fields[$candidate]) ? $candidate : 'id');
$pkMap = array_fill_keys(array_keys($configs), 'id');

$strategy = 'shared';
$appYaml = getcwd() . '/config/application.yaml';
if (file_exists($appYaml)) {
$yaml = Yaml::parseFile($appYaml);
$strategy = $yaml['tenancy']['strategy'] ?? 'shared';
}

$withMigration = $input->getOption('migration');
Expand All @@ -113,7 +116,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int

foreach ($configs as $entityName => $config) {
try {
$generator->generate($config, $withMigration, $pkMap);
$generator->generate($config, $withMigration, $pkMap, $strategy);
$output->writeln("<info>Generated {$entityName}</info>");
} catch (\Throwable $e) {
$output->writeln("<error>{$e->getMessage()}</error>");
Expand Down
10 changes: 9 additions & 1 deletion src/Console/GenerateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;

class GenerateCommand extends Command
{
Expand Down Expand Up @@ -42,10 +43,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return Command::FAILURE;
}

$strategy = 'shared';
$appYaml = getcwd() . '/config/application.yaml';
if (file_exists($appYaml)) {
$yaml = Yaml::parseFile($appYaml);
$strategy = $yaml['tenancy']['strategy'] ?? 'shared';
}

$generator = new EntityGenerator();

try {
$generator->generate($config, $withMigration);
$generator->generate($config, $withMigration, [], $strategy);
$output->writeln("<info>Generated {$entity} successfully.</info>");
} catch (\Throwable $e) {
$output->writeln("<error>Error generating {$entity}: {$e->getMessage()}</error>");
Expand Down
4 changes: 2 additions & 2 deletions src/Console/MigrateAllTenantsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected function configure(): void
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = new Application(__DIR__ . '/../../config');
$app = new Application(getcwd() . '/config');
$app->boot([], false);
$config = $app->getConfig();

Expand Down Expand Up @@ -109,7 +109,7 @@ private function migrateTenant(
$dbConfig['database'] = $dbConfig['database'] . '_' . $tenantId;

try {
$runner = new MigrationRunner(new Connection($dbConfig));
$runner = new MigrationRunner(new Connection($dbConfig), fn(string $msg) => $output->writeln($msg));
$runner->run('database/migrations', $dryRun);
$output->writeln($dryRun
? "<comment>Dry run: {$tenantId}</comment>"
Expand Down
4 changes: 2 additions & 2 deletions src/Console/MigrateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ protected function configure(): void
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = new Application(__DIR__ . '/../../config');
$app = new Application(getcwd() . '/config');
$app->boot([], false);

$config = $app->getConfig()['database'];

$dryRun = (bool) $input->getOption('dry-run');
$connection = new Connection($config);
$runner = new MigrationRunner($connection);
$runner = new MigrationRunner($connection, fn(string $msg) => $output->writeln($msg));

try {
$runner->run('database/migrations', $dryRun);
Expand Down
4 changes: 2 additions & 2 deletions src/Console/RollbackCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ protected function configure(): void
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$app = new Application(__DIR__ . '/../../config');
$app = new Application(getcwd() . '/config');
$app->boot([], false);

$db = $app->getConfig()['database'];

$dryRun = (bool) $input->getOption('dry-run');
$runner = new MigrationRunner(new Connection($db));
$runner = new MigrationRunner(new Connection($db), fn(string $msg) => $output->writeln($msg));

try {
$runner->rollback('database/migrations', $dryRun);
Expand Down
2 changes: 1 addition & 1 deletion src/Console/TenantCreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$tenantId = $input->getArgument('tenantId');
$name = $input->getOption('name') ?? $tenantId;

$app = new Application(__DIR__ . '/../../config');
$app = new Application(getcwd() . '/config');
$app->boot([], false);

$service = new TenantService($app->getConfig());
Expand Down
47 changes: 20 additions & 27 deletions src/Database/MigrationRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
class MigrationRunner
{
private Connection $connection;
/** @var callable(string): void */
private $output;

public function __construct(Connection $connection)
public function __construct(Connection $connection, ?callable $output = null)
{
$this->connection = $connection;
$this->output = $output ?? static function (string $msg): void {
echo $msg . "\n";
};
}

public function run(string $path, bool $dryRun = false): void
Expand All @@ -25,7 +30,7 @@ public function run(string $path, bool $dryRun = false): void
sort($files);

if (empty($files)) {
echo "No migrations found.\n";
($this->output)('No migrations found.');
return;
}

Expand All @@ -37,12 +42,12 @@ public function run(string $path, bool $dryRun = false): void
$name = basename($file);

if (in_array($name, $executed, true)) {
echo "{$prefix}Skipped (already executed): {$name}\n";
($this->output)("{$prefix}Skipped (already executed): {$name}");
continue;
}

if ($dryRun) {
echo "{$prefix}Would execute: {$name}\n";
($this->output)("{$prefix}Would execute: {$name}");
continue;
}

Expand All @@ -54,13 +59,13 @@ public function run(string $path, bool $dryRun = false): void
try {
$pdo->exec($sql);
$this->mark($name, $batch);
echo "Executed: {$name}\n";
($this->output)("Executed: {$name}");
} catch (\Throwable $e) {
throw new \Exception("Migration failed: {$name} - " . $e->getMessage());
}
}

echo $dryRun ? "{$prefix}Done (no changes applied)\n" : "✔ Done\n";
($this->output)($dryRun ? "{$prefix}Done (no changes applied)" : '✔ Done');
}

public function rollback(string $path, bool $dryRun = false): void
Expand All @@ -70,7 +75,7 @@ public function rollback(string $path, bool $dryRun = false): void
$batch = $this->lastBatch();

if ($batch === 0) {
echo "Nothing to rollback.\n";
($this->output)('Nothing to rollback.');
return;
}

Expand All @@ -89,7 +94,7 @@ public function rollback(string $path, bool $dryRun = false): void
}

if ($dryRun) {
echo "{$prefix}Would roll back: {$migration}\n";
($this->output)("{$prefix}Would roll back: {$migration}");
continue;
}

Expand All @@ -100,18 +105,15 @@ public function rollback(string $path, bool $dryRun = false): void

try {
$pdo->exec($sql);

$pdo->prepare("DELETE FROM migrations WHERE migration = :m")
->execute(['m' => $migration]);

echo "Rolled back: {$migration}\n";

($this->output)("Rolled back: {$migration}");
} catch (\Throwable $e) {
throw new \Exception("Rollback failed: {$migration} - " . $e->getMessage());
}
}

echo $dryRun ? "{$prefix}Done (no changes applied)\n" : "✔ Rollback complete\n";
($this->output)($dryRun ? "{$prefix}Done (no changes applied)" : '✔ Rollback complete');
}

private function ensureMigrationsTable(): void
Expand All @@ -137,9 +139,7 @@ private function ensureMigrationsTable(): void
}
}

/**
* @return array<int, string>
*/
/** @return array<int, string> */
private function getExecuted(): array
{
$stmt = $this->connection->getPdo()->query("SELECT migration FROM migrations");
Expand All @@ -149,9 +149,7 @@ private function getExecuted(): array
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}

/**
* @return array<int, string>
*/
/** @return array<int, string> */
private function getExecutedSafe(): array
{
try {
Expand All @@ -163,14 +161,9 @@ private function getExecutedSafe(): array

private function mark(string $migration, int $batch): void
{
$stmt = $this->connection->getPdo()->prepare(
"INSERT INTO migrations (migration, batch) VALUES (:m, :b)"
);

$stmt->execute([
'm' => $migration,
'b' => $batch
]);
$this->connection->getPdo()
->prepare("INSERT INTO migrations (migration, batch) VALUES (:m, :b)")
->execute(['m' => $migration, 'b' => $batch]);
}

private function nextBatch(): int
Expand Down
36 changes: 13 additions & 23 deletions src/Generator/Builder/EntityBuilder.php
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
<?php

namespace EntityForge\Generator\Builder;

use EntityForge\Generator\Schema\EntitySchema;
use EntityForge\Support\Str;

class EntityBuilder
{
public function build(EntitySchema $schema): string
{
$className = $schema->getEntityName();
$fields = $schema->getFields();
$fields = $schema->getFields();

$properties = '';
foreach ($fields as $name => $type) {
$properties .= " public {$this->mapType($type)} \${$name};\n";
}

$relationsCode = $this->buildRelations($schema);
$useBlock = $this->buildUseStatements($schema);

$body = $properties . ($relationsCode !== '' ? "\n{$relationsCode}" : '');
$useBlock = $this->buildUseStatements($schema);
$body = $properties . ($relationsCode !== '' ? "\n{$relationsCode}" : '');

return <<<PHP
<?php
Expand All @@ -35,18 +36,18 @@ class {$className}
private function mapType(string $type): string
{
return match ($type) {
'int' => 'int',
'float' => 'float',
'bool' => 'bool',
'int' => 'int',
'float' => 'float',
'bool' => 'bool',
'string', 'text', 'datetime' => 'string',
default => 'mixed',
default => 'mixed',
};
}

private function buildUseStatements(EntitySchema $schema): string
{
$relations = $schema->getRelations();
$entities = [];
$entities = [];

/** @var array<string, string> $belongsTo */
$belongsTo = $relations['belongsTo'] ?? [];
Expand Down Expand Up @@ -75,7 +76,7 @@ private function buildUseStatements(EntitySchema $schema): string
private function buildRelations(EntitySchema $schema): string
{
$relations = $schema->getRelations();
$code = '';
$code = '';

/** @var array<string, string> $belongsTo */
$belongsTo = $relations['belongsTo'] ?? [];
Expand All @@ -88,22 +89,11 @@ private function buildRelations(EntitySchema $schema): string
/** @var array<string, string> $hasMany */
$hasMany = $relations['hasMany'] ?? [];
foreach ($hasMany as $entity => $foreignKey) {
$property = $this->pluralize(lcfirst((string) $entity));
$property = Str::pluralize(lcfirst((string) $entity));
$code .= " /** @var {$entity}[] Loaded via {$foreignKey} */\n";
$code .= " public array \${$property} = [];\n";
}

return $code;
}

private function pluralize(string $word): string
{
if (str_ends_with($word, 'y') && !preg_match('/[aeiou]y$/i', $word)) {
return substr($word, 0, -1) . 'ies';
}
if (preg_match('/(s|x|z|ch|sh)$/', $word)) {
return $word . 'es';
}
return $word . 's';
}
}
}
Loading
Loading