diff --git a/.gitignore b/.gitignore index 3f4d5ed..8c5f810 100644 --- a/.gitignore +++ b/.gitignore @@ -107,4 +107,5 @@ nb-configuration.xml codeclimate.json coveralls.json -clover.xml \ No newline at end of file +clover.xml +scrutinizer.xml diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 2987165..c6fc71a 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -13,42 +13,23 @@ use BlitzPHP\CodingStandard\Blitz; use Nexus\CsConfig\Factory; -use Nexus\CsConfig\Fixer\Comment\NoCodeSeparatorCommentFixer; -use Nexus\CsConfig\FixerGenerator; use PhpCsFixer\Finder; $finder = Finder::create() ->files() - ->in([ - __DIR__ . '/src', - // __DIR__ . '/tests', - // __DIR__ . '/utils', - ]) - // ->exclude(['ThirdParty']) - ->notName('#Foobar.php$#') - ->append([ - __FILE__, - __DIR__ . '/.php-cs-fixer.no-header.php', - __DIR__ . '/.php-cs-fixer.user-guide.php', - // __DIR__ . '/rector.php', - // __DIR__ . '/spark', - // __DIR__ . '/user_guide_src/renumerate.php', - ]); + ->in([__DIR__ . '/src']) + ->append([__FILE__]); $overrides = []; $options = [ - 'cacheFile' => 'build/.php-cs-fixer.cache', - 'finder' => $finder, - 'customFixers' => FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer'), - 'customRules' => [ - NoCodeSeparatorCommentFixer::name() => true, - ], + 'cacheFile' => 'build/.php-cs-fixer.cache', + 'finder' => $finder, ]; return Factory::create(new Blitz(), $overrides, $options)->forLibrary( 'Blitz PHP framework - Database Layer', 'Dimitri Sitchet Tomkeu', 'devcode.dst@gmail.com', - 2022 + 2022, ); diff --git a/src/Builder/BaseBuilder.php b/src/Builder/BaseBuilder.php index f2e2956..8246c8b 100644 --- a/src/Builder/BaseBuilder.php +++ b/src/Builder/BaseBuilder.php @@ -75,7 +75,7 @@ class BaseBuilder implements BuilderInterface * Colonnes à sélectionner */ protected array $columns = []; - + /** * Liste des conditions WHERE */ @@ -124,7 +124,7 @@ class BaseBuilder implements BuilderInterface /** * Option DISTINCT */ - protected string|bool $distinct = false; + protected bool|string $distinct = false; /** * Option IGNORE @@ -170,9 +170,6 @@ class BaseBuilder implements BuilderInterface */ protected array $afterQueryCallbacks = []; - /** - * @var QueryCompiler - */ protected QueryCompiler $compiler; /** @@ -186,7 +183,7 @@ public function __construct(protected ConnectionInterface $db) /** * Methode magique pour recupere une valeur interne du Builder - * + * * @internal */ public function __get(string $name): mixed @@ -204,12 +201,12 @@ public function __get(string $name): mixed protected function createCompiler(): QueryCompiler { $driver = $this->db->getDriver(); - - return match($driver) { - 'mysql' => new MySQLCompiler($this->db), - 'pgsql' => new PostgreCompiler($this->db), + + return match ($driver) { + 'mysql' => new MySQLCompiler($this->db), + 'pgsql' => new PostgreCompiler($this->db), 'sqlite' => new SQLiteCompiler($this->db), - default => throw new DatabaseException("Unsupported driver: {$driver}") + default => throw new DatabaseException("Unsupported driver: {$driver}"), }; } @@ -234,7 +231,7 @@ public function newQuery(): static /** * Définit un statut de mode de test. */ - public function testMode(bool $mode = true): self + public function testMode(bool $mode = true): static { $this->testMode = $mode; @@ -244,15 +241,13 @@ public function testMode(bool $mode = true): self /** * Définit un statut de l'état d'attente de la requête. */ - public function pending(bool $state = true): self + public function pending(bool $state = true): static { $this->pending = $state; return $this; } - - /** * Recupere le nom de la table principale. */ @@ -270,10 +265,10 @@ public function getTable(): string * * @param list|string|null $from */ - public function from($from, bool $overwrite = false): self + public function from($from, bool $overwrite = false): static { if ($from === null) { - $this->from = ''; + $this->from = ''; $this->tables = []; return $this; @@ -282,7 +277,7 @@ public function from($from, bool $overwrite = false): self if ($overwrite) { $this->tables = []; } - + if (is_string($from)) { $from = explode(',', $from); } @@ -300,11 +295,11 @@ public function from($from, bool $overwrite = false): self /** * @param BaseBuilder $from */ - public function fromSubquery(BuilderInterface $from, string $alias = ''): self + public function fromSubquery(BuilderInterface $from, string $alias = ''): static { $table = $this->buildSubquery($from, true, $alias); $this->db->addTableAlias($alias); - + $this->reset(); $this->tables = [$table]; $this->bindings->merge($from->bindings); @@ -319,7 +314,7 @@ public function fromSubquery(BuilderInterface $from, string $alias = ''): self * * @alias self::from() */ - public function table($from): self + public function table($from): static { return $this->from($from, true); } @@ -327,7 +322,7 @@ public function table($from): self /** * Définit la table dans laquelle les données seront insérées */ - public function into(string $table): self + public function into(string $table): static { return $this->table($table); } @@ -335,23 +330,23 @@ public function into(string $table): self /** * Définit les colonnes à sélectionner * - * @param array|string|Expression $columns Colonnes à sélectionner + * @param array|Expression|string $columns Colonnes à sélectionner */ - public function select($columns = '*'): self + public function select($columns = '*'): static { // Gestion de l'ancienne signature avec limit/offset if (func_num_args() > 1 && is_int(func_get_arg(1))) { if (! $this->testMode) { - trigger_error( + @trigger_error( 'Passing limit/offset to select() is deprecated. Use limit() and offset() methods instead.', - E_USER_DEPRECATED + E_USER_DEPRECATED, ); } - - $limit = func_get_arg(1); + + $limit = func_get_arg(1); $offset = func_num_args() > 2 ? func_get_arg(2) : null; $this->limit($limit, $offset); - + $columns = func_get_arg(0); } @@ -386,61 +381,65 @@ public function select($columns = '*'): self /** * Sélectionne avec un alias explicite */ - public function selectAs(string $column, string $alias): self + public function selectAs(string $column, string $alias): static { $this->columns[] = $this->buildColumnName($column) . ' AS ' . $this->db->escapeIdentifiers($alias); - + return $this->asCrud('select'); } /** * Sélectionne une expression avec alias */ - public function selectExpr(string $expression, string $alias, array $bindings = []): self + public function selectExpr(string $expression, string $alias, array $bindings = []): static { $this->columns[] = new Expression($expression); $this->bindings->addMany($bindings); + return $this->asCrud('select'); } /** * Ajoute une sous requete a la selection */ - public function selectSubquery(BuilderInterface $subquery, string $as): self + public function selectSubquery(BuilderInterface $subquery, string $as): static { $this->columns[] = $this->buildSubquery($subquery, true, $as); + return $this->asCrud('select'); } /** * Ajoute une clause DISTINCT */ - public function distinct(bool $value = true): self + public function distinct(bool $value = true): static { $this->distinct = $value; + return $this->asCrud('select'); } /** * Ajoute une clause DISTINCT ON (PostgreSQL) */ - public function distinctOn(array $columns): self + public function distinctOn(array $columns): static { if ($this->db->getDriver() !== 'pgsql') { throw new DatabaseException('DISTINCT ON is only supported by PostgreSQL'); } $this->distinct = 'DISTINCT ON (' . implode(', ', array_map([$this->db, 'escapeIdentifiers'], $columns)) . ')'; + return $this->asCrud('select'); } - + /** * Ajoute une clause LIMIT */ - public function limit(int $limit, ?int $offset = null): self + public function limit(int $limit, ?int $offset = null): static { $this->limit = max(0, $limit); - + if ($offset !== null) { $this->offset = max(0, $offset); } @@ -451,10 +450,10 @@ public function limit(int $limit, ?int $offset = null): self /** * Ajoute une clause OFFSET */ - public function offset(int $offset, ?int $limit = null): self + public function offset(int $offset, ?int $limit = null): static { $this->offset = max(0, $offset); - + if ($limit !== null) { $this->limit = max(0, $limit); } @@ -465,24 +464,25 @@ public function offset(int $offset, ?int $limit = null): self /** * Ajoute une option IGNORE */ - public function ignore(bool $value = true): self + public function ignore(bool $value = true): static { $this->ignore = $value; + return $this; } /** * Définit les valeurs pour INSERT/UPDATE - * + * * @param array|object|string $key Nom du champ, ou tableau de paire champs/valeurs * @param mixed $value Valeur du champ, si $key est un simple champ */ - public function set($key, $value = ''): self + public function set($key, $value = ''): static { if (is_string($key)) { $key = [$key => $value]; } - + $key = $this->objectToArray($key); foreach ($key as $k => $v) { @@ -504,12 +504,12 @@ public function set($key, $value = ''): self */ public function insert(array|object $data = []) { - return $this->run('successfulable', function() use($data) { + return $this->run('successfulable', function () use ($data) { $this->crud = 'insert'; $this->set($data); - if ($this->values === [] && !$this->pending) { + if ($this->values === [] && ! $this->pending) { throw new DatabaseException('You must give entries to insert.'); } }); @@ -528,8 +528,8 @@ public function insertIgnore(array|object $data = []) /** * Insertion multiple * - * @param list $data Tableau a deux dimensions contenant les valeurs a inserer - * @param int $chunkSize Taille optimale des chunks + * @param list $data Tableau a deux dimensions contenant les valeurs a inserer + * @param int $chunkSize Taille optimale des chunks * * @return int|string */ @@ -547,32 +547,32 @@ public function bulkInsert(array $data, bool $ignore = false, int $chunkSize = 1 $placeholders = '(' . implode(', ', array_fill(0, count($columns), '?')) . ')'; $columnList = implode(', ', array_map([$this->db, 'escapeIdentifiers'], $columns)); $table = $this->db->escapeIdentifiers($this->getTable()); - + $totalAffected = 0; $chunks = array_chunk($data, $chunkSize); $allSql = []; - $callback = function() use ($ignore, $chunks, $table, $columnList, $placeholders, &$totalAffected, &$allSql) { + $callback = function () use ($ignore, $chunks, $table, $columnList, $placeholders, &$totalAffected, &$allSql) { foreach ($chunks as $chunk) { - $values = array_fill(0, count($chunk), $placeholders); + $values = array_fill(0, count($chunk), $placeholders); $bindings = []; - + foreach ($chunk as $row) { array_push($bindings, ...array_values((array) $row)); } - + $sql = $this->compiler->compileInsertion($table, $columnList, implode(', ', $values), $ignore); - + if ($this->testMode) { $allSql[] = $sql; } else { $totalAffected += $this->db->affectingStatement($sql, $bindings); } } - + return [$allSql, $totalAffected]; }; - + [$allSql, $totalAffected] = $this->db->transaction($callback); return $this->testMode ? implode('; ', $allSql) : $totalAffected; @@ -581,8 +581,8 @@ public function bulkInsert(array $data, bool $ignore = false, int $chunkSize = 1 /** * Insertion multiple avec IGNORE * - * @param list $data Tableau a deux dimensions contenant les valeurs a inserer - * @param int $chunkSize Taille optimale des chunks + * @param list $data Tableau a deux dimensions contenant les valeurs a inserer + * @param int $chunkSize Taille optimale des chunks * * @return int|string */ @@ -593,29 +593,29 @@ public function bulkInsertIgnore(array $data, int $chunkSize = 100) /** * UPSERT (INSERT ... ON DUPLICATE KEY UPDATE) - * + * * @return int|static|string */ public function upsert(array $values, array $uniqueBy, ?array $update = null) { - return $this->run('affectable', function() use($values, $uniqueBy, $update) { + return $this->run('affectable', function () use ($values, $uniqueBy, $update) { $this->crud = 'upsert'; - + // Support des insertions multiples if (isset($values[0]) && is_array($values[0])) { $this->values = $values; } else { $this->values = [$values]; } - - $this->uniqueBy = $uniqueBy; + + $this->uniqueBy = $uniqueBy; $this->updateColumns = $update ?? array_keys($values[0] ?? $values); }); } /** * INSERT OR IGNORE - * + * * @return int|static|string */ public function insertOrIgnore(array $values) @@ -632,12 +632,12 @@ public function insertOrIgnore(array $values) */ public function update(array|object $data = []) { - return $this->run('affectable', function() use($data) { + return $this->run('affectable', function () use ($data) { $this->crud = 'update'; $this->set($data); - if ($this->values === [] && !$this->pending) { + if ($this->values === [] && ! $this->pending) { throw new DatabaseException('You must give entries to insert.'); } }); @@ -646,18 +646,18 @@ public function update(array|object $data = []) /** * Exécute une requête de remplacement. * - * @param array|object $data Tableau ou objet de clés et de valeurs à remplacer + * @param array|object $data Tableau ou objet de clés et de valeurs à remplacer * * @return int|static|string */ public function replace(array|object $data = []) { - return $this->run('affectable', function() use($data) { + return $this->run('affectable', function () use ($data) { $this->crud = 'replace'; $this->set($data); - if ($this->values === [] && !$this->pending) { + if ($this->values === [] && ! $this->pending) { throw new DatabaseException('You must give entries to insert.'); } }); @@ -670,7 +670,7 @@ public function updateOrInsert(array $attributes, array $values = []): bool { $exists = $this->clone()->where($attributes)->exists(); - if (!$exists) { + if (! $exists) { return $this->insert(array_merge($attributes, $values)) !== false; } @@ -689,7 +689,7 @@ public function firstOrCreate(array $attributes, array $values = []) } $this->insert(array_merge($attributes, $values)); - + return $this->clone()->where($attributes)->first(); } @@ -710,13 +710,13 @@ public function firstOrNew(array $attributes, array $values = []) /** * Exécute une requête de suppression. * - * @param array $where Conditions de suppression + * @param array $where Conditions de suppression * * @return int|self|string */ public function delete(?array $where = null, ?int $limit = null) { - return $this->run('affectable', function() use($where, $limit) { + return $this->run('affectable', function () use ($where, $limit) { $this->crud = 'delete'; if ($where !== null && $where !== []) { @@ -739,7 +739,7 @@ public function delete(?array $where = null, ?int $limit = null) */ public function truncate(?string $table = null) { - return $this->run('successfulable', function() use($table) { + return $this->run('successfulable', function () use ($table) { $this->crud = 'truncate'; if ($table !== null && $table !== '') { @@ -747,16 +747,16 @@ public function truncate(?string $table = null) } }); } - + /** * Exécute la requête construite - * + * * @return Result */ public function execute(): ResultInterface { $this->applyBeforeQueryCallbacks(); - + try { $result = $this->query($this->toSql(), $this->getBindings()); @@ -778,7 +778,7 @@ public function query(string $sql, array $params = []): ResultInterface /** * @param 'affectable'|'successfulable' $as - * + * * @return ($as is 'affectable' ? int|static|string : bool|static|string) */ protected function run(string $as, Closure $callback) @@ -815,9 +815,7 @@ public function result(int|string $type = PDO::FETCH_OBJ): array /** * Récupère les résultats de la requete dans une collection - * - * @param int|string $type - * + * * @return Collection */ public function collect(int|string $type = PDO::FETCH_OBJ): Collection @@ -827,6 +825,8 @@ public function collect(int|string $type = PDO::FETCH_OBJ): Collection /** * Récupère le premier résultat + * + * @param mixed $type */ public function first($type = PDO::FETCH_OBJ): mixed { @@ -835,6 +835,8 @@ public function first($type = PDO::FETCH_OBJ): mixed /** * Récupère une ligne spécifique + * + * @param mixed $type */ public function row(int $index, $type = PDO::FETCH_OBJ): mixed { @@ -848,7 +850,7 @@ public function row(int $index, $type = PDO::FETCH_OBJ): mixed */ public function value(array|string $name) { - $names = (array) $name; + $names = (array) $name; $values = []; $row = $this->select($names)->first(PDO::FETCH_OBJ); @@ -869,7 +871,7 @@ public function value(array|string $name) */ public function values(array|string $name): array { - $names = (array) $name; + $names = (array) $name; $columns = []; $rows = $this->select($names)->all(PDO::FETCH_OBJ); @@ -901,41 +903,41 @@ public function exists(): bool */ public function doesntExist(): bool { - return !$this->exists(); + return ! $this->exists(); } /** * Verrouillage pour mise à jour */ - public function lockForUpdate(): self + public function lockForUpdate(): static { - $this->lock = match($this->db->getDriver()) { + $this->lock = match ($this->db->getDriver()) { 'sqlite' => '', // SQLite ne supporte pas le verrouillage - default => 'FOR UPDATE' + default => 'FOR UPDATE', }; - + return $this; } /** * Verrouillage partagé */ - public function sharedLock(): self + public function sharedLock(): static { - $this->lock = match($this->db->getDriver()) { - 'mysql' => 'LOCK IN SHARE MODE', - 'pgsql' => 'FOR SHARE', + $this->lock = match ($this->db->getDriver()) { + 'mysql' => 'LOCK IN SHARE MODE', + 'pgsql' => 'FOR SHARE', 'sqlite' => '', // SQLite ne supporte pas le verrouillage - default => 'LOCK IN SHARE MODE' + default => 'LOCK IN SHARE MODE', }; - + return $this; } /** * Verrouillage SKIP LOCKED */ - public function skipLocked(): self + public function skipLocked(): static { $this->lock .= ' SKIP LOCKED'; @@ -945,7 +947,7 @@ public function skipLocked(): self /** * Verrouillage NOWAIT */ - public function lockNowait(): self + public function lockNowait(): static { $this->lock .= ' NOWAIT'; @@ -954,9 +956,9 @@ public function lockNowait(): self /** * Incremente un champ numerique par la valeur specifiee. - * + * * @param array $extra - * + * * @return int<0, max>|static|string * * @throws DatabaseException @@ -970,8 +972,8 @@ public function increment(string $column, float|int $value = 1, array $extra = [ * Incrémente les valeurs des colonnes spécifiées par les montants donnés. * * @param array $columns - * @param array $extra - * + * @param array $extra + * * @return int<0, max>|static|string * * @throws InvalidArgumentException @@ -980,8 +982,9 @@ public function incrementEach(array $columns, array $extra = []) { foreach ($columns as $column => $amount) { if (! is_numeric($amount)) { - throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '$column'."); - } elseif (! is_string($column)) { + throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '{$column}'."); + } + if (! is_string($column)) { throw new InvalidArgumentException('Non-associative array passed to incrementEach method.'); } @@ -993,9 +996,9 @@ public function incrementEach(array $columns, array $extra = []) /** * Decremente un champ numerique par la valeur specifiee. - * + * * @param array $extra - * + * * @return int<0, max>|static|string * * @throws DatabaseException @@ -1009,8 +1012,8 @@ public function decrement(string $column, float|int $value = 1, array $extra = [ * Décrémente les valeurs des colonnes spécifiées par les montants donnés. * * @param array $columns - * @param array $extra - * + * @param array $extra + * * @return int<0, max>|static|string * * @throws InvalidArgumentException @@ -1019,8 +1022,9 @@ public function decrementEach(array $columns, array $extra = []) { foreach ($columns as $column => $amount) { if (! is_numeric($amount)) { - throw new InvalidArgumentException("Non-numeric value passed as decrement amount for column: '$column'."); - } elseif (! is_string($column)) { + throw new InvalidArgumentException("Non-numeric value passed as decrement amount for column: '{$column}'."); + } + if (! is_string($column)) { throw new InvalidArgumentException('Non-associative array passed to decrementEach method.'); } @@ -1032,7 +1036,7 @@ public function decrementEach(array $columns, array $extra = []) /** * Enregistre une closure à invoquer avant l'exécution de la requête. - * + * * @param callable($this): void $callback */ public function beforeQuery(callable $callback): static @@ -1041,14 +1045,14 @@ public function beforeQuery(callable $callback): static return $this; } - + /** * Invoque les callbacks de modification "avant requête". */ public function applyBeforeQueryCallbacks(): void { foreach ($this->beforeQueryCallbacks as $callback) { - call_user_func($callback, $this); + $callback($this); } $this->beforeQueryCallbacks = []; @@ -1056,7 +1060,7 @@ public function applyBeforeQueryCallbacks(): void /** * Enregistre une closure à invoquer après l'exécution de la requête. - * + * * @param callable(mixed): mixed $callback */ public function afterQuery(callable $callback): static @@ -1072,19 +1076,16 @@ public function afterQuery(callable $callback): static public function applyAfterQueryCallbacks(mixed $result): mixed { foreach ($this->afterQueryCallbacks as $callback) { - $result = call_user_func($callback, $result) ?: $result; + $result = $callback($result) ?: $result; } return $result; } - /** - * - */ public function explain(): array { $sql = 'EXPLAIN ' . $this->toSql(); - + return $this->query($sql, $this->getBindings())->resultArray(); } @@ -1095,7 +1096,7 @@ public function toSql(): string { return $this->compiler->compile($this); } - + /** * Récupère le SQL et réinitialise le builder */ @@ -1103,7 +1104,7 @@ public function sql(bool $preserve = false): string { $sql = $this->toRawSql(); - if (!$preserve) { + if (! $preserve) { $this->reset(); } @@ -1123,18 +1124,18 @@ public function clearBindings(?string $context = null): void */ public function getBindings(): array { - $types = match($this->crud) { - 'select' => ['where', 'having', 'order', 'union'], - 'insert', 'replace' => ['values'], - 'upsert' => ['values', 'uniqueBy'], // Si on a des bindings pour les conflits - 'update' => ['values', 'where', 'join'], - 'delete' => ['where', 'join'], - 'truncate' => null, // Pas de bindings pour TRUNCATE - default => [], // Fallback à tous + $types = match ($this->crud) { + 'select' => ['where', 'having', 'order', 'union'], + 'insert', 'replace' => ['values'], + 'upsert' => ['values', 'uniqueBy'], // Si on a des bindings pour les conflits + 'update' => ['values', 'where', 'join'], + 'delete' => ['where', 'join'], + 'truncate' => null, // Pas de bindings pour TRUNCATE + default => [], // Fallback à tous }; - return $types === null - ? [] + return $types === null + ? [] : $this->db->prepareBindings($this->bindings->getOrdered($types)); } @@ -1156,7 +1157,7 @@ public function toRawSql(): string /** * Affiche le SQL pour debug */ - public function dump(): self + public function dump(): static { dump($this->toRawSql()); @@ -1174,25 +1175,25 @@ public function dd(): void /** * Réinitialise le builder */ - public function reset(): self - { - $this->from = ''; - $this->tables = []; - $this->columns = []; - $this->wheres = []; - $this->joins = []; - $this->orders = []; - $this->groups = []; - $this->havings = []; - $this->unions = []; - $this->values = []; - $this->bindings = new BindingCollection(); - $this->distinct = false; - $this->ignore = false; - $this->limit = null; - $this->offset = null; - $this->lock = null; - $this->uniqueBy = []; + public function reset(): static + { + $this->from = ''; + $this->tables = []; + $this->columns = []; + $this->wheres = []; + $this->joins = []; + $this->orders = []; + $this->groups = []; + $this->havings = []; + $this->unions = []; + $this->values = []; + $this->bindings = new BindingCollection(); + $this->distinct = false; + $this->ignore = false; + $this->limit = null; + $this->offset = null; + $this->lock = null; + $this->uniqueBy = []; $this->updateColumns = []; $this->db->setAliasedTables([]); @@ -1204,21 +1205,21 @@ public function reset(): self */ public function clone(): static { - $clone = clone $this; + $clone = clone $this; $clone->bindings = clone $this->bindings; - + return $clone; } /** * Définit le type d'opération CRUD à effectuer - * + * * @internal */ - protected function asCrud(string $type): self + protected function asCrud(string $type): static { $this->crud = $type; - + return $this; } @@ -1248,7 +1249,7 @@ protected function objectToArray(array|object $object): array /** * Supprime l'alias d'un nom de table - * + * * @internal */ protected function removeAlias(string $from): string @@ -1264,10 +1265,10 @@ protected function removeAlias(string $from): string /** * Construit une sous-requête - * - * @param self|Closure $builder + * + * @param Closure|self $builder */ - protected function buildSubquery(Closure|BuilderInterface $builder, bool $wrapped = false, string $alias = ''): string + protected function buildSubquery(BuilderInterface|Closure $builder, bool $wrapped = false, string $alias = ''): string { if ($builder instanceof Closure) { $builder($builder = $this->db->newQuery()); @@ -1294,12 +1295,12 @@ protected function buildSubquery(Closure|BuilderInterface $builder, bool $wrappe protected function buildColumnName(string $column): string { $column = trim($column); - + // Cas spécial: expression SQL brute (ne pas parser) if (preg_match('/^\(.*\)$/', $column) || Utils::isRawExpression($column)) { return $column; } - + $parts = explode(' ', $column); $column = array_shift($parts); $operator = implode(' ', $parts); @@ -1316,13 +1317,13 @@ protected function buildColumnName(string $column): string } // Étape 2: Extraction des alias (améliorée) - if ($operator !== '' && !Utils::hasOperator($operator)) { + if ($operator !== '' && ! Utils::hasOperator($operator)) { if (Utils::isAlias($operator)) { - $alias = Utils::extractAlias($operator); + $alias = Utils::extractAlias($operator); $operator = ''; } else { // Ce n'est pas un alias, c'est un opérateur ou une clause - $column = implode(' ', [$column, $operator]); + $column = implode(' ', [$column, $operator]); $operator = ''; } } @@ -1331,12 +1332,12 @@ protected function buildColumnName(string $column): string $functionPattern = '/^(' . implode('|', array_map('preg_quote', Utils::SQL_FUNCTIONS)) . ')\s*\(\s*(.+?)\s*\)$/i'; if (preg_match($functionPattern, $column, $matches)) { $aggregate = $matches[1]; - $column = $matches[2]; + $column = $matches[2]; } // Étape 4: Gestion des alias de table $column = Utils::formatQualifiedColumn($this->db, $column); - + // Étape 5: Reconstruction avec fonction d'agrégation if ($aggregate !== null) { $column = strtoupper($aggregate) . '(' . $column . ')'; diff --git a/src/Builder/BindingCollection.php b/src/Builder/BindingCollection.php index 3fd3f7a..74cd998 100644 --- a/src/Builder/BindingCollection.php +++ b/src/Builder/BindingCollection.php @@ -22,7 +22,7 @@ class BindingCollection * Types de bindings supportés */ public const TYPES = [ - 'select', 'from', 'join', 'where', 'having', + 'select', 'from', 'join', 'where', 'having', 'order', 'union', 'values', 'uniqueBy', ]; @@ -51,20 +51,20 @@ public function __construct() /** * Ajoute un binding dans un contexte spécifique * - * @param mixed $value Valeur à binder - * @param string $type Contexte ('where', 'values', etc.) + * @param mixed $value Valeur à binder + * @param string $type Contexte ('where', 'values', etc.) * @param int|null $pdoType Type PDO (optionnel) - * + * * @throws InvalidArgumentException */ public function add(mixed $value, string $type = 'where', ?int $pdoType = null): self { - if (!in_array($type, self::TYPES, true)) { + if (! in_array($type, self::TYPES, true)) { throw new InvalidArgumentException("Type de binding invalide: {$type}"); } $this->bindings[$type][] = $value; - $this->types[$type][] = $pdoType ?? $this->guessType($value); + $this->types[$type][] = $pdoType ?? $this->guessType($value); return $this; } @@ -77,20 +77,20 @@ public function addMany(array $values, string $type = 'where'): self foreach ($values as $value) { $this->add($value, $type); } - + return $this; } /** * (Re)Définit un ensemble de bindings pour un contexte - * + * * Cet méthode écrase les bindings existants pour le contexte donné */ public function set(array $values, string $context = 'where'): static { $this->clear($context); $this->addMany($values, $context); - + return $this; } @@ -101,7 +101,7 @@ public function addNamed(string $name, mixed $value, string $context = 'where', { $this->bindings[$context][$name] = $value; $this->types[$context][$name] = $pdoType ?? $this->guessType($value); - + return $this; } @@ -121,7 +121,7 @@ public function get(string $context, ?string $name = null) * Récupère tous les bindings dans l'ordre de compilation * * @param list $contexts - * + * * @return list */ public function getOrdered(array $contexts = []): array @@ -131,18 +131,19 @@ public function getOrdered(array $contexts = []): array } $result = []; + foreach ($contexts as $context) { - if (!empty($this->bindings[$context])) { + if (! empty($this->bindings[$context])) { array_push($result, ...$this->bindings[$context]); } } - + return $result; } /** * Récupère tous les types dans l'ordre - * + * * @param list $types * * @return list @@ -154,12 +155,13 @@ public function getTypesOrdered(array $types = []): array } $result = []; + foreach ($types as $type) { - if (!empty($this->types[$type])) { + if (! empty($this->types[$type])) { array_push($result, ...$this->types[$type]); } } - + return $result; } @@ -168,7 +170,7 @@ public function getTypesOrdered(array $types = []): array */ public function has(string $context): bool { - return !empty($this->bindings[$context]); + return ! empty($this->bindings[$context]); } /** @@ -179,7 +181,7 @@ public function count(?string $context = null): int if ($context !== null) { return count($this->bindings[$context] ?? []); } - + return array_sum(array_map('count', $this->bindings)); } @@ -198,11 +200,11 @@ public function clear(?string $context = null): self { if ($context !== null) { return $this->clearContext($context); - } - + } + foreach (self::TYPES as $t) { $this->bindings[$t] = []; - $this->types[$t] = []; + $this->types[$t] = []; } return $this; @@ -215,7 +217,7 @@ public function clearContext(string $context): self { if (isset($this->bindings[$context])) { $this->bindings[$context] = []; - $this->types[$context] = []; + $this->types[$context] = []; } return $this; @@ -238,12 +240,12 @@ public function merge(self $collection): self * Retire les expressions des bindings (elles ne doivent pas être bindées) * * @param list $bindings - * + * * @return list */ public function clean(array $bindings): array { - return array_filter($bindings, fn($binding) => !$binding instanceof Expression); + return array_filter($bindings, static fn ($binding) => ! $binding instanceof Expression); } /** @@ -251,12 +253,12 @@ public function clean(array $bindings): array */ protected function guessType(mixed $value): int { - return match(true) { - is_int($value) => PDO::PARAM_INT, - is_bool($value) => PDO::PARAM_BOOL, - is_null($value) => PDO::PARAM_NULL, + return match (true) { + is_int($value) => PDO::PARAM_INT, + is_bool($value) => PDO::PARAM_BOOL, + null === $value => PDO::PARAM_NULL, $value instanceof PDOStatement => PDO::PARAM_STMT, - default => PDO::PARAM_STR, + default => PDO::PARAM_STR, }; } diff --git a/src/Builder/Compilers/MySQL.php b/src/Builder/Compilers/MySQL.php index c19042e..661ec45 100644 --- a/src/Builder/Compilers/MySQL.php +++ b/src/Builder/Compilers/MySQL.php @@ -21,7 +21,7 @@ class MySQL extends QueryCompiler public function compileUpdate(BaseBuilder $builder): string { $table = $this->db->makeTableName($builder->getTable()); - + $sql = ["UPDATE {$table}"]; if ([] !== $builder->joins) { @@ -29,12 +29,13 @@ public function compileUpdate(BaseBuilder $builder): string } $sets = []; + foreach ($builder->values as $column => $value) { $column = $this->db->escapeIdentifiers($column); $sets[] = "{$column} = " . $this->wrapValue($value); } - $sql[] = "SET " . implode(', ', $sets); + $sql[] = 'SET ' . implode(', ', $sets); if ([] !== $builder->wheres) { $sql[] = 'WHERE'; @@ -85,7 +86,7 @@ public function compileInsertion(string $table, string $columns, string $values, public function compileTruncate(BaseBuilder $builder): string { $table = $this->db->escapeIdentifiers($builder->getTable()); - + return "TRUNCATE TABLE {$table}"; } @@ -104,14 +105,15 @@ protected function compileUpsertment(string $table, string $columns, string $val { // Construire la partie ON DUPLICATE KEY UPDATE $updates = []; + foreach ($builder->updateColumns as $column) { - if (!in_array($column, $builder->uniqueBy)) { + if (! in_array($column, $builder->uniqueBy, true)) { $escapedColumn = $this->db->escapeIdentifiers($column); - $updates[] = "{$escapedColumn} = VALUES({$escapedColumn})"; + $updates[] = "{$escapedColumn} = VALUES({$escapedColumn})"; } } - $updateSql = !empty($updates) ? ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates) : ''; + $updateSql = ! empty($updates) ? ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates) : ''; return "INSERT INTO {$table} ({$columns}) VALUES {$values}{$updateSql}"; } @@ -123,7 +125,7 @@ protected function compileJsonContains(string $column, $value, bool $not = false { $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + return "{$notStr}JSON_CONTAINS({$column}, ?)"; } @@ -134,7 +136,7 @@ protected function compileJsonContainsKey(string $column, bool $not = false): st { $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + return "JSON_CONTAINS_PATH({$column}, 'one', ?) {$notStr}= 1"; } @@ -144,7 +146,7 @@ protected function compileJsonContainsKey(string $column, bool $not = false): st protected function compileJsonLength(string $column, string $operator, int $value): string { $column = $this->db->escapeIdentifiers($column); - + return "JSON_LENGTH({$column}) {$operator} ?"; } @@ -155,7 +157,7 @@ public function compileJsonSearch(string $column, string $value, bool $not = fal { $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + return "JSON_SEARCH({$column}, 'one', ?) IS {$notStr}NULL"; } @@ -164,9 +166,9 @@ public function compileJsonSearch(string $column, string $value, bool $not = fal */ protected function compileAnyAll(string $type, string $column, string $operator, array $values): string { - $column = $this->db->escapeIdentifiers($column); + $column = $this->db->escapeIdentifiers($column); $placeholders = implode(', ', array_fill(0, count($values), '?')); - + return "{$column} {$operator} {$type} ({$placeholders})"; } -} \ No newline at end of file +} diff --git a/src/Builder/Compilers/Postgre.php b/src/Builder/Compilers/Postgre.php index da25a05..51611b6 100644 --- a/src/Builder/Compilers/Postgre.php +++ b/src/Builder/Compilers/Postgre.php @@ -64,20 +64,20 @@ public function compileUpdate(BaseBuilder $builder): string { if ($builder->joins === []) { $sql = $this->compileUpdateStandard($builder); - + return "{$sql} RETURNING *"; } return $this->compileUpdateWithFrom($builder); } - + /** * {@inheritDoc} */ public function compileTruncate(BaseBuilder $builder): string { $table = $this->db->escapeIdentifiers($builder->getTable()); - + return "TRUNCATE TABLE {$table} RESTART IDENTITY"; } @@ -87,7 +87,7 @@ public function compileTruncate(BaseBuilder $builder): string public function compileReplace(BaseBuilder $builder): string { // PostgreSQL n'a pas de REPLACE, on utilise INSERT ... ON CONFLICT - + $sql = parent::compileReplace($builder); return "{$sql} ON CONFLICT DO UPDATE SET " . $this->compileUpdateSet($builder) . ' RETURNING *'; @@ -99,7 +99,7 @@ public function compileReplace(BaseBuilder $builder): string protected function compileReplacement(string $table, string $columns, string $values): string { // PostgreSQL n'a pas de REPLACE, on utilisera INSERT ... ON CONFLICT - + return "INSERT INTO {$table} ({$columns}) VALUES {$values}"; } @@ -109,27 +109,29 @@ protected function compileReplacement(string $table, string $columns, string $va protected function compileUpdateSet(BaseBuilder $builder): string { $sets = []; + foreach ($builder->values as $column => $value) { $column = $this->db->escapeIdentifiers($column); $sets[] = "{$column} = EXCLUDED.{$column}"; } + return implode(', ', $sets); } - /** * {@inheritDoc} */ protected function compileUpsertment(string $table, string $columns, string $values, BaseBuilder $builder): string { // Construire la clause ON CONFLICT - $uniqueBy = array_map([$this->db, 'escapeIdentifiers'], $builder->uniqueBy); + $uniqueBy = array_map([$this->db, 'escapeIdentifiers'], $builder->uniqueBy); $constraint = 'ON CONFLICT (' . implode(', ', $uniqueBy) . ') DO UPDATE SET '; - + $updates = []; + foreach ($builder->updateColumns as $column) { - if (!in_array($column, $builder->uniqueBy)) { - $col = $this->db->escapeIdentifiers($column); + if (! in_array($column, $builder->uniqueBy, true)) { + $col = $this->db->escapeIdentifiers($column); $updates[] = $col . ' = EXCLUDED.' . $col; } } @@ -144,11 +146,11 @@ protected function compileJsonContains(string $column, $value, bool $not = false { $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + // PostgreSQL utilise l'opérateur @> pour JSON contains return "{$column} {$notStr}@> ?::jsonb"; } - + /** * {@inheritDoc} */ @@ -156,7 +158,7 @@ protected function compileJsonContainsKey(string $column, bool $not = false): st { $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + // PostgreSQL utilise l'opérateur ? pour vérifier l'existence d'une clé return "{$column} {$notStr}? ?"; } @@ -167,7 +169,7 @@ protected function compileJsonContainsKey(string $column, bool $not = false): st protected function compileJsonLength(string $column, string $operator, int $value): string { $column = $this->db->escapeIdentifiers($column); - + // PostgreSQL utilise jsonb_array_length() pour les tableaux JSON return "jsonb_array_length({$column}) {$operator} ?"; } @@ -181,7 +183,7 @@ protected function compileJsonSearch(string $column, string $value, bool $not = // On peut utiliser jsonb_path_exists() pour des recherches avancées $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + return "jsonb_path_exists({$column}, ?) IS {$notStr}TRUE"; } @@ -190,9 +192,9 @@ protected function compileJsonSearch(string $column, string $value, bool $not = */ protected function compileAnyAll(string $type, string $column, string $operator, array $values): string { - $column = $this->db->escapeIdentifiers($column); + $column = $this->db->escapeIdentifiers($column); $placeholders = implode(', ', array_fill(0, count($values), '?')); - + return "{$column} {$operator} {$type} ({$placeholders})"; } @@ -202,29 +204,30 @@ protected function compileAnyAll(string $type, string $column, string $operator, protected function compileUpdateWithFrom(BaseBuilder $builder): string { $table = $this->db->makeTableName($builder->getTable()); - + $sets = []; + foreach ($builder->values as $column => $value) { $column = $this->db->escapeIdentifiers($column); $sets[] = "{$column} = " . $this->wrapValue($value); } - $sql = ["UPDATE {$table}"]; - $sql[] = "SET " . implode(', ', $sets); + $sql = ["UPDATE {$table}"]; + $sql[] = 'SET ' . implode(', ', $sets); // Construction de la clause FROM - $fromTables = []; + $fromTables = []; $joinConditions = []; foreach ($builder->joins as $join) { if ($join instanceof JoinClause) { $fromTables[] = $join->getTable(); - + // Convertir les conditions ON en conditions WHERE foreach ($join->getConditions() as $condition) { if ($condition['type'] === 'basic') { - $joinConditions[] = $condition['first'] . ' ' . - $condition['operator'] . ' ' . + $joinConditions[] = $condition['first'] . ' ' . + $condition['operator'] . ' ' . $condition['second']; } } @@ -232,12 +235,12 @@ protected function compileUpdateWithFrom(BaseBuilder $builder): string } if ($fromTables !== []) { - $sql[] = "FROM " . implode(', ', $fromTables); + $sql[] = 'FROM ' . implode(', ', $fromTables); } // Fusionner les conditions WHERE originales avec les conditions de jointure $allConditions = array_merge($joinConditions, $builder->wheres); - + if ($allConditions !== []) { $sql[] = 'WHERE'; $sql[] = $this->compileWheres($allConditions); diff --git a/src/Builder/Compilers/QueryCompiler.php b/src/Builder/Compilers/QueryCompiler.php index 2749d40..9576272 100644 --- a/src/Builder/Compilers/QueryCompiler.php +++ b/src/Builder/Compilers/QueryCompiler.php @@ -24,13 +24,13 @@ abstract class QueryCompiler public function __construct(protected BaseConnection $db) { } - + /** * Compile la requête en fonction du type CRUD */ public function compile(BaseBuilder $builder): string { - return match($builder->crud) { + return match ($builder->crud) { 'select' => $this->compileSelect($builder), 'insert' => $this->compileInsert($builder), 'update' => $this->compileUpdate($builder), @@ -38,7 +38,7 @@ public function compile(BaseBuilder $builder): string 'truncate' => $this->compileTruncate($builder), 'replace' => $this->compileReplace($builder), 'upsert' => $this->compileUpsert($builder), - default => throw new InvalidArgumentException(sprintf('Unsupported CRUD operation: %s', $builder->crud)) + default => throw new InvalidArgumentException(sprintf('Unsupported CRUD operation: %s', $builder->crud)), }; } @@ -105,17 +105,18 @@ public function compileSelect(BaseBuilder $builder): string public function compileInsert(BaseBuilder $builder): string { $table = $this->db->escapeIdentifiers($builder->getTable()); - + // Récupérer la première ligne pour les colonnes $firstRow = $builder->values[0] ?? $builder->values; - $columns = implode(', ', array_map([$this->db, 'escapeIdentifiers'], array_keys($firstRow))); - + $columns = implode(', ', array_map([$this->db, 'escapeIdentifiers'], array_keys($firstRow))); + // Support des insertions multiples if (isset($builder->values[0]) && is_array($builder->values[0])) { $values = []; + foreach ($builder->values as $row) { $rowValues = array_map([$this, 'wrapValue'], $row); - $values[] = '(' . implode(', ', $rowValues) . ')'; + $values[] = '(' . implode(', ', $rowValues) . ')'; } $values = implode(', ', $values); } else { @@ -130,11 +131,11 @@ public function compileInsert(BaseBuilder $builder): string */ public function compileInsertUsing(BaseBuilder $builder): string { - $table = $this->db->escapeIdentifiers($builder->getTable()); + $table = $this->db->escapeIdentifiers($builder->getTable()); $columns = implode(', ', array_map([$this->db, 'escapeIdentifiers'], $builder->columns)); - + /** @var BaseBuilder $query */ - $query = $builder->values['query']; + $query = $builder->values['query']; $subquery = $query->toSql(); return "INSERT INTO {$table} ({$columns}) {$subquery}"; @@ -154,23 +155,24 @@ public function compileUpdate(BaseBuilder $builder): string protected function compileUpdateStandard(BaseBuilder $builder): string { $table = $this->db->makeTableName($builder->getTable()); - + $sql = ["UPDATE {$table}"]; - + $sets = []; + foreach ($builder->values as $column => $value) { $column = $this->db->escapeIdentifiers($column); $sets[] = "{$column} = " . $this->wrapValue($value); } - + if ([] !== $builder->joins) { // Si des jointures sont présentes mais non supportées, on lève une exception. throw new DatabaseException( - "Les jointures dans UPDATE ne sont pas supportées par " . $this->db->getDriver() + 'Les jointures dans UPDATE ne sont pas supportées par ' . $this->db->getDriver(), ); } - $sql[] = "SET " . implode(', ', $sets); + $sql[] = 'SET ' . implode(', ', $sets); if ([] !== $builder->wheres) { $sql[] = 'WHERE'; @@ -190,7 +192,7 @@ protected function compileUpdateStandard(BaseBuilder $builder): string public function compileDelete(BaseBuilder $builder): string { $table = $this->db->escapeIdentifiers($builder->getTable()); - + $sql = ["DELETE FROM {$table}"]; if ([] !== $builder->joins) { @@ -215,17 +217,18 @@ public function compileDelete(BaseBuilder $builder): string public function compileReplace(BaseBuilder $builder): string { $table = $this->db->escapeIdentifiers($builder->getTable()); - + // Récupérer la première ligne pour les colonnes $firstRow = $builder->values[0] ?? $builder->values; - $columns = implode(', ', array_map([$this->db, 'escapeIdentifiers'], array_keys($firstRow))); - + $columns = implode(', ', array_map([$this->db, 'escapeIdentifiers'], array_keys($firstRow))); + // Support des insertions multiples pour REPLACE if (isset($builder->values[0]) && is_array($builder->values[0])) { $values = []; + foreach ($builder->values as $row) { $rowValues = array_map([$this, 'wrapValue'], $row); - $values[] = '(' . implode(', ', $rowValues) . ')'; + $values[] = '(' . implode(', ', $rowValues) . ')'; } $values = implode(', ', $values); } else { @@ -241,16 +244,17 @@ public function compileReplace(BaseBuilder $builder): string public function compileUpsert(BaseBuilder $builder): string { $table = $this->db->escapeIdentifiers($builder->getTable()); - + // Récupérer la première ligne pour les colonnes $firstRow = $builder->values[0] ?? $builder->values; - $columns = implode(', ', array_map([$this->db, 'escapeIdentifiers'], array_keys($firstRow))); - + $columns = implode(', ', array_map([$this->db, 'escapeIdentifiers'], array_keys($firstRow))); + // Construire les valeurs (support multi-insert) if (isset($builder->values[0]) && is_array($builder->values[0])) { $valueRows = []; + foreach ($builder->values as $row) { - $rowValues = array_map([$this, 'wrapValue'], $row); + $rowValues = array_map([$this, 'wrapValue'], $row); $valueRows[] = '(' . implode(', ', $rowValues) . ')'; } $values = implode(', ', $valueRows); @@ -310,7 +314,7 @@ public function compileJoins(array $joins): string */ public function compileJoin(JoinClause $join): string { - $type = $join->getType(); + $type = $join->getType(); $table = $join->getTable(); $sql = "{$type} JOIN {$table}"; @@ -365,7 +369,7 @@ public function compileOrders(array $orders): string $compiled[] = $this->db->escapeIdentifiers($order['column']) . ' ' . trim($order['direction']); } } - + return implode(', ', array_map('trim', $compiled)); } @@ -393,7 +397,7 @@ public function compileUnions(array $unions): string $compiled = []; foreach ($unions as $union) { - $type = $union['all'] ? 'UNION ALL' : 'UNION'; + $type = $union['all'] ? 'UNION ALL' : 'UNION'; $compiled[] = $type . ' ' . $union['query']->toSql(); } @@ -427,51 +431,58 @@ protected function compileWhere(array $where): string { switch ($where['type']) { case 'basic': - $column = $this->db->escapeIdentifiers($where['column']); + $column = $this->db->escapeIdentifiers($where['column']); $operator = $this->translateOperator($where['operator']); - + if (isset($where['value']) && $where['value'] instanceof Expression) { return "{$column} {$operator} {$where['value']}"; } - + return "{$column} {$operator} ?"; case 'in': - $column = $this->db->escapeIdentifiers($where['column']); + $column = $this->db->escapeIdentifiers($where['column']); $placeholders = implode(', ', array_fill(0, count($where['values']), '?')); + return "{$column} {$where['operator']} ({$placeholders})"; case 'insub': - $column = $this->db->escapeIdentifiers($where['column']); + $column = $this->db->escapeIdentifiers($where['column']); $subquery = $where['query']->toSql(); - $not = $where['not'] ? 'NOT ' : ''; + $not = $where['not'] ? 'NOT ' : ''; + return "{$column} {$not}IN ({$subquery})"; case 'null': $column = $this->db->escapeIdentifiers($where['column']); + return "{$column} IS " . ($where['not'] ? 'NOT NULL' : 'NULL'); case 'between': $column = $this->db->escapeIdentifiers($where['column']); - $not = $where['not'] ? 'NOT ' : ''; + $not = $where['not'] ? 'NOT ' : ''; + return "{$column} {$not}BETWEEN ? AND ?"; case 'betweencolumns': $column = $this->db->escapeIdentifiers($where['column']); - $col1 = $this->db->escapeIdentifiers($where['values'][0]); - $col2 = $this->db->escapeIdentifiers($where['values'][1]); - $not = $where['not'] ? 'NOT ' : ''; + $col1 = $this->db->escapeIdentifiers($where['values'][0]); + $col2 = $this->db->escapeIdentifiers($where['values'][1]); + $not = $where['not'] ? 'NOT ' : ''; + return "{$column} {$not}BETWEEN {$col1} AND {$col2}"; case 'valuebetween': $col1 = $this->db->escapeIdentifiers($where['column1']); $col2 = $this->db->escapeIdentifiers($where['column2']); - $not = $where['not'] ? 'NOT ' : ''; + $not = $where['not'] ? 'NOT ' : ''; + return "? {$not}BETWEEN {$col1} AND {$col2}"; case 'column': - $first = $this->db->escapeIdentifiers($where['first']); + $first = $this->db->escapeIdentifiers($where['first']); $second = $this->db->escapeIdentifiers($where['second']); + return "{$first} {$where['operator']} {$second}"; case 'nested': @@ -479,7 +490,8 @@ protected function compileWhere(array $where): string case 'exists': $subquery = $where['query']->toSql(); - $not = $where['not'] ? 'NOT ' : ''; + $not = $where['not'] ? 'NOT ' : ''; + return "{$not}EXISTS ({$subquery})"; case 'raw': @@ -497,7 +509,7 @@ protected function compileWhere(array $where): string strtoupper($where['type']), $where['column'], $where['operator'], - $where['values'] + $where['values'], ); default: @@ -512,6 +524,7 @@ protected function compileJsonWhere(array $where): string if ($where['operator'] === 'JSON_CONTAINS') { return $this->compileJsonContains($where['column'], $where['value'], $where['not']); } + return ''; case 'jsonkey': @@ -522,6 +535,7 @@ protected function compileJsonWhere(array $where): string case 'jsonsearch': return $this->compileJsonSearch($where['column'], $where['value'], $where['not']); + default: return ''; } @@ -541,22 +555,24 @@ public function compileValues(array $values): string public function compileBetweenColumns(string $column, array $values, bool $not = false): string { $column = $this->db->escapeIdentifiers($column); - $col1 = $this->db->escapeIdentifiers($values[0]); - $col2 = $this->db->escapeIdentifiers($values[1]); + $col1 = $this->db->escapeIdentifiers($values[0]); + $col2 = $this->db->escapeIdentifiers($values[1]); $notStr = $not ? 'NOT ' : ''; - + return "{$column} {$notStr}BETWEEN {$col1} AND {$col2}"; } /** * Compile une clause WHERE VALUE BETWEEN + * + * @param mixed $value */ public function compileValueBetween($value, string $column1, string $column2, bool $not = false): string { - $col1 = $this->db->escapeIdentifiers($column1); - $col2 = $this->db->escapeIdentifiers($column2); + $col1 = $this->db->escapeIdentifiers($column1); + $col2 = $this->db->escapeIdentifiers($column2); $notStr = $not ? 'NOT ' : ''; - + return "? {$notStr}BETWEEN {$col1} AND {$col2}"; } @@ -576,31 +592,31 @@ protected function compileJoinConditions(array $conditions): string switch ($condition['type']) { case 'basic': - $parts[] = $this->db->escapeIdentifiers($condition['first']) . - ' ' . $condition['operator'] . ' ' . + $parts[] = $this->db->escapeIdentifiers($condition['first']) . + ' ' . $condition['operator'] . ' ' . $this->db->escapeIdentifiers($condition['second']); break; case 'where': - $parts[] = $this->db->escapeIdentifiers($condition['first']) . + $parts[] = $this->db->escapeIdentifiers($condition['first']) . ' ' . $condition['operator'] . ' ?'; break; case 'in': $placeholders = implode(', ', array_fill(0, count($condition['values']), '?')); - $parts[] = $this->db->escapeIdentifiers($condition['column']) . - ($condition['not'] ? ' NOT IN ' : ' IN ') . + $parts[] = $this->db->escapeIdentifiers($condition['column']) . + ($condition['not'] ? ' NOT IN ' : ' IN ') . '(' . $placeholders . ')'; break; case 'null': - $parts[] = $this->db->escapeIdentifiers($condition['column']) . + $parts[] = $this->db->escapeIdentifiers($condition['column']) . ($condition['not'] ? ' IS NOT NULL' : ' IS NULL'); break; case 'nested': $nestedConditions = $this->compileJoinConditions($condition['join']->getConditions()); - $parts[] = '(' . $nestedConditions . ')'; + $parts[] = '(' . $nestedConditions . ')'; break; } } @@ -610,6 +626,8 @@ protected function compileJoinConditions(array $conditions): string /** * Enveloppe une valeur pour le SQL + * + * @param mixed $value */ protected function wrapValue($value): string { @@ -632,7 +650,7 @@ protected function translateOperator(string $operator): string * Compile la clause DISTINCT */ abstract protected function compileDistinct(bool|string $distinct): string; - + /** * Compile la clause LOCK */ @@ -660,9 +678,11 @@ abstract public function compileTruncate(BaseBuilder $builder): string; /** * Compile une clause JSON CONTAINS + * + * @param mixed $value */ abstract protected function compileJsonContains(string $column, $value, bool $not = false): string; - + /** * Compile une clause JSON CONTAINS KEY */ @@ -682,4 +702,4 @@ abstract protected function compileJsonSearch(string $column, string $value, boo * Compile une clause WHERE ANY/ALL */ abstract protected function compileAnyAll(string $type, string $column, string $operator, array $values): string; -} \ No newline at end of file +} diff --git a/src/Builder/Compilers/SQLite.php b/src/Builder/Compilers/SQLite.php index e81b28d..79967f7 100644 --- a/src/Builder/Compilers/SQLite.php +++ b/src/Builder/Compilers/SQLite.php @@ -23,14 +23,14 @@ public function compileUpdate(BaseBuilder $builder): string { if ($builder->joins !== []) { throw new DatabaseException( - "SQLite ne supporte pas les jointures dans les requêtes UPDATE. " . - "Utilisez des sous-requêtes à la place." + 'SQLite ne supporte pas les jointures dans les requêtes UPDATE. ' . + 'Utilisez des sous-requêtes à la place.', ); } return $this->compileUpdateStandard($builder); } - + /** * {@inheritDoc} */ @@ -64,13 +64,13 @@ public function compileInsertion(string $table, string $columns, string $values, public function compileTruncate(BaseBuilder $builder): string { $table = $this->db->escapeIdentifiers($builder->getTable()); - + // SQLite n'a pas de TRUNCATE, on utilise DELETE $sql = "DELETE FROM {$table}"; - + // Réinitialiser l'auto-increment $sql .= "; DELETE FROM sqlite_sequence WHERE name = '" . str_replace("'", "''", $builder->getTable()) . "'"; - + return $sql; } @@ -100,15 +100,15 @@ protected function compileJsonContains(string $column, $value, bool $not = false if (version_compare($this->db->getVersion(), '3.38', '>=')) { $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + // Utiliser json_each ou json_extract pour simuler JSON_CONTAINS return "json_each({$column}) IS {$notStr}NULL"; } - + // Version plus ancienne, pas de support JSON return $not ? '0' : '1'; } - + /** * {@inheritDoc} */ @@ -118,10 +118,10 @@ protected function compileJsonContainsKey(string $column, bool $not = false): st if (version_compare($this->db->getVersion(), '3.38', '>=')) { $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + return "json_extract({$column}, ?) IS {$notStr}NULL"; } - + return $not ? '0' : '1'; } @@ -132,10 +132,10 @@ protected function compileJsonLength(string $column, string $operator, int $valu { if (version_compare($this->db->getVersion(), '3.38', '>=')) { $column = $this->db->escapeIdentifiers($column); - + return "json_array_length({$column}) {$operator} ?"; } - + return '1'; } @@ -148,11 +148,11 @@ protected function compileJsonSearch(string $column, string $value, bool $not = if (version_compare($this->db->getVersion(), '3.38', '>=')) { $column = $this->db->escapeIdentifiers($column); $notStr = $not ? 'NOT ' : ''; - + // Utiliser json_each pour rechercher dans les tableaux return "EXISTS (SELECT 1 FROM json_each({$column}) WHERE value = ?) IS {$notStr}TRUE"; } - + return $not ? '0' : '1'; } @@ -162,14 +162,14 @@ protected function compileJsonSearch(string $column, string $value, bool $not = protected function compileAnyAll(string $type, string $column, string $operator, array $values): string { // SQLite ne supporte pas ANY/ALL, on simule avec IN/NOT IN - $column = $this->db->escapeIdentifiers($column); + $column = $this->db->escapeIdentifiers($column); $placeholders = implode(', ', array_fill(0, count($values), '?')); - + if ($type === 'ANY') { return "{$column} {$operator} ({$placeholders})"; } - + // Pour ALL, c'est plus complexe - on utilise une sous-requête return "NOT EXISTS (SELECT 1 WHERE {$column} NOT {$operator} ({$placeholders}))"; } -} \ No newline at end of file +} diff --git a/src/Builder/Concerns/AdvancedMethods.php b/src/Builder/Concerns/AdvancedMethods.php index 54fc0b5..f70b43d 100644 --- a/src/Builder/Concerns/AdvancedMethods.php +++ b/src/Builder/Concerns/AdvancedMethods.php @@ -22,12 +22,11 @@ */ trait AdvancedMethods { - /** * Liste des requêtes UNION */ protected array $unions = []; - + /* |-------------------------------------------------------------------------- | DATE QUERIES @@ -36,6 +35,9 @@ trait AdvancedMethods /** * Ajoute une clause WHERE pour les dates + * + * @param mixed|null $operator + * @param mixed|null $value */ public function whereDate(array|string $column, $operator = null, $value = null, string $boolean = 'and'): static { @@ -58,6 +60,9 @@ public function whereDate(array|string $column, $operator = null, $value = null, /** * Ajoute une clause WHERE NOT DATE + * + * @param mixed|null $operator + * @param mixed|null $value */ public function whereNotDate(array|string $column, $operator = null, $value = null, string $boolean = 'and'): static { @@ -68,12 +73,15 @@ public function whereNotDate(array|string $column, $operator = null, $value = nu [$column, $operator, $value] = $this->normalizeWhereParameters($column, $operator, $value); $operator = $this->invertOperator($operator); - + return $this->whereDate($column, $operator, $value, $boolean); } /** * Ajoute une clause WHERE DATE avec OR + * + * @param mixed|null $operator + * @param mixed|null $value */ public function orWhereDate(array|string $column, $operator = null, $value = null): static { @@ -82,6 +90,9 @@ public function orWhereDate(array|string $column, $operator = null, $value = nul /** * Ajoute une clause WHERE NOT DATE avec OR + * + * @param mixed|null $operator + * @param mixed|null $value */ public function orWhereNotDate(array|string $column, $operator = null, $value = null): static { @@ -90,11 +101,14 @@ public function orWhereNotDate(array|string $column, $operator = null, $value = /** * Ajoute une clause WHERE pour les heures + * + * @param mixed $operator + * @param mixed|null $value */ public function whereTime(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } @@ -107,21 +121,27 @@ public function whereTime(string $column, $operator, $value = null, string $bool /** * Ajoute une clause WHERE NOT TIME + * + * @param mixed $operator + * @param mixed|null $value */ public function whereNotTime(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } $operator = $this->invertOperator($operator); - + return $this->whereTime($column, $operator, $value, $boolean); } /** * Ajoute une clause WHERE TIME avec OR + * + * @param mixed $operator + * @param mixed|null $value */ public function orWhereTime(string $column, $operator, $value = null): static { @@ -130,6 +150,9 @@ public function orWhereTime(string $column, $operator, $value = null): static /** * Ajoute une clause WHERE NOT TIME avec OR + * + * @param mixed $operator + * @param mixed|null $value */ public function orWhereNotTime(string $column, $operator, $value = null): static { @@ -138,11 +161,14 @@ public function orWhereNotTime(string $column, $operator, $value = null): static /** * Ajoute une clause WHERE pour le jour + * + * @param mixed $operator + * @param mixed|null $value */ public function whereDay(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } @@ -151,21 +177,27 @@ public function whereDay(string $column, $operator, $value = null, string $boole /** * Ajoute une clause WHERE NOT DAY + * + * @param mixed $operator + * @param mixed|null $value */ public function whereNotDay(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } $operator = $this->invertOperator($operator); - + return $this->whereDay($column, $operator, $value, $boolean); } /** * Ajoute une clause WHERE DAY avec OR + * + * @param mixed $operator + * @param mixed|null $value */ public function orWhereDay(string $column, $operator, $value = null): static { @@ -174,6 +206,9 @@ public function orWhereDay(string $column, $operator, $value = null): static /** * Ajoute une clause WHERE NOT DAY avec OR + * + * @param mixed $operator + * @param mixed|null $value */ public function orWhereNotDay(string $column, $operator, $value = null): static { @@ -182,11 +217,14 @@ public function orWhereNotDay(string $column, $operator, $value = null): static /** * Ajoute une clause WHERE pour le mois + * + * @param mixed $operator + * @param mixed|null $value */ public function whereMonth(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } @@ -195,21 +233,27 @@ public function whereMonth(string $column, $operator, $value = null, string $boo /** * Ajoute une clause WHERE NOT MONTH + * + * @param mixed $operator + * @param mixed|null $value */ public function whereNotMonth(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } - + $operator = $this->invertOperator($operator); - + return $this->whereMonth($column, $operator, $value, $boolean); } /** * Ajoute une clause WHERE MONTH avec OR + * + * @param mixed $operator + * @param mixed|null $value */ public function orWhereMonth(string $column, $operator, $value = null): static { @@ -218,6 +262,9 @@ public function orWhereMonth(string $column, $operator, $value = null): static /** * Ajoute une clause WHERE NOT MONTH avec OR + * + * @param mixed $operator + * @param mixed|null $value */ public function orWhereNotMonth(string $column, $operator, $value = null): static { @@ -226,11 +273,14 @@ public function orWhereNotMonth(string $column, $operator, $value = null): stati /** * Ajoute une clause WHERE pour l'année + * + * @param mixed $operator + * @param mixed|null $value */ public function whereYear(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } @@ -239,21 +289,27 @@ public function whereYear(string $column, $operator, $value = null, string $bool /** * Ajoute une clause WHERE NOT YEAR + * + * @param mixed $operator + * @param mixed|null $value */ public function whereNotYear(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } - + $operator = $this->invertOperator($operator); - + return $this->whereYear($column, $operator, $value, $boolean); } /** * Ajoute une clause WHERE YEAR avec OR + * + * @param mixed $operator + * @param mixed|null $value */ public function orWhereYear(string $column, $operator, $value = null): static { @@ -262,6 +318,9 @@ public function orWhereYear(string $column, $operator, $value = null): static /** * Ajoute une clause WHERE NOT YEAR avec OR + * + * @param mixed $operator + * @param mixed|null $value */ public function orWhereNotYear(string $column, $operator, $value = null): static { @@ -270,11 +329,14 @@ public function orWhereNotYear(string $column, $operator, $value = null): static /** * Ajoute une clause WHERE pour la semaine + * + * @param mixed $operator + * @param mixed|null $value */ public function whereWeek(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } @@ -283,26 +345,32 @@ public function whereWeek(string $column, $operator, $value = null, string $bool /** * Ajoute une clause WHERE pour le jour de la semaine (0-6) + * + * @param mixed $operator + * @param mixed|null $value */ public function whereDayOfWeek(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } $function = $this->db->getDriver() === 'pgsql' ? 'EXTRACT(DOW FROM ' : 'DAYOFWEEK('; - + return $this->whereRaw($function . $column . ') ' . $operator . ' ?', [$value], $boolean); } /** * Ajoute une clause WHERE pour le trimestre + * + * @param mixed $operator + * @param mixed|null $value */ public function whereQuarter(string $column, $operator, $value = null, string $boolean = 'and'): static { if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } @@ -405,15 +473,17 @@ public function whereTodayOrAfter(string $column, string $boolean = 'and'): stat /** * Ajoute une clause WHERE pour les colonnes JSON + * + * @param mixed $value */ public function whereJsonContains(string $column, $value, string $boolean = 'and', bool $not = false): static { $this->wheres[] = [ - 'type' => 'json', - 'column' => $column, - 'value' => $value, - 'boolean' => $boolean, - 'not' => $not, + 'type' => 'json', + 'column' => $column, + 'value' => $value, + 'boolean' => $boolean, + 'not' => $not, 'operator' => 'JSON_CONTAINS', ]; @@ -424,6 +494,8 @@ public function whereJsonContains(string $column, $value, string $boolean = 'and /** * Ajoute une clause WHERE JSON NOT CONTAINS + * + * @param mixed $value */ public function whereJsonDoesntContain(string $column, $value, string $boolean = 'and'): static { @@ -432,6 +504,8 @@ public function whereJsonDoesntContain(string $column, $value, string $boolean = /** * Ajoute une clause WHERE OR JSON CONTAINS + * + * @param mixed $value */ public function orWhereJsonContains(string $column, $value): static { @@ -440,6 +514,8 @@ public function orWhereJsonContains(string $column, $value): static /** * Ajoute une clause WHERE OR JSON NOT CONTAINS + * + * @param mixed $value */ public function orWhereJsonDoesntContain(string $column, $value): static { @@ -452,13 +528,13 @@ public function orWhereJsonDoesntContain(string $column, $value): static public function whereJsonContainsKey(string $column, string $boolean = 'and', bool $not = false): static { $operator = $not ? 'JSON_NOT_CONTAINS_KEY' : 'JSON_CONTAINS_KEY'; - + $this->wheres[] = [ - 'type' => 'jsonkey', - 'column' => $column, - 'boolean' => $boolean, - 'not' => $not, - 'operator' => $operator + 'type' => 'jsonkey', + 'column' => $column, + 'boolean' => $boolean, + 'not' => $not, + 'operator' => $operator, ]; return $this; @@ -494,12 +570,12 @@ public function orWhereJsonDoesntContainKey(string $column): static public function whereJsonLength(string $column, string $operator, int $value, string $boolean = 'and'): static { $this->wheres[] = [ - 'type' => 'jsonlength', - 'column' => $column, - 'value' => $value, + 'type' => 'jsonlength', + 'column' => $column, + 'value' => $value, 'operator' => $operator, - 'boolean' => $boolean, - 'json_op' => 'JSON_LENGTH' + 'boolean' => $boolean, + 'json_op' => 'JSON_LENGTH', ]; $this->bindings->add($value); @@ -521,11 +597,11 @@ public function orWhereJsonLength(string $column, string $operator, int $value): public function whereJsonSearch(string $column, string $value, string $boolean = 'and', bool $not = false): static { $this->wheres[] = [ - 'type' => 'jsonsearch', - 'column' => $column, - 'value' => $value, + 'type' => 'jsonsearch', + 'column' => $column, + 'value' => $value, 'boolean' => $boolean, - 'not' => $not + 'not' => $not, ]; $this->bindings->add($value); @@ -542,11 +618,11 @@ public function whereJsonSearch(string $column, string $value, string $boolean = /** * Ajoute une requête UNION */ - public function union(Closure|BuilderInterface $query, bool $all = false): static + public function union(BuilderInterface|Closure $query, bool $all = false): static { $this->unions[] = [ 'query' => $this->createUnionQuery($query), - 'all' => $all + 'all' => $all, ]; return $this; @@ -555,7 +631,7 @@ public function union(Closure|BuilderInterface $query, bool $all = false): stati /** * Ajoute une requête UNION ALL */ - public function unionAll(Closure|BuilderInterface $query): static + public function unionAll(BuilderInterface|Closure $query): static { return $this->union($query, true); } @@ -563,14 +639,14 @@ public function unionAll(Closure|BuilderInterface $query): static /** * Crée une requête pour UNION */ - protected function createUnionQuery(Closure|BaseBuilder $query): BaseBuilder + protected function createUnionQuery(BaseBuilder|Closure $query): BaseBuilder { if ($query instanceof Closure) { $builder = $this->newQuery(); $query($builder); - + $this->bindings->merge($builder->bindings); - + return $builder; } diff --git a/src/Builder/Concerns/BuildsQueries.php b/src/Builder/Concerns/BuildsQueries.php index 2eb46c0..d97ee96 100644 --- a/src/Builder/Concerns/BuildsQueries.php +++ b/src/Builder/Concerns/BuildsQueries.php @@ -11,8 +11,8 @@ namespace BlitzPHP\Database\Builder\Concerns; -use BlitzPHP\Database\Exceptions\RecordsNotFoundException; use BlitzPHP\Database\Exceptions\MultipleRecordsFoundException; +use BlitzPHP\Database\Exceptions\RecordsNotFoundException; use BlitzPHP\Traits\Conditionable; use BlitzPHP\Utilities\Helpers; use BlitzPHP\Utilities\Iterable\Collection; @@ -22,13 +22,13 @@ /** * @template TValue - * + * * @mixin \BlitzPHP\Database\Builder\BaseBuilder */ trait BuildsQueries { use Conditionable; - + /** * Passe la requête à un callback donné puis la retourne. * @@ -46,9 +46,10 @@ public function tap(callable $callback): static * * @template TReturn * - * @param (callable($this): TReturn) $callback - * - * @return (TReturn is null|void ? $this : TReturn) + * @param (callable($this): TReturn) $callback + * @param mixed $callback + * + * @return (TReturn is void|null ? $this : TReturn) */ public function pipe($callback) { @@ -65,13 +66,13 @@ public function forPage(int $page, int $perPage = 15): static /** * Contraint la requête à la "page" précédente de résultats avant un ID donné. - * + * * Pagination avec curseur (pour les grandes tables) */ public function forPageBeforeId(int $perPage = 15, ?int $lastId = 0, string $column = 'id'): self { $this->orders = $this->removeExistingOrdersFor($column); - + if ($lastId === null) { $this->whereNotNull($column); } else { @@ -80,10 +81,10 @@ public function forPageBeforeId(int $perPage = 15, ?int $lastId = 0, string $col return $this->orderBy($column, 'DESC')->limit($perPage); } - + /** * Contraint la requête à la "page" suivante de résultats après un ID donné. - * + * * Pagination avec curseur (pour les grandes tables) */ public function forPageAfterId(int $perPage = 15, ?int $lastId = 0, string $column = 'id'): self @@ -101,8 +102,8 @@ public function forPageAfterId(int $perPage = 15, ?int $lastId = 0, string $colu /** * Traitement par lots - * - * @param callable(Collection, int): mixed $callback + * + * @param callable(Collection, int): mixed $callback */ public function chunk(int $count, callable $callback): bool { @@ -115,14 +116,14 @@ public function chunk(int $count, callable $callback): bool do { $offset = (($page - 1) * $count) + (int) $skip; $limit = $remaining === null ? $count : min($count, $remaining); - - if ($limit == 0) { + + if ($limit === 0) { break; } $results = $this->clone()->limit($limit, $offset)->collect(); - - if (0 == $countResults = $results->count()) { + + if (0 === $countResults = $results->count()) { break; } @@ -133,7 +134,7 @@ public function chunk(int $count, callable $callback): bool unset($results); $page++; - } while ($countResults == $count); + } while ($countResults === $count); return true; } @@ -144,7 +145,7 @@ public function chunk(int $count, callable $callback): bool * @template TReturn * * @param callable(TValue): TReturn $callback - * + * * @return Collection */ public function chunkMap(callable $callback, int $count = 1000): Collection @@ -187,7 +188,7 @@ public function chunkByIdDesc(int $count, callable $callback, string $column = ' */ public function orderedChunkById(int $count, callable $callback, string $column = 'id', ?string $alias = null, bool $descending = false): bool { - $alias ??= $column; + $alias ??= $column; $lastId = null; $skip = $this->offset; $remaining = $this->limit; @@ -202,7 +203,7 @@ public function orderedChunkById(int $count, callable $callback, string $column } $limit = $remaining === null ? $count : min($count, $remaining); - if ($limit == 0) { + if ($limit === 0) { break; } @@ -233,7 +234,7 @@ public function orderedChunkById(int $count, callable $callback, string $column unset($results); $page++; - } while ($countResults == $count); + } while ($countResults === $count); return true; } @@ -247,7 +248,7 @@ public function orderedChunkById(int $count, callable $callback, string $column */ public function each(callable $callback, int $count = 1000): bool { - return $this->chunk($count, function ($results) use ($callback) { + return $this->chunk($count, static function ($results) use ($callback) { foreach ($results as $key => $value) { if ($callback($value, $key) === false) { return false; @@ -261,7 +262,7 @@ public function each(callable $callback, int $count = 1000): bool */ public function eachById(callable $callback, int $count = 1000, string $column = 'id', ?string $alias = null): bool { - return $this->chunkById($count, function ($results, $page) use ($callback, $count) { + return $this->chunkById($count, static function ($results, $page) use ($callback, $count) { foreach ($results as $key => $value) { if ($callback($value, (($page - 1) * $count) + $key) === false) { return false; diff --git a/src/Builder/Concerns/CoreMethods.php b/src/Builder/Concerns/CoreMethods.php index e96b53a..0968c43 100644 --- a/src/Builder/Concerns/CoreMethods.php +++ b/src/Builder/Concerns/CoreMethods.php @@ -11,10 +11,10 @@ namespace BlitzPHP\Database\Builder\Concerns; -use BlitzPHP\Database\Builder\JoinClause; -use BlitzPHP\Database\Query\Expression; use BlitzPHP\Contracts\Database\BuilderInterface; use BlitzPHP\Database\Builder\BaseBuilder; +use BlitzPHP\Database\Builder\JoinClause; +use BlitzPHP\Database\Query\Expression; use BlitzPHP\Database\Utils; use BlitzPHP\Utilities\Iterable\Collection; use Closure; @@ -36,7 +36,7 @@ trait CoreMethods * Liste des conditions HAVING */ protected array $havings = []; - + /** * Propriété pour stocker les clauses ORDER BY */ @@ -47,6 +47,25 @@ trait CoreMethods */ protected array $groups = []; + /* + |-------------------------------------------------------------------------- + | JOIN CLAUSES + |-------------------------------------------------------------------------- + */ + + /** + * Liste des jointures + */ + protected array $joins = []; + + /** + * Type de jointures entre tables + */ + protected array $joinTypes = [ + 'INNER', 'LEFT', 'RIGHT', 'FULL OUTER', + 'CROSS', 'LEFT OUTER', 'RIGHT OUTER', + ]; + /* |-------------------------------------------------------------------------- | WHERE CLAUSES @@ -60,8 +79,10 @@ trait CoreMethods * - where(string $column, mixed $value) // operator = '=' * - where(array $conditions) * - where(Closure $callback) - * + * * @param array|Closure|Expression|string $column + * @param mixed|null $operator + * @param mixed|null $value */ public function where($column, $operator = null, $value = null, string $boolean = 'and'): static { @@ -86,7 +107,7 @@ public function where($column, $operator = null, $value = null, string $boolean 'column' => $column, 'operator' => $operator, 'value' => $value, - 'boolean' => $boolean + 'boolean' => $boolean, ]); } @@ -94,21 +115,21 @@ public function where($column, $operator = null, $value = null, string $boolean $value = $value->format('Y-m-d H:i:s'); } - if (in_array($operator, ['IN', 'NOT IN', '@', '!@'])) { + if (in_array($operator, ['IN', 'NOT IN', '@', '!@'], true)) { $values = is_array($value) ? $value : [$value]; return $this->whereIn($column, $values, $boolean, $operator === 'NOT IN' || $operator === '!@'); } - if (in_array($operator, ['BETWEEN', 'NOT BETWEEN'])) { + if (in_array($operator, ['BETWEEN', 'NOT BETWEEN'], true)) { if (! is_array($value) || count($value) !== 2) { - throw new InvalidArgumentException("BETWEEN requires an array with exactly 2 values"); + throw new InvalidArgumentException('BETWEEN requires an array with exactly 2 values'); } return $this->whereBetween($column, $value[0], $value[1], $boolean, $operator === 'NOT BETWEEN'); } - if (in_array($operator, ['LIKE', 'NOT LIKE', '%', '!%'])) { + if (in_array($operator, ['LIKE', 'NOT LIKE', '%', '!%'], true)) { return $this->whereLike($column, $value, $boolean, $operator === 'NOT LIKE' || $operator === '!%', false); } @@ -116,12 +137,16 @@ public function where($column, $operator = null, $value = null, string $boolean 'column' => $column, 'operator' => $operator, 'value' => $value, - 'boolean' => $boolean + 'boolean' => $boolean, ]); } /** * Ajoute une clause WHERE NOT + * + * @param mixed $column + * @param mixed|null $operator + * @param mixed|null $value */ public function whereNot($column, $operator = null, $value = null, string $boolean = 'and'): static { @@ -134,15 +159,19 @@ public function whereNot($column, $operator = null, $value = null, string $boole } [$column, $operator, $value] = $this->normalizeWhereParameters($column, $operator, $value); - + // Inverser l'opérateur $operator = $this->invertOperator($operator); - + return $this->where($column, $operator, $value, $boolean); } /** * Ajoute une clause WHERE avec OR + * + * @param mixed $column + * @param mixed|null $operator + * @param mixed|null $value */ public function orWhere($column, $operator = null, $value = null): static { @@ -151,6 +180,10 @@ public function orWhere($column, $operator = null, $value = null): static /** * Ajoute une clause WHERE NOT avec OR + * + * @param mixed $column + * @param mixed|null $operator + * @param mixed|null $value */ public function orWhereNot($column, $operator = null, $value = null): static { @@ -167,9 +200,9 @@ public function whereIn(string $column, array|Closure $values, string $boolean = } return $this->addCondition('wheres', 'in', [ - 'column' => $column, - 'values' => $values, - 'boolean' => $boolean, + 'column' => $column, + 'values' => $values, + 'boolean' => $boolean, 'operator' => $not ? 'NOT IN' : 'IN', ]); } @@ -207,10 +240,10 @@ public function whereInSub(string $column, Closure $callback, string $boolean = $callback($query); $this->addCondition('wheres', 'insub', [ - 'column' => $column, - 'query' => $query, + 'column' => $column, + 'query' => $query, 'boolean' => $boolean, - 'not' => $not, + 'not' => $not, ]); $this->bindings->merge($query->bindings); @@ -220,14 +253,17 @@ public function whereInSub(string $column, Closure $callback, string $boolean = /** * Ajoute une clause WHERE BETWEEN + * + * @param mixed $value1 + * @param mixed $value2 */ public function whereBetween(string $column, $value1, $value2, string $boolean = 'and', bool $not = false): static { return $this->addCondition('wheres', 'between', [ - 'column' => $column, - 'values' => [$value1, $value2], + 'column' => $column, + 'values' => [$value1, $value2], 'boolean' => $boolean, - 'not' => $not, + 'not' => $not, ]); } @@ -237,19 +273,22 @@ public function whereBetween(string $column, $value1, $value2, string $boolean = public function whereBetweenColumns(string $column, array $values, string $boolean = 'and', bool $not = false): static { if (count($values) !== 2) { - throw new InvalidArgumentException("whereBetweenColumns requires an array with exactly 2 columns"); + throw new InvalidArgumentException('whereBetweenColumns requires an array with exactly 2 columns'); } return $this->addCondition('wheres', 'betweencolumns', [ - 'column' => $column, - 'values' => $values, + 'column' => $column, + 'values' => $values, 'boolean' => $boolean, - 'not' => $not, + 'not' => $not, ]); } /** * Ajoute une clause WHERE NOT BETWEEN + * + * @param mixed $value1 + * @param mixed $value2 */ public function whereNotBetween(string $column, $value1, $value2, string $boolean = 'and'): static { @@ -266,6 +305,9 @@ public function whereNotBetweenColumns(string $column, array $values, string $bo /** * Ajoute une clause WHERE BETWEEN avec OR + * + * @param mixed $value1 + * @param mixed $value2 */ public function orWhereBetween(string $column, $value1, $value2): static { @@ -274,6 +316,9 @@ public function orWhereBetween(string $column, $value1, $value2): static /** * Ajoute une clause WHERE NOT BETWEEN avec OR + * + * @param mixed $value1 + * @param mixed $value2 */ public function orWhereNotBetween(string $column, $value1, $value2): static { @@ -294,9 +339,9 @@ public function whereNull(array|string $column, string $boolean = 'and', bool $n } return $this->addCondition('wheres', 'null', [ - 'column' => $column, + 'column' => $column, 'boolean' => $boolean, - 'not' => $not, + 'not' => $not, ]); } @@ -330,7 +375,7 @@ public function orWhereNotNull(array|string $column): static public function whereLike(string $column, string $value, string $boolean = 'and', bool $not = false, bool $caseSensitive = false, string $side = 'both'): static { $operator = $not ? 'NOT LIKE' : 'LIKE'; - + if ($caseSensitive && $this->db->getDriver() === 'pgsql') { $operator = $not ? 'NOT ILIKE' : 'ILIKE'; } elseif ($caseSensitive && $this->db->getDriver() === 'mysql') { @@ -347,15 +392,15 @@ public function whereLike(string $column, string $value, string $boolean = 'and' $value = str_replace('%', '', $value); } - $value = match($side) { + $value = match ($side) { 'before' => "%{$value}", 'after' => "{$value}%", 'both' => "%{$value}%", - default => $value + default => $value, }; return $this->addCondition('wheres', 'basic', [ - 'column' => $column, + 'column' => $column, 'operator' => $operator, 'value' => $value, 'boolean' => $boolean, @@ -395,9 +440,9 @@ public function whereExists(Closure $callback, string $boolean = 'and', bool $no $callback($query); return $this->addCondition('wheres', 'exists', [ - 'query' => $query, + 'query' => $query, 'boolean' => $boolean, - 'not' => $not, + 'not' => $not, ]); } @@ -435,15 +480,15 @@ public function whereColumn(array|string $first, ?string $operator = null, ?stri } if ($second === null) { - $second = $operator; + $second = $operator; $operator = '='; } return $this->addCondition('wheres', 'column', [ - 'first' => $first, + 'first' => $first, 'operator' => $operator, - 'second' => $second, - 'boolean' => $boolean, + 'second' => $second, + 'boolean' => $boolean, ]); } @@ -465,7 +510,7 @@ public function whereNotColumn(array|string $first, ?string $operator = null, ?s } if ($second === null) { - $second = $operator; + $second = $operator; $operator = '='; } @@ -489,10 +534,10 @@ public function orWhereNotColumn(array|string $first, ?string $operator = null, public function whereAny(string $column, string $operator, array $values, string $boolean = 'and'): static { return $this->addCondition('wheres', 'any', [ - 'column' => $column, + 'column' => $column, 'operator' => $operator, - 'values' => $values, - 'boolean' => $boolean, + 'values' => $values, + 'boolean' => $boolean, ]); } @@ -502,10 +547,10 @@ public function whereAny(string $column, string $operator, array $values, string public function whereAll(string $column, string $operator, array $values, string $boolean = 'and'): static { return $this->addCondition('wheres', 'all', [ - 'column' => $column, + 'column' => $column, 'operator' => $operator, - 'values' => $values, - 'boolean' => $boolean, + 'values' => $values, + 'boolean' => $boolean, ]); } @@ -519,18 +564,20 @@ public function whereNone(string $column, string $operator, array $values, strin /** * Ajoute une clause WHERE VALUE BETWEEN (WHERE ? BETWEEN column1 AND column2) + * + * @param mixed $value */ public function whereValueBetween($value, string $column1, string $column2, string $boolean = 'and', bool $not = false): static { return $this->addCondition('wheres', 'valuebetween', [ - 'value' => $value, + 'value' => $value, 'column1' => $column1, 'column2' => $column2, 'boolean' => $boolean, - 'not' => $not + 'not' => $not, ]); } - + /** * Add another query builder as a nested where to the query builder. */ @@ -538,9 +585,9 @@ public function addNestedWhereQuery(BaseBuilder $query, string $boolean = 'and') { if (count($query->wheres)) { $this->wheres[] = [ - 'type' => 'nested', - 'query' => $query, - 'boolean' => $boolean + 'type' => 'nested', + 'query' => $query, + 'boolean' => $boolean, ]; $this->bindings->merge($query->bindings); @@ -551,13 +598,15 @@ public function addNestedWhereQuery(BaseBuilder $query, string $boolean = 'and') /** * Ajoute une clause HAVING - * + * * Supporte les signatures : * - having(string $column, string $operator, mixed $value) * - having(string $column, mixed $value) // operator = '=' * - having(array $conditions) - * + * * @param array|Closure|Expression|string $column + * @param mixed|null $operator + * @param mixed|null $value */ public function having($column, $operator = null, $value = null, string $boolean = 'and'): static { @@ -569,6 +618,7 @@ public function having($column, $operator = null, $value = null, string $boolean foreach ($column as $key => $val) { $this->having($key, '=', $val, $boolean); } + return $this; } @@ -580,10 +630,10 @@ public function having($column, $operator = null, $value = null, string $boolean if ($value instanceof Expression) { return $this->addCondition('havings', 'basic', [ - 'column' => $column, + 'column' => $column, 'operator' => $operator, - 'value' => $value, - 'boolean' => $boolean + 'value' => $value, + 'boolean' => $boolean, ]); } @@ -591,34 +641,38 @@ public function having($column, $operator = null, $value = null, string $boolean $value = $value->format('Y-m-d H:i:s'); } - if (in_array($operator, ['IN', 'NOT IN', '@', '!@'])) { + if (in_array($operator, ['IN', 'NOT IN', '@', '!@'], true)) { $values = is_array($value) ? $value : [$value]; - + return $this->havingIn($column, $values, $boolean, $operator === 'NOT IN' || $operator === '!@'); } - if (in_array($operator, ['BETWEEN', 'NOT BETWEEN'])) { - if (!is_array($value) || count($value) !== 2) { - throw new InvalidArgumentException("BETWEEN requires an array with exactly 2 values"); + if (in_array($operator, ['BETWEEN', 'NOT BETWEEN'], true)) { + if (! is_array($value) || count($value) !== 2) { + throw new InvalidArgumentException('BETWEEN requires an array with exactly 2 values'); } return $this->havingBetween($column, $value[0], $value[1], $boolean, $operator === 'NOT BETWEEN'); } - if (in_array($operator, ['LIKE', 'NOT LIKE', '%', '!%'])) { + if (in_array($operator, ['LIKE', 'NOT LIKE', '%', '!%'], true)) { return $this->havingLike($column, $value, $boolean, $operator === 'NOT LIKE' || $operator === '!%'); } return $this->addCondition('havings', 'basic', [ - 'column' => $column, + 'column' => $column, 'operator' => $operator, - 'value' => $value, - 'boolean' => $boolean + 'value' => $value, + 'boolean' => $boolean, ])->asCrud('select'); } /** * Ajoute une clause HAVING avec OR + * + * @param mixed $column + * @param mixed|null $operator + * @param mixed|null $value */ public function orHaving($column, $operator = null, $value = null): static { @@ -635,10 +689,10 @@ public function havingIn(string $column, array|Closure $values, string $boolean } return $this->addCondition('havings', 'in', [ - 'column' => $column, - 'values' => $values, - 'boolean' => $boolean, - 'operator' => $not ? 'NOT IN' : 'IN' + 'column' => $column, + 'values' => $values, + 'boolean' => $boolean, + 'operator' => $not ? 'NOT IN' : 'IN', ]); } @@ -675,32 +729,38 @@ public function havingInSub(string $column, Closure $callback, string $boolean = $callback($query); $this->addCondition('havings', 'insub', [ - 'column' => $column, - 'query' => $query, + 'column' => $column, + 'query' => $query, 'boolean' => $boolean, - 'not' => $not + 'not' => $not, ]); $this->bindings->merge($query->bindings); return $this; } - + /** * Ajoute une clause HAVING BETWEEN + * + * @param mixed $value1 + * @param mixed $value2 */ public function havingBetween(string $column, $value1, $value2, string $boolean = 'and', bool $not = false): static { return $this->addCondition('havings', 'between', [ - 'column' => $column, - 'values' => [$value1, $value2], + 'column' => $column, + 'values' => [$value1, $value2], 'boolean' => $boolean, - 'not' => $not + 'not' => $not, ]); } /** * Ajoute une clause HAVING NOT BETWEEN + * + * @param mixed $value1 + * @param mixed $value2 */ public function havingNotBetween(string $column, $value1, $value2, string $boolean = 'and'): static { @@ -709,6 +769,9 @@ public function havingNotBetween(string $column, $value1, $value2, string $boole /** * Ajoute une clause HAVING BETWEEN avec OR + * + * @param mixed $value1 + * @param mixed $value2 */ public function orHavingBetween(string $column, $value1, $value2): static { @@ -717,6 +780,9 @@ public function orHavingBetween(string $column, $value1, $value2): static /** * Ajoute une clause HAVING NOT BETWEEN avec OR + * + * @param mixed $value1 + * @param mixed $value2 */ public function orHavingNotBetween(string $column, $value1, $value2): static { @@ -729,9 +795,9 @@ public function orHavingNotBetween(string $column, $value1, $value2): static public function havingNull(string $column, string $boolean = 'and', bool $not = false): static { return $this->addCondition('havings', 'null', [ - 'column' => $column, + 'column' => $column, 'boolean' => $boolean, - 'not' => $not + 'not' => $not, ]); } @@ -765,7 +831,7 @@ public function orHavingNotNull(string $column): static public function havingLike(string $column, string $value, string $boolean = 'and', bool $not = false, bool $caseSensitive = false, string $side = 'both'): static { $operator = $not ? 'NOT LIKE' : 'LIKE'; - + if ($caseSensitive && $this->db->getDriver() === 'pgsql') { $operator = $not ? 'NOT ILIKE' : 'ILIKE'; } elseif ($caseSensitive && $this->db->getDriver() === 'mysql') { @@ -782,11 +848,11 @@ public function havingLike(string $column, string $value, string $boolean = 'and $value = str_replace('%', '', $value); } - $value = match($side) { + $value = match ($side) { 'before' => "%{$value}", 'after' => "{$value}%", 'both' => "%{$value}%", - default => $value + default => $value, }; return $this->addCondition('havings', 'basic', [ @@ -830,9 +896,9 @@ public function havingExists(Closure $callback, string $boolean = 'and', bool $n $callback($query); return $this->addCondition('havings', 'exists', [ - 'query' => $query, + 'query' => $query, 'boolean' => $boolean, - 'not' => $not + 'not' => $not, ]); } @@ -860,30 +926,14 @@ public function orHavingNotExists(Closure $callback): static return $this->havingNotExists($callback, 'or'); } - /* - |-------------------------------------------------------------------------- - | JOIN CLAUSES - |-------------------------------------------------------------------------- - */ - - /** - * Liste des jointures - */ - protected array $joins = []; - - /** - * Type de jointures entre tables - */ - protected array $joinTypes = [ - 'INNER', 'LEFT', 'RIGHT', 'FULL OUTER', - 'CROSS', 'LEFT OUTER', 'RIGHT OUTER', - ]; - /** * Ajoute une jointure à la requête * Supporte les anciennes et nouvelles syntaxes + * + * @param mixed $first + * @param mixed|null $second */ - public function join(string $table, $first, ?string $operator = null, $second = null, string $type = 'INNER'): self + public function join(string $table, $first, ?string $operator = null, $second = null, string $type = 'INNER'): static { // Ancienne syntaxe : join(table, array|string $fields, string $type) ou avec un tableau associatif if ((is_string($first) && $second === null) || is_array($first)) { @@ -907,24 +957,33 @@ public function join(string $table, $first, ?string $operator = null, $second = /** * Génère la partie JOIN (de type FULL OUTER) de la requête + * + * @param mixed $first + * @param mixed|null $second */ - public function fullJoin(string $table, $first, ?string $operator = null, $second = null): self + public function fullJoin(string $table, $first, ?string $operator = null, $second = null): static { return $this->join($table, $first, $operator, $second, 'FULL OUTER'); } /** * Génère la partie JOIN (de type INNER) de la requête + * + * @param mixed $first + * @param mixed|null $second */ - public function innerJoin(string $table, $first, ?string $operator = null, $second = null): self + public function innerJoin(string $table, $first, ?string $operator = null, $second = null): static { return $this->join($table, $first, $operator, $second, 'INNER'); } /** * Génère la partie JOIN (de type LEFT) de la requête + * + * @param mixed $first + * @param mixed|null $second */ - public function leftJoin(string $table, $first, ?string $operator = null, $second = null, bool $outer = false): self + public function leftJoin(string $table, $first, ?string $operator = null, $second = null, bool $outer = false): static { $type = 'LEFT' . ($outer ? ' OUTER' : ''); @@ -933,16 +992,22 @@ public function leftJoin(string $table, $first, ?string $operator = null, $secon /** * Génère la partie JOIN (de type LEFT OUTER) de la requête + * + * @param mixed $first + * @param mixed|null $second */ - public function leftOuterJoin(string $table, $first, ?string $operator = null, $second = null): self + public function leftOuterJoin(string $table, $first, ?string $operator = null, $second = null): static { return $this->leftJoin($table, $first, $operator, $second, true); } /** * Génère la partie JOIN (de type RIGHT) de la requête + * + * @param mixed $first + * @param mixed|null $second */ - public function rightJoin(string $table, $first, ?string $operator = null, $second = null, bool $outer = false): self + public function rightJoin(string $table, $first, ?string $operator = null, $second = null, bool $outer = false): static { $type = 'RIGHT' . ($outer ? ' OUTER' : ''); @@ -951,8 +1016,11 @@ public function rightJoin(string $table, $first, ?string $operator = null, $seco /** * Génère la partie JOIN (de type RIGHT OUTER) de la requête + * + * @param mixed $first + * @param mixed|null $second */ - public function rightOuterJoin(string $table, $first, ?string $operator = null, $second = null): self + public function rightOuterJoin(string $table, $first, ?string $operator = null, $second = null): static { return $this->rightJoin($table, $first, $operator, $second, true); } @@ -960,7 +1028,7 @@ public function rightOuterJoin(string $table, $first, ?string $operator = null, /** * Génère la partie JOIN (de type CROSS JOIN) de la requête */ - public function crossJoin(string $table, ?Closure $first = null, ?string $operator = null, ?string $second = null): self + public function crossJoin(string $table, ?Closure $first = null, ?string $operator = null, ?string $second = null): static { if ($first instanceof Closure) { return $this->join($table, $first, null, null, 'CROSS'); @@ -971,25 +1039,25 @@ public function crossJoin(string $table, ?Closure $first = null, ?string $operat /** * Ajoute une jointure avec une sous-requête - * + * * @param (Closure(JoinClause): void) $callback */ - public function joinSub(Closure|BuilderInterface $query, string $as, Closure $callback, string $type = 'INNER'): self + public function joinSub(BuilderInterface|Closure $query, string $as, Closure $callback, string $type = 'INNER'): static { $subquery = $this->buildSubquery($query, true, $as); - + $join = new JoinClause($this->db, $type, $subquery); $callback($join); - + $this->joins[] = $join; - + return $this->asCrud('select'); } /** * Ajoute une jointure avec conditions complexes */ - public function joinComplex(string $table, Closure $callback, string $type = 'INNER'): self + public function joinComplex(string $table, Closure $callback, string $type = 'INNER'): static { return $this->join($table, $callback, $type); } @@ -997,29 +1065,29 @@ public function joinComplex(string $table, Closure $callback, string $type = 'IN /** * Ajoute une jointure avec des conditions supplémentaires */ - public function joinWhere(string $table, string $first, string $operator, string $second, string $type = 'INNER'): self + public function joinWhere(string $table, string $first, string $operator, string $second, string $type = 'INNER'): static { $join = new JoinClause($this->db, $type, $this->db->makeTableName($table)); $join->on($first, $operator, $second); - + $this->joins[] = $join; - + return $this->asCrud('select'); } /** * Ajoute une jointure LATERAL */ - public function joinLateral(Closure|BuilderInterface $query, string $as, Closure $callback, string $type = 'INNER'): self + public function joinLateral(BuilderInterface|Closure $query, string $as, Closure $callback, string $type = 'INNER'): static { $subquery = $this->buildSubquery($query, true, $as); $subquery = 'LATERAL ' . $subquery; - + $join = new JoinClause($this->db, $type, $subquery); $callback($join); - + $this->joins[] = $join; - + return $this->asCrud('select'); } @@ -1030,27 +1098,27 @@ public function joinLateral(Closure|BuilderInterface $query, string $as, Closure */ /** - * - * * Supporte les signatures : * - orderBy(string $column, string $direction = 'ASC') * - orderBy(array $columns, string $direction = 'ASC') * - orderBy(Expression $expression) * - orderBy(Closure $closure) + * + * @param mixed $column */ - public function orderBy($column, string $direction = 'ASC'): self + public function orderBy($column, string $direction = 'ASC'): static { $direction = strtoupper(trim($direction)); - - if (!in_array($direction, ['ASC', 'DESC', 'RANDOM'], true)) { + + if (! in_array($direction, ['ASC', 'DESC', 'RANDOM'], true)) { throw new InvalidArgumentException("Invalid direction: {$direction}"); } if ($column instanceof Expression) { return $this->addCondition('orders', [ - 'column' => $column, + 'column' => $column, 'direction' => '', - 'raw' => true + 'raw' => true, ])->asCrud('select'); } @@ -1063,7 +1131,7 @@ public function orderBy($column, string $direction = 'ASC'): self } $column = trim($column); - + if ($direction === 'RANDOM') { return $this->orderByRandom($column); } @@ -1071,14 +1139,14 @@ public function orderBy($column, string $direction = 'ASC'): self return $this->addCondition('orders', [ 'column' => $column, 'direction' => in_array($direction, ['ASC', 'DESC'], true) ? ' ' . $direction : '', - 'raw' => false + 'raw' => false, ])->asCrud('select'); } /** * Ajoute un tri aléatoire */ - public function rand(?int $digit = null): self + public function rand(?int $digit = null): static { if ($digit === null) { $digit = ''; @@ -1090,7 +1158,7 @@ public function rand(?int $digit = null): self /** * Ajoute plusieurs ORDER BY à la fois */ - public function orderByMultiple(array $orders, string $direction = 'ASC'): self + public function orderByMultiple(array $orders, string $direction = 'ASC'): static { foreach ($orders as $key => $value) { if (is_int($key)) { @@ -1101,65 +1169,65 @@ public function orderByMultiple(array $orders, string $direction = 'ASC'): self $this->orderBy($key, $value); } } - + return $this; } /** * Ajoute une clause ORDER BY avec une sous-requête */ - public function orderBySub(Closure|BuilderInterface $query, string $direction = 'ASC'): self + public function orderBySub(BuilderInterface|Closure $query, string $direction = 'ASC'): static { - $subquery = $this->buildSubquery($query, true); + $subquery = $this->buildSubquery($query, true); $this->orders[] = [ 'column' => new Expression($subquery), 'direction' => ' ' . $direction, - 'raw' => true + 'raw' => true, ]; - + return $this->asCrud('select'); } /** * Ajoute une clause ORDER BY NULLS FIRST/LAST (PostgreSQL) */ - public function orderByNulls(string $column, string $direction = 'ASC', string $nulls = 'LAST'): self + public function orderByNulls(string $column, string $direction = 'ASC', string $nulls = 'LAST'): static { - $column = $this->buildParseField($column); + $column = $this->buildParseField($column); $direction = strtoupper($direction); - $nulls = strtoupper($nulls); - - if (!in_array($nulls, ['FIRST', 'LAST'])) { + $nulls = strtoupper($nulls); + + if (! in_array($nulls, ['FIRST', 'LAST'], true)) { $nulls = 'LAST'; } - + $this->orders[] = [ 'column' => new Expression("{$column} {$direction} NULLS {$nulls}"), 'direction' => '', - 'raw' => true + 'raw' => true, ]; - + return $this->asCrud('select'); } /** * Réinitialise les clauses ORDER BY */ - public function reorder(?string $column = null, string $direction = 'ASC'): self + public function reorder(?string $column = null, string $direction = 'ASC'): static { $this->orders = []; - + if ($column !== null) { $this->orderBy($column, $direction); } - + return $this; } /** * Ajoute une clause ORDER BY en dernier */ - public function orderByAppend(string $column, string $direction = 'ASC'): self + public function orderByAppend(string $column, string $direction = 'ASC'): static { return $this->orderBy($column, $direction); } @@ -1167,28 +1235,28 @@ public function orderByAppend(string $column, string $direction = 'ASC'): self /** * Ajoute une clause ORDER BY en premier */ - public function orderByPrepend(string $column, string $direction = 'ASC'): self + public function orderByPrepend(string $column, string $direction = 'ASC'): static { $direction = in_array($direction, ['ASC', 'DESC'], true) ? ' ' . $direction : ''; - + $order = [ - 'column' => $this->buildColumnName($column), + 'column' => $this->buildColumnName($column), 'direction' => $direction, - 'raw' => false + 'raw' => false, ]; - + array_unshift($this->orders, $order); - + return $this->asCrud('select'); } /** * Supprime toutes les clauses ORDER BY */ - public function withoutOrder(): self + public function withoutOrder(): static { $this->orders = []; - + return $this; } @@ -1216,7 +1284,7 @@ protected function removeExistingOrdersFor(string $column): array $column = $this->buildColumnName($column); return (new Collection($this->orders)) - ->reject(fn ($order) => isset($order['column']) && $order['column'] === $column) + ->reject(static fn ($order) => isset($order['column']) && $order['column'] === $column) ->values() ->all(); } @@ -1235,16 +1303,18 @@ protected function enforceOrderBy(): void /** * Ajoute une clause GROUP BY - * + * * Supporte les signatures : * - groupBy(string|array $column) * - groupBy(Expression $expression) + * + * @param mixed $column */ - public function groupBy($column): self + public function groupBy($column): static { if ($column instanceof Expression) { $this->groups[] = $column; - + return $this->asCrud('select'); } @@ -1265,21 +1335,21 @@ public function groupBy($column): self /** * Ajoute une clause GROUP BY avec une sous-requête */ - public function groupBySub(Closure|BuilderInterface $query): self + public function groupBySub(BuilderInterface|Closure $query): static { - $subquery = $this->buildSubquery($query, true); + $subquery = $this->buildSubquery($query, true); $this->groups[] = new Expression($subquery); - + return $this->asCrud('select'); } /** * Supprime toutes les clauses GROUP BY */ - public function withoutGroup(): self + public function withoutGroup(): static { $this->groups = []; - + return $this; } @@ -1321,7 +1391,7 @@ protected function whereNested(Closure $callback, string $boolean = 'and'): stat */ protected function whereArray(array $conditions, string $boolean = 'and', bool $not = false, string $function = 'where'): static { - if (! in_array($function, ['where', 'whereColumn', 'whereDate'])) { + if (! in_array($function, ['where', 'whereColumn', 'whereDate'], true)) { $function = 'where'; } @@ -1335,7 +1405,7 @@ protected function whereArray(array $conditions, string $boolean = 'and', bool $ $this->{$function}($key, $val[0], $val[1], $boolean); } else { [$key, $operator, $val] = $this->normalizeWhereParameters($key, $val, null); - $operator = $not ? $this->invertOperator($operator) : $operator; + $operator = $not ? $this->invertOperator($operator) : $operator; $this->{$function}($key, $operator, $val, $boolean); } } @@ -1353,16 +1423,16 @@ protected function havingNested(Closure $callback, string $boolean = 'and'): sta if (count($query->havings)) { $this->havings[] = [ - 'type' => 'nested', - 'query' => $query, - 'boolean' => $boolean + 'type' => 'nested', + 'query' => $query, + 'boolean' => $boolean, ]; } return $this; } - - protected function addCondition(string $property, array|string $type, ?array $condition = null): self + + protected function addCondition(string $property, array|string $type, ?array $condition = null): static { if (! in_array($property, ['wheres', 'havings', 'orders'], true)) { throw new InvalidArgumentException(); @@ -1370,13 +1440,13 @@ protected function addCondition(string $property, array|string $type, ?array $co if (is_array($type)) { $condition = $type; - $type = null; + $type = null; } if ($type !== null) { $condition['type'] = $type; } - + foreach (['column', 'column1', 'column2', 'first', 'second'] as $column) { if (isset($condition[$column]) && is_string($condition[$column])) { $condition[$column] = $this->buildColumnName($condition[$column]); @@ -1384,16 +1454,16 @@ protected function addCondition(string $property, array|string $type, ?array $co } // Ajouter les bindings dans le contexte approprié - $context = match($property) { + $context = match ($property) { 'wheres' => 'where', 'havings' => 'having', 'orders' => 'order', - default => 'where' + default => 'where', }; - + if (isset($condition['values'])) { $this->bindings->addMany($condition['values'], $context); - } else if (isset($condition['value']) && !$condition['value'] instanceof Expression) { + } elseif (isset($condition['value']) && ! $condition['value'] instanceof Expression) { $this->bindings->add($condition['value'], $context); } @@ -1410,10 +1480,10 @@ protected function normalizeWhereParameters(mixed $column, mixed $operator, mixe if ($value === null) { // Si seulement 2 paramètres sont fournis, le deuxième est la valeur if ($operator !== null) { - $value = $operator; + $value = $operator; [$column, $operator] = Utils::extractOperatorFromColumn($column, $operator); - } else if (null !== $parsed = Utils::parseExpression($column)) { - [$column, $operator, $value] = $parsed; + } elseif (null !== $parsed = Utils::parseExpression($column)) { + [$column, $operator, $value] = $parsed; } } @@ -1426,7 +1496,7 @@ protected function normalizeWhereParameters(mixed $column, mixed $operator, mixe /** * Normalise les opérateurs personnalisés - * + * * @deprecated use Utils::translateOperator() instead */ protected function normalizeOperator(string $operator): string @@ -1441,13 +1511,14 @@ protected function invertOperator(string $operator): string { return Utils::invertOperator($operator); } + /** * Support de l'ancienne syntaxe de jointure */ - protected function legacyJoin(string $table, array|string $fields, string $type = 'INNER'): self + protected function legacyJoin(string $table, array|string $fields, string $type = 'INNER'): static { $type = strtoupper(trim($type)); - if (!in_array($type, $this->joinTypes, true)) { + if (! in_array($type, $this->joinTypes, true)) { $type = 'INNER'; } @@ -1471,11 +1542,12 @@ protected function legacyJoin(string $table, array|string $fields, string $type } else { // Tableau associatif [$key, $operator, $value] = $this->normalizeWhereParameters($key, $value, null); - - $join->on($this->buildColumnName($key), - $operator, + + $join->on( + $this->buildColumnName($key), + $operator, $this->buildColumnName($value), - $key[0] === '|' ? 'or' : 'and' + $key[0] === '|' ? 'or' : 'and', ); } } @@ -1489,22 +1561,22 @@ protected function legacyJoin(string $table, array|string $fields, string $type /** * Gère le tri aléatoire */ - protected function orderByRandom(string $column): self + protected function orderByRandom(string $column): static { $driver = $this->db->getDriver(); - + // Si le champ est numérique, c'est une seed if (ctype_digit($column)) { $seed = (int) $column; - + if ($driver === 'mysql') { $column = "RAND({$seed})"; } elseif ($driver === 'pgsql') { // Pour PostgreSQL, on utilise SET SEED d'abord $this->db->query("SELECT setseed({$seed})"); - $column = "RANDOM()"; + $column = 'RANDOM()'; } else { - $column = "RANDOM()"; + $column = 'RANDOM()'; } } else { $column = $driver === 'mysql' ? 'RAND()' : 'RANDOM()'; @@ -1513,7 +1585,7 @@ protected function orderByRandom(string $column): self $this->orders[] = [ 'column' => new Expression($column), 'direction' => '', - 'raw' => true + 'raw' => true, ]; return $this; diff --git a/src/Builder/Concerns/DataMethods.php b/src/Builder/Concerns/DataMethods.php index 10ae81b..f579f1c 100644 --- a/src/Builder/Concerns/DataMethods.php +++ b/src/Builder/Concerns/DataMethods.php @@ -51,7 +51,6 @@ public function sum(string $column) return $this->aggregate('sum', $column); } - /** * Récupère la moyenne des valeurs d'un champ */ @@ -66,7 +65,7 @@ public function avg(string $column) public function count(string $column = '*') { $builder = $this->clone(); - $column = $this->buildColumnName($column); + $column = $this->buildColumnName($column); if ($builder->distinct || $builder->hasGroup()) { $builder = $this->fromSubquery($builder, 'count_table') @@ -101,18 +100,15 @@ public function countAllResults() } /** - * - * @param string $type - * @param string $column * @return float|string */ public function aggregate(string $type, string $column) { - $alias = $type . '_value'; + $alias = $type . '_value'; $column = $this->buildColumnName($column); $result = $this->clone()->selectRaw(sprintf('%s(%s) AS %s', strtoupper($type), $column, $alias)); - + return $this->testMode ? $result->sql() : (float) ($result->value($alias) ?? 0); } @@ -124,10 +120,10 @@ public function aggregate(string $type, string $column) /** * Insère en utilisant le résultat d'une sous-requête - * + * * @return int|string */ - public function insertUsing(array $columns, Closure|BuilderInterface $query) + public function insertUsing(array $columns, BuilderInterface|Closure $query) { $this->crud = 'insert'; @@ -138,20 +134,20 @@ public function insertUsing(array $columns, Closure|BuilderInterface $query) } $this->columns = $columns; - $this->values = ['query' => $query]; + $this->values = ['query' => $query]; if ($this->testMode) { return $this->compiler->compileInsertUsing($this); } $result = $this->execute(); - + return $result instanceof Result ? $result->affectedRows() : 0; } /** * Insère et récupère l'ID généré - * + * * @return int|static|string|null */ public function insertGetId(array $values, ?string $sequence = null) @@ -165,7 +161,7 @@ public function insertGetId(array $values, ?string $sequence = null) /** * Insère et récupère l'enregistrement inséré - * + * * @return object|static|string|null */ public function insertAndGet(array $values) @@ -202,7 +198,7 @@ public static function raw(string $value): Expression /** * Ajoute une expression brute dans la clause SELECT */ - public function selectRaw(string|Expression $expression, array $bindings = []) + public function selectRaw(Expression|string $expression, array $bindings = []) { if (is_string($expression)) { $expression = new Expression($expression); @@ -220,9 +216,9 @@ public function selectRaw(string|Expression $expression, array $bindings = []) public function whereRaw(string $sql, array $bindings = [], string $boolean = 'and'): self { $this->wheres[] = [ - 'type' => 'raw', - 'sql' => $sql, - 'boolean' => $boolean + 'type' => 'raw', + 'sql' => $sql, + 'boolean' => $boolean, ]; $this->bindings->addMany($bindings); @@ -244,13 +240,13 @@ public function orWhereRaw(string $sql, array $bindings = []): self public function havingRaw(string $sql, array $bindings = [], string $boolean = 'and'): static { $this->havings[] = [ - 'type' => 'raw', - 'sql' => $sql, - 'boolean' => $boolean + 'type' => 'raw', + 'sql' => $sql, + 'boolean' => $boolean, ]; - + $this->bindings->addMany($bindings); - + return $this; } @@ -270,11 +266,11 @@ public function orderByRaw(string $expression, array $bindings = []): self $this->orders[] = [ 'column' => new Expression($expression), 'direction' => '', - 'raw' => true + 'raw' => true, ]; - + $this->bindings->addMany($bindings); - + return $this->asCrud('select'); } @@ -285,7 +281,7 @@ public function groupByRaw(string $expression, array $bindings = []): self { $this->groups[] = new Expression($expression); $this->bindings->addMany($bindings); - + return $this->asCrud('select'); } @@ -296,7 +292,7 @@ public function joinRaw(string $table, string $on, array $bindings = [], string { $this->joins[] = $type . ' JOIN ' . $table . ' ON ' . $on; $this->bindings->addMany($bindings); - + return $this->asCrud('select'); } @@ -307,4 +303,4 @@ protected function isRawExpression(mixed $value): bool { return $value instanceof Expression; } -} \ No newline at end of file +} diff --git a/src/Builder/Concerns/ProxyMethods.php b/src/Builder/Concerns/ProxyMethods.php index 7300b05..8926daa 100644 --- a/src/Builder/Concerns/ProxyMethods.php +++ b/src/Builder/Concerns/ProxyMethods.php @@ -17,11 +17,79 @@ /** * Gère les appels aux méthodes alias via un système de proxy + * + * @method ConnectionInterface getConnection() Alias de db() - Récupère la connexion à la base de données + * @method self latest(\Closure|\BlitzPHP\Database\Builder\BaseBuilder|\BlitzPHP\Database\Query\Expression|string $column = 'created_at') Alias de orderBy() avec direction DESC - Ajoute un tri par date décroissante + * @method self oldest(\Closure|\BlitzPHP\Database\Builder\BaseBuilder|\BlitzPHP\Database\Query\Expression|string $column = 'created_at') Alias de orderBy() avec direction ASC - Ajoute un tri par date croissante + * + * // Récupération de résultats + * @method mixed one(int|string $type = \PDO::FETCH_OBJ) Alias de first() - Récupère le premier résultat + * @method array all(int|string $type = \PDO::FETCH_OBJ) Alias de result() - Récupère tous les résultats sous forme de tableau + * @method \BlitzPHP\Utilities\Iterable\Collection get(int|string $type = \PDO::FETCH_OBJ) Alias de collect() - Récupère tous les résultats sous forme de Collection + * + * // Commandes SQL + * @method self order(array|string $columns, string $direction = 'ASC') Alias de orderBy() - Ajoute une clause ORDER BY + * @method self group(array|string $columns) Alias de groupBy() - Ajoute une clause GROUP BY + * @method self addSelect(array|string $columns) Alias de select() - Ajoute des colonnes à la sélection + * @method self selectSub(\BlitzPHP\Contracts\Database\BuilderInterface $subquery, string $as) Alias de selectSubquery() - Ajoute une sous-requête dans la sélection + * @method self skip(int $offset) Alias de offset() - Ajoute une clause OFFSET + * @method self take(int $limit) Alias de limit() - Ajoute une clause LIMIT + * + * // Conditions WHERE (basiques) + * @method self notWhere(array|string $column, mixed $operator = null, mixed $value = null, string $boolean = 'and') Alias de whereNot() - Ajoute une clause WHERE NOT + * @method self orNotWhere(array|string $column, mixed $operator = null, mixed $value = null) Alias de orWhereNot() - Ajoute une clause WHERE NOT avec OR + * + * // Conditions WHERE IN + * @method self in(string $column, array|\Closure $values, string $boolean = 'and', bool $not = false) Alias de whereIn() - Ajoute une clause WHERE IN + * @method self notIn(string $column, array|\Closure $values, string $boolean = 'and') Alias de whereNotIn() - Ajoute une clause WHERE NOT IN + * @method self orIn(string $column, array|\Closure $values) Alias de orWhereIn() - Ajoute une clause WHERE IN avec OR + * @method self orNotIn(string $column, array|\Closure $values) Alias de orWhereNotIn() - Ajoute une clause WHERE NOT IN avec OR * - * @method ConnectionInterface getConnection() - * @method self latest(\Closure|\BlitzPHP\Database\Builder\BaseBuilder|\BlitzPHP\Database\Query\Expression|string $column = 'created_at') Ajoute une clause "order by" pour un timestamp à la requête. - * @method self oldest(\Closure|\BlitzPHP\Database\Builder\BaseBuilder|\BlitzPHP\Database\Query\Expression|string $column = 'created_at') Ajoute une clause "order by" pour un timestamp à la requête. + * // Conditions WHERE LIKE + * @method self like(string|array $column, string $value = '', string $side = 'both', string $boolean = 'and', bool $not = false, bool $caseSensitive = false) Alias de whereLike() - Ajoute une clause WHERE LIKE + * @method self notLike(string|array $column, string $value = '', string $side = 'both', string $boolean = 'and', bool $caseSensitive = false) Alias de whereNotLike() - Ajoute une clause WHERE NOT LIKE + * @method self orLike(string|array $column, string $value = '', string $side = 'both', bool $caseSensitive = false) Alias de orWhereLike() - Ajoute une clause WHERE LIKE avec OR + * @method self orNotLike(string|array $column, string $value = '', string $side = 'both', bool $caseSensitive = false) Alias de orWhereNotLike() - Ajoute une clause WHERE NOT LIKE avec OR * + * // Conditions WHERE BETWEEN + * @method self between(string $column, mixed $value1, mixed $value2, string $boolean = 'and', bool $not = false) Alias de whereBetween() - Ajoute une clause WHERE BETWEEN + * @method self notBetween(string $column, mixed $value1, mixed $value2, string $boolean = 'and') Alias de whereNotBetween() - Ajoute une clause WHERE NOT BETWEEN + * @method self orBetween(string $column, mixed $value1, mixed $value2) Alias de orWhereBetween() - Ajoute une clause WHERE BETWEEN avec OR + * @method self orNotBetween(string $column, mixed $value1, mixed $value2) Alias de orWhereNotBetween() - Ajoute une clause WHERE NOT BETWEEN avec OR + * + * // Conditions WHERE COLUMN + * @method self notWhereColumn(array|string $first, string $operator = null, string $second = null, string $boolean = 'and') Alias de whereNotColumn() - Ajoute une clause WHERE avec comparaison de colonnes inversée + * @method self orNotWhereColumn(array|string $first, string $operator = null, string $second = null) Alias de orWhereNotColumn() - Ajoute une clause WHERE avec comparaison de colonnes inversée avec OR + * + * // Conditions HAVING + * @method self havingIn(string $column, array|\Closure $values, string $boolean = 'and', bool $not = false) Alias de havingIn() - Ajoute une clause HAVING IN + * @method self havingNotIn(string $column, array|\Closure $values, string $boolean = 'and') Alias de havingNotIn() - Ajoute une clause HAVING NOT IN + * @method self orHavingIn(string $column, array|\Closure $values) Alias de orHavingIn() - Ajoute une clause HAVING IN avec OR + * @method self orHavingNotIn(string $column, array|\Closure $values) Alias de orHavingNotIn() - Ajoute une clause HAVING NOT IN avec OR + * @method self havingLike(string $column, string $value, string $side = 'both', string $boolean = 'and', bool $not = false, bool $caseSensitive = false) Alias de havingLike() - Ajoute une clause HAVING LIKE + * @method self notHavingLike(string $column, string $value, string $side = 'both', string $boolean = 'and', bool $caseSensitive = false) Alias de havingNotLike() - Ajoute une clause HAVING NOT LIKE + * @method self orHavingLike(string $column, string $value, string $side = 'both', bool $caseSensitive = false) Alias de orHavingLike() - Ajoute une clause HAVING LIKE avec OR + * @method self orHavingNotLike(string $column, string $value, string $side = 'both', bool $caseSensitive = false) Alias de orHavingNotLike() - Ajoute une clause HAVING NOT LIKE avec OR + * @method self havingBetween(string $column, mixed $value1, mixed $value2, string $boolean = 'and', bool $not = false) Alias de havingBetween() - Ajoute une clause HAVING BETWEEN + * @method self havingNotBetween(string $column, mixed $value1, mixed $value2, string $boolean = 'and') Alias de havingNotBetween() - Ajoute une clause HAVING NOT BETWEEN + * @method self orHavingBetween(string $column, mixed $value1, mixed $value2) Alias de orHavingBetween() - Ajoute une clause HAVING BETWEEN avec OR + * @method self orHavingNotBetween(string $column, mixed $value1, mixed $value2) Alias de orHavingNotBetween() - Ajoute une clause HAVING NOT BETWEEN avec OR + * @method self havingNull(string $column, string $boolean = 'and', bool $not = false) Alias de havingNull() - Ajoute une clause HAVING NULL + * @method self havingNotNull(string $column, string $boolean = 'and') Alias de havingNotNull() - Ajoute une clause HAVING NOT NULL + * @method self orHavingNull(string $column) Alias de orHavingNull() - Ajoute une clause HAVING NULL avec OR + * @method self orHavingNotNull(string $column) Alias de orHavingNotNull() - Ajoute une clause HAVING NOT NULL avec OR + * + * // Tri + * @method self sortAsc(string|array $column) Alias de orderBy() avec direction ASC - Ajoute un tri croissant + * @method self sortDesc(string|array $column) Alias de orderBy() avec direction DESC - Ajoute un tri décroissant + * @method self sortRand(?int $digit = null) Alias de rand() - Ajoute un tri aléatoire + * @method self inRandomOrder(?int $digit = null) Alias de rand() - Ajoute un tri aléatoire + * @method self reorderDesc(?string $column = null) Alias de reorder() avec direction DESC - Réinitialise et ajoute un tri décroissant + * + * // Insertions + * @method int|string bulkInsert(array $data, bool $ignore = false, int $chunkSize = 100) Alias de bulkInsert() - Insertion multiple + * @method int|string bulkInsertIgnore(array $data, int $chunkSize = 100) Alias de bulkInsertIgnore() - Insertion multiple avec IGNORE + * * @mixin \BlitzPHP\Database\Builder\BaseBuilder */ trait ProxyMethods @@ -30,25 +98,25 @@ trait ProxyMethods /** * Mapping des méthodes alias vers leurs méthodes cibles + * + * @var array */ - protected array $methodAliases = [ - 'getConnection' => 'db', + protected array $methodAliases = [ + 'getConnection' => 'db', // Récupération de résultats - 'one' => 'first', - - // Requêtes - 'all' => 'result', - 'get' => 'result', - + 'one' => 'first', + 'all' => 'result', + 'get' => 'collect', + // Commandes SQL - 'order' => 'orderBy', - 'group' => 'groupBy', - 'addSelect' => 'select', - 'selectSub' => 'selectSubquery', - 'skip' => 'offset', - 'take' => 'limit', - + 'order' => 'orderBy', + 'group' => 'groupBy', + 'addSelect' => 'select', + 'selectSub' => 'selectSubquery', + 'skip' => 'offset', + 'take' => 'limit', + // Conditions WHERE 'notWhere' => 'whereNot', 'orNotWhere' => 'orWhereNot', @@ -66,12 +134,25 @@ trait ProxyMethods 'orNotBetween' => 'orWhereNotBetween', 'notWhereColumn' => 'whereNotColumn', 'orNotWhereColumn' => 'orWhereNotColumn', - + // Conditions HAVING + 'havingIn' => 'havingIn', + 'havingNotIn' => 'havingNotIn', + 'orHavingIn' => 'orHavingIn', + 'orHavingNotIn' => 'orHavingNotIn', + 'havingLike' => 'havingLike', 'notHavingLike' => 'havingNotLike', 'orHavingLike' => 'orHavingLike', 'orHavingNotLike' => 'orHavingNotLike', - + 'havingBetween' => 'havingBetween', + 'havingNotBetween' => 'havingNotBetween', + 'orHavingBetween' => 'orHavingBetween', + 'orHavingNotBetween'=> 'orHavingNotBetween', + 'havingNull' => 'havingNull', + 'havingNotNull' => 'havingNotNull', + 'orHavingNull' => 'orHavingNull', + 'orHavingNotNull' => 'orHavingNotNull', + // Tri 'sortAsc' => 'orderBy', 'sortDesc' => 'orderBy', @@ -80,17 +161,20 @@ trait ProxyMethods 'latest' => 'orderBy', 'oldest' => 'orderBy', 'reorderDesc' => 'reorder', - + // Insertions - 'bulckInsert' => 'bulkInsert', - 'bulckInsertIgnore' => 'bulkInsertIgnore', + 'bulckInsert' => 'bulkInsert', + 'bulckInsertIgnore' => 'bulkInsertIgnore', ]; /** * Gère les appels aux méthodes alias - * + * + * @param string $method Nom de la méthode appelée + * @param array $parameters Paramètres de la méthode + * * @return mixed - * + * * @throws BadMethodCallException */ public function __call(string $method, array $parameters) @@ -101,9 +185,8 @@ public function __call(string $method, array $parameters) if (isset($this->methodAliases[$method])) { $targetMethod = $this->methodAliases[$method]; - $parameters = $this->adaptParameters($method, $targetMethod, $parameters); - + return $this->{$targetMethod}(...$parameters); } @@ -112,22 +195,28 @@ public function __call(string $method, array $parameters) /** * Adapte les paramètres d'une méthode alias vers sa méthode cible + * + * @param string $alias Nom de l'alias appelé + * @param string $target Nom de la méthode cible + * @param array $params Paramètres originaux + * + * @return array Paramètres adaptés */ protected function adaptParameters(string $alias, string $target, array $params): array { - return match($alias) { + return match ($alias) { // Pour sortAsc/sortDesc, on ajoute la direction - 'sortAsc' => [$params[0], 'ASC'], - 'sortDesc' => [$params[0], 'DESC'], - + 'sortAsc' => [$params[0], 'ASC'], + 'sortDesc' => [$params[0], 'DESC'], + // Pour latest/oldest, direction par défaut + paramètre optionnel - 'latest' => [$params[0] ?? 'created_at', 'DESC'], - 'oldest' => [$params[0] ?? 'created_at', 'ASC'], - - 'reorderDesc' => [$params[0] ?? null, 'DESC'], - + 'latest' => [$params[0] ?? 'created_at', 'DESC'], + 'oldest' => [$params[0] ?? 'created_at', 'ASC'], + + 'reorderDesc' => [$params[0] ?? null, 'DESC'], + // Pour les alias simples, pas de modification - default => $params, + default => $params, }; } } diff --git a/src/Builder/JoinClause.php b/src/Builder/JoinClause.php index d190c77..802072c 100644 --- a/src/Builder/JoinClause.php +++ b/src/Builder/JoinClause.php @@ -31,7 +31,7 @@ class JoinClause * Opérateurs supportés */ protected array $operators = [ - '=', '<', '>', '<=', '>=', '<>', '!=', + '=', '<', '>', '<=', '>=', '<>', '!=', 'LIKE', 'NOT LIKE', 'ILIKE', 'NOT ILIKE', 'IN', 'NOT IN', 'EXISTS', 'NOT EXISTS', 'BETWEEN', 'NOT BETWEEN', @@ -50,61 +50,65 @@ public function __construct(protected BaseConnection $db, protected string $type /** * Ajoute une condition ON avec AND */ - public function on(string|Closure $first, ?string $operator = null, ?string $second = null, string $boolean = 'and'): self + public function on(Closure|string $first, ?string $operator = null, ?string $second = null, string $boolean = 'and'): self { if ($first instanceof Closure) { return $this->whereNested($first, $boolean); } if ($second === null) { - $second = $operator; + $second = $operator; $operator = '='; } return $this->addCondition([ - 'type' => 'basic', - 'first' => $first, + 'type' => 'basic', + 'first' => $first, 'operator' => $operator, - 'second' => $second, - 'boolean' => $boolean + 'second' => $second, + 'boolean' => $boolean, ]); } /** * Ajoute une condition ON avec OR */ - public function orOn(string|Closure $first, ?string $operator = null, ?string $second = null): self + public function orOn(Closure|string $first, ?string $operator = null, ?string $second = null): self { return $this->on($first, $operator, $second, 'or'); } /** * Ajoute une condition supplémentaire sur la jointure + * + * @param mixed|null $value */ - public function where(string|Closure $first, ?string $operator = null, $value = null, string $boolean = 'and'): self + public function where(Closure|string $first, ?string $operator = null, $value = null, string $boolean = 'and'): self { if ($first instanceof Closure) { return $this->whereNested($first, $boolean); } if ($value === null) { - $value = $operator; + $value = $operator; $operator = '='; } return $this->addCondition([ - 'type' => 'where', - 'first' => $first, + 'type' => 'where', + 'first' => $first, 'operator' => $operator, - 'value' => $value, - 'boolean' => $boolean + 'value' => $value, + 'boolean' => $boolean, ]); } /** * Ajoute une condition WHERE avec OR + * + * @param mixed|null $value */ - public function orWhere(string|Closure $first, ?string $operator = null, $value = null): self + public function orWhere(Closure|string $first, ?string $operator = null, $value = null): self { return $this->where($first, $operator, $value, 'or'); } @@ -115,11 +119,11 @@ public function orWhere(string|Closure $first, ?string $operator = null, $value public function whereIn(string $column, array $values, string $boolean = 'and'): self { return $this->addCondition([ - 'type' => 'in', - 'column' => $column, - 'values' => $values, + 'type' => 'in', + 'column' => $column, + 'values' => $values, 'boolean' => $boolean, - 'not' => false + 'not' => false, ]); } @@ -129,11 +133,11 @@ public function whereIn(string $column, array $values, string $boolean = 'and'): public function whereNotIn(string $column, array $values, string $boolean = 'and'): self { return $this->addCondition([ - 'type' => 'in', - 'column' => $column, - 'values' => $values, + 'type' => 'in', + 'column' => $column, + 'values' => $values, 'boolean' => $boolean, - 'not' => true + 'not' => true, ]); } @@ -143,10 +147,10 @@ public function whereNotIn(string $column, array $values, string $boolean = 'and public function whereNull(string $column, string $boolean = 'and'): self { return $this->addCondition([ - 'type' => 'null', - 'column' => $column, + 'type' => 'null', + 'column' => $column, 'boolean' => $boolean, - 'not' => false + 'not' => false, ]); } @@ -156,10 +160,10 @@ public function whereNull(string $column, string $boolean = 'and'): self public function whereNotNull(string $column, string $boolean = 'and'): self { return $this->addCondition([ - 'type' => 'null', - 'column' => $column, + 'type' => 'null', + 'column' => $column, 'boolean' => $boolean, - 'not' => true + 'not' => true, ]); } @@ -173,9 +177,9 @@ protected function whereNested(Closure $callback, string $boolean = 'and'): self if (count($join->conditions)) { $this->addCondition([ - 'type' => 'nested', - 'join' => $join, - 'boolean' => $boolean + 'type' => 'nested', + 'join' => $join, + 'boolean' => $boolean, ]); } diff --git a/src/Collectors/DatabaseCollector.php b/src/Collectors/DatabaseCollector.php index b474d8b..e2a098c 100644 --- a/src/Collectors/DatabaseCollector.php +++ b/src/Collectors/DatabaseCollector.php @@ -14,6 +14,7 @@ use BlitzPHP\Contracts\Event\EventInterface; use BlitzPHP\Database\Connection\BaseConnection; use BlitzPHP\Database\ConnectionResolver; +use BlitzPHP\Database\Result\BaseResult; use BlitzPHP\Debug\Toolbar\Collectors\BaseCollector; use BlitzPHP\Utilities\Date; use BlitzPHP\Utilities\String\Text; @@ -76,7 +77,7 @@ public function __construct() public static function collect(EventInterface $event) { /** - * @var \BlitzPHP\Database\Result\BaseResult + * @var BaseResult */ $result = $event->getTarget(); diff --git a/src/Commands/DatabaseCommand.php b/src/Commands/DatabaseCommand.php index 7eec34d..18ff33a 100644 --- a/src/Commands/DatabaseCommand.php +++ b/src/Commands/DatabaseCommand.php @@ -39,7 +39,7 @@ protected function db(array|string|null $group = null, bool $shared = true): Bas return $this->resolver->connect($group, $shared); } - /** + /** * Recupere les informations a utiliser pour la connexion a la base de données * * @return array [group, configuration] @@ -54,21 +54,21 @@ public function connectionInfo(array|string|null $group = null): array */ public function runner(string $namespace, ?string $group = null): Runner { - $namespaces = match($namespace) { + $namespaces = match ($namespace) { 'ALL' => array_keys($this->container->get(Autoloader::class)->getNamespace()), default => [$namespace], }; $locator = $this->container->get(LocatorInterface::class); - $files = []; + $files = []; foreach ($namespaces as $namespace) { $files[$namespace] = $locator->listNamespaceFiles($namespace, '/Database/Migrations/'); } - + return new Runner( - $this->container->get(DatabaseManager::class), - $group, + $this->container->get(DatabaseManager::class), + $group, $files, config('migrations'), ); diff --git a/src/Commands/Dump/Backup.php b/src/Commands/Dump/Backup.php index f5e3b84..8094144 100644 --- a/src/Commands/Dump/Backup.php +++ b/src/Commands/Dump/Backup.php @@ -97,7 +97,7 @@ public function handle() $ext = match ($option->compress) { Option::COMPRESSION_GZIP => 'gz', Option::COMPRESSION_BZIP2 => 'bz2', - default => 'sql' + default => 'sql', }; $path = $config['path'] ?? storage_path('app/backups'); diff --git a/src/Commands/Generators/Migration.php b/src/Commands/Generators/Migration.php index f44d269..d66855d 100644 --- a/src/Commands/Generators/Migration.php +++ b/src/Commands/Generators/Migration.php @@ -17,7 +17,7 @@ /** * Génère un squelette de fichier de migration. - * + * * Analyse le nom de la migration pour déterminer automatiquement * l'action (create/modify) et la table concernée. */ @@ -94,11 +94,11 @@ public function handle() try { $this->generateClass($this->parameters()); - + return EXIT_SUCCESS; } catch (InvalidArgumentException $e) { $this->error($e->getMessage()); - + return EXIT_ERROR; } } @@ -110,9 +110,9 @@ protected function prepare(string $class): string { $name = $this->argument('name'); $anonymous = $this->option('anonymous') === true; - + $parsed = $this->parseMigrationName($name); - + $create = $this->option('create'); $alter = $this->option('alter'); $table = $this->option('table'); @@ -120,7 +120,7 @@ protected function prepare(string $class): string if ($create && $alter) { throw new InvalidArgumentException( - 'Impossible d\'utiliser "create" et "alter" simultanément.' + 'Impossible d\'utiliser "create" et "alter" simultanément.', ); } @@ -135,27 +135,27 @@ protected function prepare(string $class): string $detectedTable = $parsed['table']; } - // Si c'est une session, on force l'action à 'create' et la table par défaut + // Si c'est une session, on force l'action à 'create' et la table par défaut if ($session) { - $action = 'create'; - $table = $table ?: 'blitz_sessions'; + $action = 'create'; + $table = $table ?: 'blitz_sessions'; $detectedTable = $table; } - if (!$action) { + if (! $action) { throw new InvalidArgumentException( "Impossible de déterminer l'action à partir du nom '{$name}'.\n" . - "Utilisez --create=table ou --table=table pour spécifier explicitement." + 'Utilisez --create=table ou --table=table pour spécifier explicitement.', ); } $table = $this->cleanTableName($table ?: $detectedTable); // Valider qu'on a une table (sauf pour certaines actions) - if (!$table && !in_array($action, ['drop', 'delete', 'remove'])) { + if (! $table && ! in_array($action, ['drop', 'delete', 'remove'], true)) { throw new InvalidArgumentException( "Impossible de déterminer la table à partir du nom '{$name}'. \n" . - "Utilisez --table=tableName pour spécifier le nom de la table explicitement." + 'Utilisez --table=tableName pour spécifier le nom de la table explicitement.', ); } @@ -182,7 +182,7 @@ protected function prepare(string $class): string protected function parseMigrationName(string $name): array { $name = $this->normalizeName($name); - + $result = [ 'action' => null, 'table' => null, @@ -191,20 +191,20 @@ protected function parseMigrationName(string $name): array // Pattern 1: action_table (ex: create_users_table, add_email_to_users) if (preg_match('/^(' . $this->getKeywordsPattern() . ')_(.+?)(?:_table)?$/', $name, $matches)) { $result['action'] = $this->mapKeywordToAction($matches[1]); - $result['table'] = $this->extractTableName($matches[2]); + $result['table'] = $this->extractTableName($matches[2]); } - + // Pattern 2: ActionTable (ex: CreateUsersTable, AddEmailToUsers) elseif (preg_match('/^(' . $this->getKeywordsPattern(true) . ')([A-Z][a-zA-Z0-9]+)$/', $name, $matches)) { $result['action'] = $this->mapKeywordToAction(strtolower($matches[1])); - $result['table'] = $this->decamelize($matches[2]); + $result['table'] = $this->decamelize($matches[2]); } - + // Pattern 3: table_action (ex: users_create, users_add_email) elseif (preg_match('/^([a-z][a-z0-9_]+)_(' . $this->getKeywordsPattern() . ')(?:_(.+))?$/', $name, $matches)) { - $result['table'] = $matches[1]; + $result['table'] = $matches[1]; $result['action'] = $this->mapKeywordToAction($matches[2]); - + // Si c'est une modification de colonne, on garde le nom original if (isset($matches[3])) { $result['table'] .= ' (colonne: ' . $matches[3] . ')'; @@ -226,15 +226,15 @@ protected function cleanTableName(?string $table): ?string // Enlever les préfixes/suffixes "table" redondants $table = preg_replace('/^(table_|tbl_)/', '', $table); $table = preg_replace('/(_table|_tbl)$/', '', $table); - + // Enlever les caractères indésirables $table = preg_replace('/[^a-z0-9_]/', '', $table); - + // Éviter les underscores multiples $table = preg_replace('/_+/', '_', $table); - + $table = trim($table, '_'); - + return $table ?: null; } @@ -246,10 +246,10 @@ protected function normalizeName(string $name): string // Convertir CamelCase en snake_case $name = preg_replace('/(? 2 && in_array($parts[0], ['add', 'remove', 'drop'])) { + if (count($parts) > 2 && in_array($parts[0], ['add', 'remove', 'drop'], true)) { return end($parts); } - + return $input; } @@ -288,16 +288,16 @@ protected function getKeywordsPattern(bool $forCamelCase = false): string $allKeywords = array_merge( $this->createKeywords, $this->modifyKeywords, - $this->dropKeywords + $this->dropKeywords, ); - + $allKeywords = array_unique($allKeywords); - + if ($forCamelCase) { // Pour CamelCase, on garde les mots tels quels $allKeywords = array_map('ucfirst', $allKeywords); } - + return implode('|', array_map('preg_quote', $allKeywords)); } @@ -307,19 +307,19 @@ protected function getKeywordsPattern(bool $forCamelCase = false): string protected function mapKeywordToAction(string $keyword): string { $keyword = strtolower($keyword); - - if (in_array($keyword, $this->createKeywords)) { + + if (in_array($keyword, $this->createKeywords, true)) { return 'create'; } - - if (in_array($keyword, $this->dropKeywords)) { + + if (in_array($keyword, $this->dropKeywords, true)) { return 'drop'; } - - if (in_array($keyword, $this->modifyKeywords)) { + + if (in_array($keyword, $this->modifyKeywords, true)) { return 'alter'; } - + // Par défaut, on considère comme une modification return 'alter'; } @@ -330,7 +330,7 @@ protected function mapKeywordToAction(string $keyword): string protected function basename(string $filename): string { $timestamp = gmdate(config('migrations.timestampFormat', 'YmdHis_')); - + return $timestamp . $this->decamelize(basename($filename)); } } diff --git a/src/Commands/Generators/Views/migration.tpl.php b/src/Commands/Generators/Views/migration.tpl.php index 032ff30..01c5bc8 100644 --- a/src/Commands/Generators/Views/migration.tpl.php +++ b/src/Commands/Generators/Views/migration.tpl.php @@ -1,6 +1,6 @@ <@php - + namespace {namespace}; @@ -25,22 +25,22 @@ public function up(): void $table->timestamp('timestamp'); $table->binary('data'); $table->index('timestamp'); - + $table->primary(['id', 'ip_address']); $table->primary('id'); }); - + $this->create('', function(Builder $table) { $table->id(); $table->timestamps(); }); - + $this->dropIfExists(''); - + $this->alter('', function(Builder $table) { - // + // }); // @@ -52,16 +52,16 @@ public function up(): void */ public function down(): void { - + $this->dropIfExists(''); - + $this->create('', function(Builder $table) { $table->id(); $table->timestamps(); }); - + $this->alter('', function(Builder $table) { - // + // }); // diff --git a/src/Commands/Migration/Migrate.php b/src/Commands/Migration/Migrate.php index d45c28c..ffdb24f 100644 --- a/src/Commands/Migration/Migrate.php +++ b/src/Commands/Migration/Migrate.php @@ -49,14 +49,14 @@ class Migrate extends DatabaseCommand */ public function handle() { - if (on_prod() && !$this->option('force')) { - if (!$this->confirm('Êtes-vous sûr de vouloir exécuter des migrations en production ?')) { + if (on_prod() && ! $this->option('force')) { + if (! $this->confirm('Êtes-vous sûr de vouloir exécuter des migrations en production ?')) { return EXIT_SUCCESS; } } $this->eol()->info('Recherche des migrations en attente...'); - + $group = $this->option('group', 'default'); $namespace = $this->option('namespace', APP_NAMESPACE); $all = $this->option('all') === true; @@ -67,56 +67,57 @@ public function handle() if ($pretend) { $this->pretendMode($runner); + return EXIT_SUCCESS; } $errorCount = 0; $migrationsCount = 0; - $runner->on('process.empty-migrations', function() { + $runner->on('process.empty-migrations', function () { $this->warning('Aucune migration en attente.'); }) - ->on('process.migrations-disabled', function() { - $this->badge()->info('Les migrations sont désactivées dans la configuration.'); - }) - ->on('migration.error', function($payload) use(&$errorCount) { - ['migration' => $migration, 'exception' => $e] = $payload; - - $this->justify( - $this->getMigrationName($migration), - $this->color->error('Échec') - ); - - if (!$this->option('continue-on-error')) { - throw $e; - } - - $errorCount++; - }) - ->on('migration.ignored', function($payload) { - ['migration' => $migration] = $payload; - - $this->justify( - $this->getMigrationName($migration), - $this->color->warn('Ignoré') - ); - }) - ->on('migration.done', function($payload) use(&$migrationsCount) { - ['migration' => $migration, 'duration' => $duration] = $payload; - - $this->justify( - $this->getMigrationName($migration), - $this->color->comment($duration . ' ms') . ' ' . $this->color->ok('Exécuté') - ); - - $migrationsCount++; - }); + ->on('process.migrations-disabled', function () { + $this->badge()->info('Les migrations sont désactivées dans la configuration.'); + }) + ->on('migration.error', function ($payload) use (&$errorCount) { + ['migration' => $migration, 'exception' => $e] = $payload; + + $this->justify( + $this->getMigrationName($migration), + $this->color->error('Échec'), + ); + + if (! $this->option('continue-on-error')) { + throw $e; + } + + $errorCount++; + }) + ->on('migration.ignored', function ($payload) { + ['migration' => $migration] = $payload; + + $this->justify( + $this->getMigrationName($migration), + $this->color->warn('Ignoré'), + ); + }) + ->on('migration.done', function ($payload) use (&$migrationsCount) { + ['migration' => $migration, 'duration' => $duration] = $payload; + + $this->justify( + $this->getMigrationName($migration), + $this->color->comment($duration . ' ms') . ' ' . $this->color->ok('Exécuté'), + ); + + $migrationsCount++; + }); $executed = $runner->latest($group); if ($executed > 0) { $this->newLine()->success("{$executed} migration(s) exécutée(s) avec succès."); - + if ($this->option('show-stats')) { $this->displayStats($runner, $executed, $errorCount); } @@ -151,7 +152,7 @@ private function getMigrationName(object $migration): string '[%s] %s_%s', $migration->namespace, $migration->version, - $migration->migration + $migration->migration, ); } @@ -162,25 +163,25 @@ private function displayStats(Runner $runner, int $executed, int $errorCount): v { $batches = $runner->getLastBatch(); $history = $runner->getHistory(); - + $options = ['sep' => '-', 'second' => ['fg' => Color::GREEN]]; - $data = [ - 'Total dans l\'historique' => $total = count($history), - 'Migrations exécutées' => $executed, - 'Migrations échouées' => $errorCount, - 'Migrations ignorées' => $total - $executed - $errorCount, - 'Dernier lot' => $batches, - 'Groupe de connexion' => $this->option('group', 'default'), - 'Namespace' => $this->option('all') ? 'Tous' : $this->option('namespace', APP_NAMESPACE), + $data = [ + 'Total dans l\'historique' => $total = count($history), + 'Migrations exécutées' => $executed, + 'Migrations échouées' => $errorCount, + 'Migrations ignorées' => $total - $executed - $errorCount, + 'Dernier lot' => $batches, + 'Groupe de connexion' => $this->option('group', 'default'), + 'Namespace' => $this->option('all') ? 'Tous' : $this->option('namespace', APP_NAMESPACE), // 'Durée totale d\'éxécution' => $duration . ' ms', ]; - + $this->eol()->border(char: '*'); - + foreach ($data as $k => $v) { $this->justify($k, (string) $v, $options); - } - + } + $this->border(char: '*'); } } diff --git a/src/Commands/Migration/Refresh.php b/src/Commands/Migration/Refresh.php index 2fa3bd2..d105f91 100644 --- a/src/Commands/Migration/Refresh.php +++ b/src/Commands/Migration/Refresh.php @@ -44,19 +44,19 @@ class Refresh extends DatabaseCommand */ public function handle() { - if (on_prod() && !$this->option('force')) { + if (on_prod() && ! $this->option('force')) { if (! $this->confirm('Êtes-vous sûr de vouloir réinitialiser toutes les migrations en production ?')) { return EXIT_SUCCESS; } } $this->eol()->info('Réinitialisation et réexécution des migrations...'); - + $group = $this->option('group', 'default'); $seed = $this->option('seed') === true; $this->newLine()->comment('Étape 1/2: Annulation de toutes les migrations'); - + $rollbackResult = $this->call('migrate:rollback', options: [ '--group' => $group, '--all' => true, @@ -70,14 +70,14 @@ public function handle() } $this->newLine()->comment('Étape 2/2: Réexécution des migrations'); - + $migrateResult = $this->call('migrate', options: [ '--group' => $group, ]); if ($migrateResult !== EXIT_SUCCESS) { $this->error('Échec de l\'exécution des migrations.'); - + return $migrateResult; } @@ -90,7 +90,6 @@ public function handle() $this->newLine()->success('Refresh terminé avec succès !'); - return EXIT_SUCCESS; } } diff --git a/src/Commands/Migration/Reset.php b/src/Commands/Migration/Reset.php index 3b20008..2ac14dc 100644 --- a/src/Commands/Migration/Reset.php +++ b/src/Commands/Migration/Reset.php @@ -41,8 +41,8 @@ class Reset extends DatabaseCommand */ public function handle() { - if (on_prod() && !$this->option('force')) { - if (!$this->confirm('Êtes-vous sûr de vouloir réinitialiser TOUTES les migrations en production ?')) { + if (on_prod() && ! $this->option('force')) { + if (! $this->confirm('Êtes-vous sûr de vouloir réinitialiser TOUTES les migrations en production ?')) { return EXIT_SUCCESS; } } @@ -50,13 +50,11 @@ public function handle() $this->eol()->info('Réinitialisation de toutes les migrations...'); $group = $this->option('group', 'default'); - - $result = $this->call('migrate:rollback', [ + + return $this->call('migrate:rollback', [ '--group' => $group, '--all' => true, '--force' => $this->option('force'), ]); - - return $result; } } diff --git a/src/Commands/Migration/Rollback.php b/src/Commands/Migration/Rollback.php index 3689e6c..45d7cd5 100644 --- a/src/Commands/Migration/Rollback.php +++ b/src/Commands/Migration/Rollback.php @@ -47,8 +47,8 @@ class Rollback extends DatabaseCommand */ public function handle() { - if (on_prod() && !$this->option('force')) { - if (!$this->confirm('Êtes-vous sûr de vouloir annuler des migrations en production ?')) { + if (on_prod() && ! $this->option('force')) { + if (! $this->confirm('Êtes-vous sûr de vouloir annuler des migrations en production ?')) { return EXIT_SUCCESS; } } @@ -58,58 +58,59 @@ public function handle() $group = $this->option('group', 'default'); $batch = $this->option('all') ? 0 : $this->option('batch', 1); - if (is_string($batch) && !preg_match('/^-?\d+$/', $batch)) { + if (is_string($batch) && ! preg_match('/^-?\d+$/', $batch)) { $this->error('Le numéro de lot doit être un entier.'); + return EXIT_ERROR; } $batch = (int) $batch; $runner = $this->runner('ALL', $group); - + $rolledBack = 0; $errorCount = 0; - $runner->on('process.empty-migrations', function() { + $runner->on('process.empty-migrations', function () { $this->warning('Aucune migration à annuler'); }) - ->on('migration.error', function($payload) use(&$errorCount) { - ['migration' => $migration, 'exception' => $e] = $payload; - - $this->justify( - $this->getMigrationName($migration), - $this->color->error('Échec') - ); - - if (!$this->option('continue-on-error')) { - throw $e; - } - - $errorCount++; - }) - ->on('migration.skipped', function($payload) { - ['migration' => $migration] = $payload; - - $this->justify( - $this->getMigrationName($migration), - $this->color->warn('Fichier introuvable') - ); - }) - ->on('migration.done', function($payload) use(&$rolledBack) { - ['migration' => $migration, 'duration' => $duration] = $payload; - - $this->justify( - $this->getMigrationName($migration), - $this->color->comment($duration . ' ms') . ' ' . $this->color->ok('Annulé') - ); - - $rolledBack++; - }); + ->on('migration.error', function ($payload) use (&$errorCount) { + ['migration' => $migration, 'exception' => $e] = $payload; + + $this->justify( + $this->getMigrationName($migration), + $this->color->error('Échec'), + ); + + if (! $this->option('continue-on-error')) { + throw $e; + } + + $errorCount++; + }) + ->on('migration.skipped', function ($payload) { + ['migration' => $migration] = $payload; + + $this->justify( + $this->getMigrationName($migration), + $this->color->warn('Fichier introuvable'), + ); + }) + ->on('migration.done', function ($payload) use (&$rolledBack) { + ['migration' => $migration, 'duration' => $duration] = $payload; + + $this->justify( + $this->getMigrationName($migration), + $this->color->comment($duration . ' ms') . ' ' . $this->color->ok('Annulé'), + ); + + $rolledBack++; + }); $runner->rollback($batch, $group); if ($rolledBack > 0) { $this->newLine()->success("{$rolledBack} migration(s) annulée(s) avec succès."); - + if ($this->option('show-stats')) { $this->displayStats($runner, $rolledBack, $errorCount, $batch); } @@ -128,7 +129,7 @@ private function getMigrationName(object $migration): string $migration->namespace ?? $migration->history->namespace, $migration->version ?? $migration->history->version, $migration->migration ?? $migration->history->migration, - $migration->history->batch ?? '?' + $migration->history->batch ?? '?', ); } @@ -138,19 +139,19 @@ private function getMigrationName(object $migration): string private function displayStats(Runner $runner, int $rolledBack, int $errorCount, int $targetBatch): void { $options = ['sep' => '-', 'second' => ['fg' => Color::GREEN]]; - $data = [ + $data = [ 'Migrations annulées' => $rolledBack, 'Migrations échouées' => $errorCount, 'Lot cible' => $targetBatch, 'Groupe de connexion' => $this->option('group', 'default'), ]; - + $this->eol()->border(char: '*'); - + foreach ($data as $k => $v) { $this->justify($k, (string) $v, $options); - } - + } + $this->border(char: '*'); } } diff --git a/src/Commands/Migration/Status.php b/src/Commands/Migration/Status.php index 4cb739d..dd7fd9f 100644 --- a/src/Commands/Migration/Status.php +++ b/src/Commands/Migration/Status.php @@ -43,35 +43,37 @@ public function handle() { $this->eol()->info('Récupération du statut des migrations...'); - $group = $this->option('group', 'default'); + $group = $this->option('group', 'default'); $runner = $this->runner('ALL', $group); - + $history = $runner->getHistory($group); - $files = $runner->findMigrationFiles(); - + $files = $runner->findMigrationFiles(); + if (empty($files)) { $this->warning('Aucun fichier de migration trouvé.'); + return EXIT_SUCCESS; } - + $executedMap = []; + foreach ($history as $item) { - $key = $item->migration . '_' . $item->version; + $key = $item->migration . '_' . $item->version; $executedMap[$key] = $item; } $tbody = []; foreach ($files as $file) { - $key = $file->migration . '_' . $file->version; + $key = $file->migration . '_' . $file->version; $executed = isset($executedMap[$key]); - - $date = $executed ? date('Y-m-d H:i', $executedMap[$key]->time) : '---'; - $batch = $executed ? $executedMap[$key]->batch : '---'; - $status = $executed + + $date = $executed ? date('Y-m-d H:i', $executedMap[$key]->time) : '---'; + $batch = $executed ? $executedMap[$key]->batch : '---'; + $status = $executed ? $this->color->ok('EXÉCUTÉE') : $this->color->warn('EN ATTENTE'); - + $tbody[] = [ $this->getMigrationName($file), $date, @@ -83,9 +85,9 @@ public function handle() $this->table(['MIGRATION', 'EXÉCUTÉE', 'LOT', 'STATUT'], $tbody); // Statistiques - $total = count($files); + $total = count($files); $executedCount = count($history); - $pendingCount = $total - $executedCount; + $pendingCount = $total - $executedCount; $this->newLine()->info('RÉSUMÉ'); $this->justify('Total migrations', (string) $total); @@ -105,7 +107,7 @@ private function getMigrationName(object $migration): string '[%s] %s_%s', $migration->namespace, $migration->version, - $migration->migration + $migration->migration, ); } } diff --git a/src/Commands/Seed.php b/src/Commands/Seed.php index 875d852..c8c8af3 100644 --- a/src/Commands/Seed.php +++ b/src/Commands/Seed.php @@ -53,7 +53,7 @@ class Seed extends DatabaseCommand */ public function handle() { - $group = $this->option('group'); + $group = $this->option('group'); $silent = $this->option('silent') !== null; $locale = $this->option('locale', config('app.language', 'fr_FR')); @@ -82,12 +82,13 @@ protected function getSeederName(): string return $this->prompt( 'Quel seeder souhaitez-vous exécuter ?', 'DatabaseSeeder', - function ($val) { + static function ($val) { if (empty($val)) { throw new InvalidArgumentException('Veuillez entrer le nom du seeder.'); } + return $val; - } + }, ); } @@ -107,7 +108,7 @@ protected function resolveSeeder(string $name): Seeder $className = $name; // Si le nom ne contient pas de namespace, on essaie les chemins standards - if (!str_contains($name, '\\')) { + if (! str_contains($name, '\\')) { foreach ($paths as $path) { $fullClass = $path . $name; if (class_exists($fullClass)) { @@ -117,11 +118,11 @@ protected function resolveSeeder(string $name): Seeder } } - if (!class_exists($className)) { + if (! class_exists($className)) { throw new InvalidArgumentException( "Le seeder '{$name}' n'a pas été trouvé.\n" . "Chemins recherchés :\n" . - implode("\n", array_map(fn($p) => "- {$p}{$name}", $paths)) + implode("\n", array_map(static fn ($p) => "- {$p}{$name}", $paths)), ); } diff --git a/src/Commands/TableInfo.php b/src/Commands/TableInfo.php index a4882af..8e11136 100644 --- a/src/Commands/TableInfo.php +++ b/src/Commands/TableInfo.php @@ -11,6 +11,7 @@ namespace BlitzPHP\Database\Commands; +use BlitzPHP\Database\Result\BaseResult; use InvalidArgumentException; use PDO; @@ -189,7 +190,7 @@ private function makeTbodyForShowAllTables(array $tables): array foreach ($tables as $id => $tableName) { $table = $this->db->protectIdentifiers($tableName); - /** @var \BlitzPHP\Database\Result\BaseResult $db */ + /** @var BaseResult $db */ $db = $this->db->query("SELECT * FROM {$table}"); $this->tbody[] = [ @@ -218,7 +219,7 @@ private function makeTableRows( string $tableName, int $limitRows, int $limitFieldValue, - ?string $sortField = null + ?string $sortField = null, ): array { $this->tbody = []; @@ -236,7 +237,7 @@ private function makeTableRows( static fn ($item): string => mb_strlen((string) $item) > $limitFieldValue ? mb_substr((string) $item, 0, $limitFieldValue) . '...' : (string) $item, - $row + $row, ); $this->tbody[] = $row; } diff --git a/src/Config/Services.php b/src/Config/Services.php index 64c41e0..36da4f9 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -45,14 +45,14 @@ public static function dbManager(): DatabaseManager /** * Récupère une connexion à la base de données - * + * * @return BaseConnection */ public static function database(?string $group = null, bool $shared = true): ConnectionInterface { $connection = static::dbManager()->connect($group, $shared); - if (!$connection instanceof BaseConnection) { + if (! $connection instanceof BaseConnection) { throw new InvalidArgumentException('La connexion retournée n\'est pas une instance de BaseConnection'); } @@ -61,9 +61,9 @@ public static function database(?string $group = null, bool $shared = true): Con /** * Récupère un query builder - * + * * @return BaseBuilder - * + * * @deprecated 1.0 use static::database()->table($tablename) instead */ public static function builder(?string $group = null, bool $shared = true): BuilderInterface diff --git a/src/Config/database.php b/src/Config/database.php index b4e764b..7f45dc9 100644 --- a/src/Config/database.php +++ b/src/Config/database.php @@ -45,7 +45,7 @@ * * Si défini sur 'auto', alors vaudra true en developpement et false en production */ - 'debug' => !on_prod(), + 'debug' => ! on_prod(), /** @var string */ 'charset' => 'utf8mb4', /** @var string */ diff --git a/src/Config/helpers.php b/src/Config/helpers.php index 17cc67e..0527c6b 100644 --- a/src/Config/helpers.php +++ b/src/Config/helpers.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + use BlitzPHP\Contracts\Database\ConnectionInterface; use BlitzPHP\Database\Config\Services; use BlitzPHP\Database\Connection\BaseConnection; @@ -13,9 +22,6 @@ * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ - - - if (! function_exists('model')) { /** * Simple maniere d'obtenir un modele. @@ -32,7 +38,6 @@ function model(array|string $name, ?ConnectionInterface &$conn = null) } } - if (! function_exists('db')) { /** * Grabs a database connection and returns it to the user. diff --git a/src/Connection/BaseConnection.php b/src/Connection/BaseConnection.php index 923d932..3903558 100644 --- a/src/Connection/BaseConnection.php +++ b/src/Connection/BaseConnection.php @@ -33,13 +33,16 @@ /** * Connexion de base à la base de données - * - * @method bool tableExists(string $name) Vérifie si une table existe - * @method array getColumnNames(string $table) Retourne les noms des champs d'une table - * @method bool columnExists(string $column, string $table) Vérifie si un champ existe dans une table - * @method array getColumnData(string $table) Retourne les métadonnées des champs d'une table - * @method array getIndexData(string $table) Retourne les métadonnées des index d'une table - * @method array getForeignKeyData(string $table) Retourne les métadonnées des clés étrangères d'une table + * + * @method array listTables(bool $constrainByPrefix = false) Retourne la liste des tables de la base de données + * @method bool tableExists(string $tableName, bool $cached = true) Vérifie si une table existe + * @method array getColumnNames(string $table) Retourne les noms des champs d'une table + * @method bool columnExists(string $column, string $table) Vérifie si un champ existe dans une table + * @method array getColumnData(string $table) Retourne les informations détaillées des champs d'une table + * @method array getIndexData(string $table) Retourne les informations des index d'une table + * @method array getForeignKeyData(string $table) Retourne les informations des clés étrangères d'une table + * @method self clearCache() Vide le cache des métadonnées + * @method self resetDataCache() Alias de clearCache() - Vide le cache des métadonnées */ abstract class BaseConnection implements ConnectionInterface { @@ -55,11 +58,11 @@ abstract class BaseConnection implements ConnectionInterface /** * Configuration de la connexion - * + * * @var array{ * dsn?: string, - * hostname: string, port: int, username?: string, password?: string, - * database?: string, charset?: string, collation?: string, strict_on?: boolean, + * hostname: string, port: int, username?: string, password?: string, + * database?: string, charset?: string, collation?: string, strict_on?: bool, * debug?: bool * } */ @@ -78,7 +81,7 @@ abstract class BaseConnection implements ConnectionInterface /** * Tous les callbacks qui doivent être invoqués avant l'exécution d'une requête. * - * @var (Closure(string, array, static): mixed)[] + * @var list */ protected array $beforeExecutingCallbacks = []; @@ -87,15 +90,20 @@ abstract class BaseConnection implements ConnectionInterface */ protected ?MetadataCollector $metadata = null; + /** + * Mapping des méthodes proxy vers MetadataCollector + * + * @var array + */ protected array $proxyMethods = [ - 'listTables', - 'tableExists', - 'getColumnNames', - 'columnExists', - 'getColumnData', - 'getIndexData', - 'getForeignKeyData', - 'resetDataCache' => 'clearCache', + 'listTables' => 'listTables', + 'tableExists' => 'tableExists', + 'getColumnNames' => 'getColumnNames', + 'columnExists' => 'columnExists', + 'getColumnData' => 'getColumnData', + 'getIndexData' => 'getIndexData', + 'getForeignKeyData' => 'getForeignKeyData', + 'resetDataCache' => 'clearCache', ]; /** @@ -107,14 +115,14 @@ abstract class BaseConnection implements ConnectionInterface * Niveau de profondeur des transactions */ protected int $transDepth = 0; - + /** * Drapeau du statut des transaction * * Utilise avec les transactions pour determiner si un rollback est en cours. */ protected bool $transStatus = true; - + /** * Points de sauvegarde des transactions (pour les transaction imbriquees) */ @@ -142,9 +150,9 @@ abstract class BaseConnection implements ConnectionInterface /** * Constructeur - * - * @param ?LoggerInterface $logger Journaliseur - * @param ?EventManagerInterface $event Gestionnaire d'evenement + * + * @param ?LoggerInterface $logger Journaliseur + * @param ?EventManagerInterface $event Gestionnaire d'evenement */ public function __construct(array $config, protected ?LoggerInterface $logger = null, protected ?EventManagerInterface $event = null) { @@ -167,13 +175,13 @@ public function initialize(): void $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); - + $this->afterConnect(); } catch (PDOException $e) { throw new DatabaseException( - "Impossible de se connecter à la base de données : " . $e->getMessage(), + 'Impossible de se connecter à la base de données : ' . $e->getMessage(), 0, - $e + $e, ); } } @@ -185,13 +193,13 @@ abstract protected function afterConnect(): void; /** * {@inheritDoc} - * + * * @return PDO */ public function connect(bool $persistent = false): mixed { $options = $this->getPdoOptions(); - + if ($persistent) { $options[PDO::ATTR_PERSISTENT] = true; } @@ -200,7 +208,7 @@ public function connect(bool $persistent = false): mixed $this->getDsn(), $this->config['username'] ?? null, $this->config['password'] ?? null, - $options + $options, ); } @@ -219,7 +227,7 @@ abstract protected function getDsn(): string; /** * Retourne les options PDO par défaut - * + * * @return array */ protected function getPdoOptions(): array @@ -257,7 +265,7 @@ public function close(): void { $this->pdo = null; } - + /** * Obtient le nom de la connexion à la base de données. */ @@ -332,10 +340,10 @@ public function prepareBindings(array $bindings): array return $bindings; } - + /** * Exécute une instruction SQL et journalise son contexte d'exécution. - * + * * @param Closure(string, array): mixed $callback */ protected function run(string $query, array $bindings, Closure $callback) @@ -351,34 +359,34 @@ protected function run(string $query, array $bindings, Closure $callback) try { $result = $callback($query, $bindings); $this->logQuery($query, $bindings, microtime(true) - $start); - + return $result; } catch (PDOException $e) { $this->logQuery($query, $bindings, microtime(true) - $start, $e); - + if ($this->transDepth > 0) { $this->transStatus = false; } - + throw new QueryException( $this->getName(), $query, $this->prepareBindings($bindings), $e, - $this->getConnectionDetails() + $this->getConnectionDetails(), ); } } - + /** * {@inheritDoc} */ public function query(string $sql, array $bindings = []): ResultInterface { - return $this->run($sql, $bindings, function($query, $bindings) use ($sql) { + return $this->run($sql, $bindings, function ($query, $bindings) { $statement = $this->pdo->prepare($query); - $success = $statement->execute($this->prepareBindings($bindings)); - + $success = $statement->execute($this->prepareBindings($bindings)); + return $this->result = new Result($this, $statement, $success); }); } @@ -390,6 +398,7 @@ public function statement(string $query, array $bindings = []): bool { return $this->run($query, $bindings, function ($query, $bindings) { $statement = $this->pdo->prepare($query); + return $statement->execute($this->prepareBindings($bindings)); }); } @@ -416,7 +425,7 @@ protected function logQuery(string $sql, array $bindings, float $time, ?Throwabl 'sql' => $sql, 'bindings' => $bindings, 'time' => $time, - 'error' => $e?->getMessage() + 'error' => $e?->getMessage(), ]); } @@ -426,7 +435,7 @@ protected function logQuery(string $sql, array $bindings, float $time, ?Throwabl public function simpleQuery(string $query) { $this->initialize(); - + try { return $this->pdo->query($query); } catch (PDOException $e) { @@ -435,7 +444,7 @@ public function simpleQuery(string $query) $query, [], $e, - $this->getConnectionDetails() + $this->getConnectionDetails(), ); } } @@ -464,21 +473,21 @@ public function beginTransaction(bool $testMode = false): bool } $this->initialize(); - + if ($this->transDepth === 0) { - $this->transStatus = !$testMode; - $this->transDepth = 1; - + $this->transStatus = ! $testMode; + $this->transDepth = 1; + return $this->pdo->beginTransaction(); } - + // Création d'un savepoint pour les transactions imbriquées $savepoint = 'sp_' . $this->transDepth; $this->pdo->exec("SAVEPOINT {$savepoint}"); $this->savepoints[$this->transDepth] = $savepoint; - + $this->transDepth++; - + return true; } @@ -490,23 +499,24 @@ public function commit(): bool if (! $this->transEnabled || $this->transDepth === 0) { return false; } - + $this->initialize(); - + if ($this->transDepth === 1) { $this->transDepth = 0; + return $this->pdo->commit(); } - + // Libération du savepoint $savepoint = $this->savepoints[$this->transDepth] ?? null; if ($savepoint) { $this->pdo->exec("RELEASE SAVEPOINT {$savepoint}"); unset($this->savepoints[$this->transDepth]); } - + $this->transDepth--; - + return true; } @@ -518,24 +528,25 @@ public function rollback(): bool if (! $this->transEnabled || $this->transDepth === 0) { return false; } - + $this->initialize(); - + if ($this->transDepth === 1) { - $this->transDepth = 0; + $this->transDepth = 0; $this->transStatus = true; + return $this->pdo->rollBack(); } - + // Retour au savepoint $savepoint = $this->savepoints[$this->transDepth] ?? null; if ($savepoint) { $this->pdo->exec("ROLLBACK TO SAVEPOINT {$savepoint}"); unset($this->savepoints[$this->transDepth]); } - + $this->transDepth--; - + return true; } @@ -546,9 +557,10 @@ public function transComplete(): bool { if ($this->transStatus === false) { $this->rollback(); + return false; } - + return $this->commit(); } @@ -572,15 +584,15 @@ public function transaction(Closure $callback, int $attempts = 1): mixed { for ($i = 1; $i <= $attempts; $i++) { $this->beginTransaction(); - + try { $result = $callback($this); $this->commit(); - + return $result; } catch (Throwable $e) { $this->rollback(); - + if ($i === $attempts) { throw $e; } @@ -596,14 +608,28 @@ public function transaction(Closure $callback, int $attempts = 1): mixed |-------------------------------------------------------------------------- */ + /** + * Gère les appels aux méthodes proxy vers MetadataCollector + * + * @param string $name Nom de la méthode appelée + * @param array $arguments Arguments de la méthode + * + * @return mixed + * + * @throws BadMethodCallException + */ public function __call(string $name, array $arguments = []): mixed { + // Méthodes proxy simples (même nom) if (in_array($name, $this->proxyMethods, true)) { return call_user_func_array([$this->metadata(), $name], $arguments); } + + // Méthodes proxy avec nom différent (ex: resetDataCache -> clearCache) if (array_key_exists($name, $this->proxyMethods)) { return call_user_func_array([$this->metadata(), $this->proxyMethods[$name]], $arguments); } + throw new BadMethodCallException(sprintf('Methode %s non definie', static::class . '::' . $name)); } @@ -635,9 +661,12 @@ abstract public function _listColumns(string $table): array; */ abstract public function _listForeignKeys(string $table): array; + /** + * Récupère l'instance du collecteur de métadonnées + */ private function metadata(): MetadataCollector { - if (!$this->metadata) { + if (! $this->metadata) { $this->metadata = new MetadataCollector($this); } @@ -685,35 +714,35 @@ public function escape(mixed $str): mixed public function escapeString($str, bool $like = false): array|string { if (is_array($str)) { - return array_map(fn($s) => $this->escapeString($s, $like), $str); + return array_map(fn ($s) => $this->escapeString($s, $like), $str); } if ($str instanceof Stringable) { $str = (string) $str; } - + $str = $this->pdo->quote($str); - + if ($like === true) { $str = str_replace(['%', '_'], ['\\%', '\\_'], $str); } - + return $str; } /** * Entoure une chaîne de guillemets et échappe le contenu d'un paramètre de chaîne. */ - public function quote(string|null|Expression $value): string + public function quote(Expression|string|null $value): string { if ($value === null) { return 'NULL'; } - + if ($value instanceof Expression) { return (string) $value; } - + if (! is_string($value = Utils::castValue($value))) { return $value; } @@ -730,10 +759,10 @@ public function escapeIdentifiers(mixed $item): mixed return array_map([$this, 'escapeIdentifiers'], $item); } - if (!isset($this->escapeCache[$item])) { + if (! isset($this->escapeCache[$item])) { $this->escapeCache[$item] = $this->doEscapeIdentifiers($item); } - + return $this->escapeCache[$item]; } @@ -745,6 +774,7 @@ protected function doEscapeIdentifiers(string $item): string if (str_contains($item, '.')) { $parts = explode('.', $item); + return implode('.', array_map([$this, 'escapeIdentifier'], $parts)); } @@ -834,7 +864,7 @@ public function getTableAlias(string $table): array if ($alias !== $table) { $this->aliasedTables[$table] = $alias; } - + return [$this->aliasedTables[$table] ?? $table, $table]; } @@ -895,18 +925,18 @@ public function addTableAlias(string $table): self */ public function getDriver(): string { - $this->initialize(); - + $this->initialize(); + return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME); } - + /** * Returns a string containing the version of the database being used. */ public function getVersion(): string { $this->initialize(); - + return $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION); } @@ -957,9 +987,9 @@ public function setPrefix(string $prefix = ''): string /** * Retourne une nouvelle instance non partagee du query builder pour cette connexion. - * + * * @param list|string $tableName - * + * * @return BaseBuilder */ public function table(array|string $tableName): BuilderInterface @@ -969,7 +999,7 @@ public function table(array|string $tableName): BuilderInterface /** * Returns a new instance of the BaseBuilder class with a cleared FROM clause. - * + * * @return BaseBuilder */ public function newQuery(): BuilderInterface @@ -977,7 +1007,6 @@ public function newQuery(): BuilderInterface return new BaseBuilder($this); } - /** * {@inheritDoc} */ @@ -992,10 +1021,10 @@ public function getLastQuery() public function error(): array { $errorInfo = $this->pdo?->errorInfo() ?? []; - + return [ - 'code' => $errorInfo[1] ?? 0, - 'message' => $errorInfo[2] ?? '' + 'code' => $errorInfo[1] ?? 0, + 'message' => $errorInfo[2] ?? '', ]; } diff --git a/src/Connection/MetadataCollector.php b/src/Connection/MetadataCollector.php index 95d5f5e..9de1ce7 100644 --- a/src/Connection/MetadataCollector.php +++ b/src/Connection/MetadataCollector.php @@ -18,8 +18,8 @@ class MetadataCollector { /** * Cache des métadonnées - * - * @var array{table: array, columns: array[], indexes: array, foreign_keys: array} + * + * @var array{table: array, columns: list, indexes: array, foreign_keys: array} */ protected array $cache = [ 'tables' => [], @@ -30,7 +30,7 @@ class MetadataCollector /** * Constructeur - * + * * @param BaseConnection $db Instance de connexion */ public function __construct(protected BaseConnection $db) @@ -48,7 +48,7 @@ public function clearCache(): self 'indexes' => [], 'foreign_keys' => [], ]; - + return $this; } @@ -62,14 +62,15 @@ public function listTables(bool $constrainByPrefix = false): array } $result = $this->db->query($this->db->_listTables($constrainByPrefix)); - + $tables = []; + foreach ($result->resultArray() as $row) { $tables[] = current($row); } - + $this->cache['tables'] = $tables; - + return $this->filterTables($tables, $constrainByPrefix); } @@ -79,10 +80,10 @@ public function listTables(bool $constrainByPrefix = false): array public function tableExists(string $tableName, bool $cached = true): bool { $tables = $this->listTables(false); - + $tableName = str_replace($this->db->getPrefix(), '', $tableName); - - return in_array($tableName, $tables, true) + + return in_array($tableName, $tables, true) || in_array($this->db->getPrefix() . $tableName, $tables, true); } @@ -92,7 +93,7 @@ public function tableExists(string $tableName, bool $cached = true): bool public function getColumnNames(string $table): array { $data = $this->getColumnData($table); - + return array_column($data, 'name'); } @@ -102,7 +103,7 @@ public function getColumnNames(string $table): array public function columnExists(string $column, string $table): bool { $columns = $this->getColumnNames($table); - + return in_array($column, $columns, true); } @@ -116,9 +117,9 @@ public function getColumnData(string $table): array } $columns = $this->db->_listColumns($table); - + $this->cache['columns'][$table] = $columns; - + return $columns; } @@ -132,9 +133,9 @@ public function getIndexData(string $table): array } $indexes = $this->db->_listIndexes($table); - + $this->cache['indexes'][$table] = $indexes; - + return $indexes; } @@ -148,9 +149,9 @@ public function getForeignKeyData(string $table): array } $keys = $this->db->_listForeignKeys($table); - + $this->cache['foreign_keys'][$table] = $keys; - + return $keys; } @@ -159,10 +160,10 @@ public function getForeignKeyData(string $table): array */ protected function filterTables(array $tables, bool $constrainByPrefix): array { - if (!$constrainByPrefix || $this->db->getPrefix() === '') { + if (! $constrainByPrefix || $this->db->getPrefix() === '') { return $tables; } - - return array_filter($tables, fn($table) => str_starts_with($table, $this->db->getPrefix())); + + return array_filter($tables, fn ($table) => str_starts_with($table, $this->db->getPrefix())); } -} \ No newline at end of file +} diff --git a/src/Connection/MySQL.php b/src/Connection/MySQL.php index 47a5ebe..ef97c6c 100644 --- a/src/Connection/MySQL.php +++ b/src/Connection/MySQL.php @@ -40,24 +40,24 @@ class MySQL extends BaseConnection */ protected function getDsn(): string { - if (!empty($this->config['dsn'])) { + if (! empty($this->config['dsn'])) { return $this->config['dsn']; } $dsn = "mysql:host={$this->config['hostname']}"; - - if (!empty($this->config['port'])) { + + if (! empty($this->config['port'])) { $dsn .= ";port={$this->config['port']}"; } - - if (!empty($this->config['database'])) { + + if (! empty($this->config['database'])) { $dsn .= ";dbname={$this->config['database']}"; } - - if (!empty($this->config['charset'])) { + + if (! empty($this->config['charset'])) { $dsn .= ";charset={$this->config['charset']}"; } - + return $dsn; } @@ -67,16 +67,16 @@ protected function getDsn(): string protected function afterConnect(): void { // Configuration du charset - if (!empty($this->config['charset'])) { + if (! empty($this->config['charset'])) { $statement = "SET NAMES '{$this->config['charset']}'"; - - if (!empty($this->config['collation'])) { + + if (! empty($this->config['collation'])) { $statement .= " COLLATE '{$this->config['collation']}'"; } $this->pdo->exec($statement); } - + // Mode strict if (isset($this->config['strict_on']) && $this->config['strict_on'] === true) { $this->pdo->exec("SET sql_mode = 'STRICT_ALL_TABLES'"); @@ -91,12 +91,13 @@ public function setDatabase(string $databaseName): bool try { $this->pdo->exec("USE {$this->escapeIdentifiers($databaseName)}"); $this->config['database'] = $databaseName; + return true; } catch (PDOException $e) { throw new DatabaseException( - "Impossible de sélectionner la base de données : " . $e->getMessage(), + 'Impossible de sélectionner la base de données : ' . $e->getMessage(), 0, - $e + $e, ); } } @@ -107,14 +108,14 @@ public function setDatabase(string $databaseName): bool public function _listTables(bool $constrainByPrefix = false): string { $sql = "SHOW TABLES FROM `{$this->getDatabase()}`"; - + if ($constrainByPrefix && $this->getPrefix() !== '') { $sql .= " LIKE '" . $this->getPrefix() . "%'"; } - + return $sql; } - + /** * {@inheritDoc} */ @@ -126,13 +127,13 @@ public function _listIndexes(string $table): array $indexes = []; foreach ($rows as $row) { - $index = new stdClass(); + $index = new stdClass(); $index->name = $row->Key_name; - $index->type = match(true) { - $row->Key_name === 'PRIMARY' => 'PRIMARY', + $index->type = match (true) { + $row->Key_name === 'PRIMARY' => 'PRIMARY', $row->Index_type === 'FULLTEXT' => 'FULLTEXT', - isset($row->Non_unique) => $row->Index_type === 'SPATIAL' ? 'SPATIAL' : 'INDEX', - default => 'UNIQUE', + isset($row->Non_unique) => $row->Index_type === 'SPATIAL' ? 'SPATIAL' : 'INDEX', + default => 'UNIQUE', }; $indexes[] = $index; @@ -150,18 +151,18 @@ public function _listColumns(string $table): array $rows = $this->query($sql)->resultObject(); $columns = []; - + foreach ($rows as $row) { $column = new stdClass(); $column->name = $row->Field; $column->type = $row->Type; $column->nullable = $row->Null === 'YES'; $column->default = $row->Default; - $column->primary_key = $row->Key === 'PRI'; - + $column->primary_key = $row->Key === 'PRI'; + $columns[] = $column; } - + return $columns; } @@ -170,7 +171,7 @@ public function _listColumns(string $table): array */ public function _listForeignKeys(string $table): array { - $sql =' + $sql = ' SELECT tc.CONSTRAINT_NAME, tc.TABLE_NAME, diff --git a/src/Connection/Postgre.php b/src/Connection/Postgre.php index b7ce683..e672535 100644 --- a/src/Connection/Postgre.php +++ b/src/Connection/Postgre.php @@ -38,20 +38,20 @@ class Postgre extends BaseConnection */ protected function getDsn(): string { - if (!empty($this->config['dsn'])) { + if (! empty($this->config['dsn'])) { return $this->config['dsn']; } $dsn = "pgsql:host={$this->config['hostname']}"; - - if (!empty($this->config['port'])) { + + if (! empty($this->config['port'])) { $dsn .= ";port={$this->config['port']}"; } - - if (!empty($this->config['database'])) { + + if (! empty($this->config['database'])) { $dsn .= ";dbname={$this->config['database']}"; } - + return $dsn; } @@ -63,9 +63,9 @@ protected function afterConnect(): void // Configuration du schéma $schema = $this->config['schema'] ?? 'public'; $this->pdo->exec("SET search_path TO {$schema}"); - + // Configuration du charset - if (!empty($this->config['charset'])) { + if (! empty($this->config['charset'])) { $this->pdo->exec("SET NAMES '{$this->config['charset']}'"); } } @@ -76,14 +76,14 @@ protected function afterConnect(): void public function _listTables(bool $constrainByPrefix = false): string { $sql = "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname NOT IN ('information_schema','pg_catalog')"; - + if ($constrainByPrefix && $this->getPrefix() !== '') { $sql .= " AND tablename LIKE '" . $this->getPrefix() . "%'"; } - + return $sql; } - + /** * {@inheritDoc} */ @@ -99,9 +99,9 @@ public function _listIndexes(string $table): array $indexes = []; foreach ($rows as $row) { - $index = new stdClass(); - $index->name = $row->indexname; - $_columns = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef))); + $index = new stdClass(); + $index->name = $row->indexname; + $_columns = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef))); $index->columns = array_map(static fn ($v) => trim($v), $_columns); if (str_starts_with($row->indexdef, 'CREATE UNIQUE INDEX pk')) { @@ -129,7 +129,7 @@ public function _listColumns(string $table): array $rows = $this->query($sql)->resultObject(); $columns = []; - + foreach ($rows as $row) { $column = new stdClass(); $column->name = $row->column_name; @@ -137,10 +137,10 @@ public function _listColumns(string $table): array $column->nullable = $row->is_nullable === 'YES'; $column->default = $row->column_default; $column->max_length = $row->character_maximum_length > 0 ? $row->character_maximum_length : $row->numeric_precision; - + $columns[] = $column; } - + return $columns; } @@ -185,4 +185,4 @@ public function _listForeignKeys(string $table): array return $keys; } -} \ No newline at end of file +} diff --git a/src/Connection/SQLite.php b/src/Connection/SQLite.php index a7db1ea..eb08eb5 100644 --- a/src/Connection/SQLite.php +++ b/src/Connection/SQLite.php @@ -39,22 +39,22 @@ class SQLite extends BaseConnection */ protected function getDsn(): string { - if (!empty($this->config['dsn'])) { + if (! empty($this->config['dsn'])) { return $this->config['dsn']; } $database = $this->config['database']; - + if ($database === ':memory:') { return 'sqlite::memory:'; } - - if (!file_exists($database) && !is_writable(dirname($database))) { + + if (! file_exists($database) && ! is_writable(dirname($database))) { throw new DatabaseException( - "Impossible de créer la base de données SQLite : le répertoire n'est pas accessible en écriture" + "Impossible de créer la base de données SQLite : le répertoire n'est pas accessible en écriture", ); } - + return "sqlite:{$database}"; } @@ -64,7 +64,7 @@ protected function getDsn(): string protected function afterConnect(): void { // Activer les clés étrangères - if (!empty($this->config['foreign_keys'])) { + if (! empty($this->config['foreign_keys'])) { $this->pdo->exec($this->enableForeignKeyChecks); } } @@ -75,14 +75,14 @@ protected function afterConnect(): void public function _listTables(bool $constrainByPrefix = false): string { $sql = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"; - + if ($constrainByPrefix && $this->getPrefix() !== '') { $sql .= " AND name LIKE '" . $this->getPrefix() . "%'"; } - + return $sql; } - + /** * {@inheritDoc} */ @@ -103,7 +103,7 @@ public function _listIndexes(string $table): array INNER JOIN pragma_index_xinfo(sqlite_master.name) ii ON ii.name IS NOT NULL LEFT JOIN pragma_table_info(" . $this->escape(strtolower($table)) . ") ti ON ti.name = ii.name WHERE sqlite_master.type='index' AND sqlite_master.tbl_name = " . $this->escape(strtolower($table)) . ' COLLATE NOCASE'; - + $rows = $this->query($sql)->resultObject(); $indexes = []; @@ -128,17 +128,17 @@ public function _listColumns(string $table): array $columns = []; foreach ($rows as $row) { - $column = new stdClass(); - $column->name = $row->name; - $column->type = $row->type; - $column->nullable = !$row->notnull; - $column->default = $row->dflt_value; + $column = new stdClass(); + $column->name = $row->name; + $column->type = $row->type; + $column->nullable = ! $row->notnull; + $column->default = $row->dflt_value; $column->primary_key = (bool) $row->pk; - $column->max_length = null; - + $column->max_length = null; + $columns[] = $column; } - + return $columns; } @@ -168,4 +168,4 @@ public function _listForeignKeys(string $table): array return $keys; } -} \ No newline at end of file +} diff --git a/src/Creator/BaseCreator.php b/src/Creator/BaseCreator.php index fd6a8bc..8140df4 100644 --- a/src/Creator/BaseCreator.php +++ b/src/Creator/BaseCreator.php @@ -165,7 +165,7 @@ class BaseCreator protected array $dataCache = []; /** - * Drapeau de debugage. + * Drapeau de debugage. * Doit on afficher les erreurs ? */ protected bool $debug = false; @@ -192,7 +192,7 @@ class BaseCreator /** * Constructeur. - * + * * @param BaseConnection $db La connexion a la base de donnees */ public function __construct(protected BaseConnection $db) @@ -268,7 +268,7 @@ public function createDatabase(string $dbName, bool $ifNotExists = false): bool $ifNotExists ? $this->createDatabaseIfStr : $this->createDatabaseStr, $this->db->escapeIdentifier($dbName), $this->charset, - $this->collation + $this->collation, )); if (! $result && $this->debug) { @@ -328,14 +328,14 @@ public function dropDatabase(string $dbName): bool if (! $result && $this->debug) { throw CreatorException::unableToDropDatabase($dbName); } - + if (! empty($this->dataCache['db_names'])) { $key = array_search(strtolower($dbName), array_map('strtolower', $this->dataCache['db_names']), true); if ($key !== false) { unset($this->dataCache['db_names'][$key]); } } - + return $result; } catch (Throwable $e) { if ($this->debug) { @@ -521,7 +521,7 @@ public function dropForeignKey(string $table, string $foreignName): bool $sql = sprintf( (string) $this->dropConstraintStr, $this->db->prefixTable($table), - $this->db->escapeIdentifiers($foreignName) + $this->db->escapeIdentifiers($foreignName), ); if ($sql === '') { @@ -536,8 +536,8 @@ public function dropForeignKey(string $table, string $foreignName): bool } /** - * @throws InvalidArgumentException * @throws CreatorException + * @throws InvalidArgumentException */ public function createTable(string $table, bool $ifNotExists = false, array $attributes = []): bool { @@ -605,7 +605,7 @@ protected function _createTable(string $table, array $attributes): string 'CREATE TABLE', $this->db->escapeIdentifiers($table), $columns, - $this->_createTableAttributes($attributes) + $this->_createTableAttributes($attributes), ); } @@ -654,7 +654,7 @@ public function dropTable(string $tableName, bool $ifExists = false, bool $casca $key = array_search( strtolower($prefix . $tableName), array_map('strtolower', $this->dataCache['table_names']), - true + true, ); if ($key !== false) { @@ -688,8 +688,8 @@ protected function _dropTable(string $table, bool $ifExists, bool $cascade) } /** - * @throws InvalidArgumentException * @throws CreatorException + * @throws InvalidArgumentException */ public function renameTable(string $tableName, string $newTableName): bool { @@ -708,14 +708,14 @@ public function renameTable(string $tableName, string $newTableName): bool $result = $this->db->statement(sprintf( $this->renameTableStr, $this->db->prefixTable($tableName), - $this->db->prefixTable($newTableName) + $this->db->prefixTable($newTableName), )); if ($result && ! empty($this->dataCache['table_names'])) { $key = array_search( strtolower($this->prefix . $tableName), array_map('strtolower', $this->dataCache['table_names']), - true + true, ); if ($key !== false) { diff --git a/src/Creator/MySQL.php b/src/Creator/MySQL.php index 02275c0..bbbb6e9 100644 --- a/src/Creator/MySQL.php +++ b/src/Creator/MySQL.php @@ -282,7 +282,7 @@ public function dropPrimaryKey(string $table, string $keyName = ''): bool { $sql = sprintf( 'ALTER TABLE %s DROP PRIMARY KEY', - $this->db->prefixTable($table) + $this->db->prefixTable($table), ); return $this->db->statement($sql); diff --git a/src/Creator/SQLite.php b/src/Creator/SQLite.php index 860cfd2..ef2d074 100644 --- a/src/Creator/SQLite.php +++ b/src/Creator/SQLite.php @@ -234,15 +234,15 @@ protected function _processColumn(array $processedField): string if ($processedField['type'] === 'TEXT') { // Retirer les parenthèses autour de la contrainte $constraint = trim($processedField['length'], '()'); - + if (str_starts_with($constraint, "'")) { // Cas énumération : ('A','B','C') $processedField['type'] .= ' CHECK(' . $column . ' IN (' . $constraint . '))'; - } elseif (ctype_digit($constraint) && (int)$constraint > 0) { + } elseif (ctype_digit($constraint) && (int) $constraint > 0) { // Cas longueur numérique : (255) - $processedField['type'] .= ' CHECK(length(' . $column . ') <= ' . (int)$constraint . ')'; + $processedField['type'] .= ' CHECK(length(' . $column . ') <= ' . (int) $constraint . ')'; } - } + } return $column . ' ' . $processedField['type'] @@ -278,7 +278,7 @@ protected function _attributeType(array &$attributes) protected function _attributeAutoIncrement(array &$attributes, array &$field) { if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true - && stripos($field['type'], 'int') !== false) { + && str_contains(strtolower($field['type']), strtolower('int'))) { $field['type'] = 'INTEGER PRIMARY KEY'; $field['default'] = ''; $field['null'] = ''; diff --git a/src/Creator/SQLite/Table.php b/src/Creator/SQLite/Table.php index 198cb6f..ec6de3c 100644 --- a/src/Creator/SQLite/Table.php +++ b/src/Creator/SQLite/Table.php @@ -297,7 +297,7 @@ protected function createTable() $this->creator->addForeignKey( $foreignKey->column_name, trim($foreignKey->foreign_table_name, $prefix), - $foreignKey->foreign_column_name + $foreignKey->foreign_column_name, ); } @@ -320,15 +320,15 @@ protected function copyData(): void $exFields = implode( ', ', - array_map(fn ($item) => $this->db->protectIdentifiers($item), $exFields) + array_map(fn ($item) => $this->db->protectIdentifiers($item), $exFields), ); $newFields = implode( ', ', - array_map(fn ($item) => $this->db->protectIdentifiers($item), $newFields) + array_map(fn ($item) => $this->db->protectIdentifiers($item), $newFields), ); $this->db->statement( - "INSERT INTO {$this->prefixedTableName}({$newFields}) SELECT {$exFields} FROM {$this->db->getPrefix()}temp_{$this->tableName}" + "INSERT INTO {$this->prefixedTableName}({$newFields}) SELECT {$exFields} FROM {$this->db->getPrefix()}temp_{$this->tableName}", ); } diff --git a/src/DatabaseManager.php b/src/DatabaseManager.php index 7e1a6ff..10b6f16 100644 --- a/src/DatabaseManager.php +++ b/src/DatabaseManager.php @@ -21,7 +21,7 @@ /** * Gestionnaire de bases de données - * + * * Responsabilités: * - Résolution des connexions * - Gestion des instances partagées @@ -51,9 +51,9 @@ class DatabaseManager implements ConnectionResolverInterface /** * Constructeur - * - * @param ?LoggerInterface $logger Logger - * @param ?EventManagerInterface $event Event Manager + * + * @param ?LoggerInterface $logger Logger + * @param ?EventManagerInterface $event Event Manager */ public function __construct(protected ?LoggerInterface $logger = null, protected ?EventManagerInterface $event = null) { @@ -74,7 +74,7 @@ public function connection(?string $name = null): ConnectionInterface /** * {@inheritDoc} - * + * * @param array|ConnectionInterface|string|null $group */ public function connect($group = null, bool $shared = true): ConnectionInterface @@ -103,14 +103,14 @@ public function connect($group = null, bool $shared = true): ConnectionInterface /** * {@inheritDoc} - * - * @return array{0: string, 1: array} [nom_du_groupe, configuration] + * + * @return array{0: string, 1: array} [nom_du_groupe, configuration] */ public function connectionInfo(array|string|null $group = null): array { // Si c'est un tableau, c'est une configuration ad-hoc if (is_array($group)) { - $config = $group; + $config = $group; $groupName = 'custom-' . md5(json_encode($config)); return [$groupName, $config]; @@ -127,11 +127,11 @@ public function connectionInfo(array|string|null $group = null): array } // Fallback vers default si le groupe n'existe pas - if (!isset($config[$group]) && $group !== 'default' && !str_starts_with($group, 'custom-')) { + if (! isset($config[$group]) && $group !== 'default' && ! str_starts_with($group, 'custom-')) { $group = 'default'; } - if (!isset($config[$group])) { + if (! isset($config[$group])) { throw new InvalidArgumentException("Le groupe de connexion '{$group}' n'est pas configuré."); } @@ -158,11 +158,11 @@ public function builder(?ConnectionInterface $db = null): BaseBuilder */ public function creator(?ConnectionInterface $db = null): BaseCreator { - $db = $db ?? $this->activeConnection(); + $db ??= $this->activeConnection(); - $driver = $this->normalizeDriver($db->getDriver()); + $driver = $this->normalizeDriver($db->getDriver()); $className = "BlitzPHP\\Database\\Creator\\{$driver}"; - + return new $className($db); } @@ -192,7 +192,7 @@ public function getConnections(): array /** * Retourne les noms de tous les groupes de connexion configurés - * + * * @return list */ public function getConnectionNames(): array @@ -241,7 +241,7 @@ protected function resolveSqlitePath(array $config): array } if (! str_contains($config['database'], DIRECTORY_SEPARATOR)) { - $config['database'] = defined('APP_STORAGE_PATH') + $config['database'] = defined('APP_STORAGE_PATH') ? APP_STORAGE_PATH . $config['database'] : $config['database']; } @@ -255,7 +255,7 @@ protected function resolveSqlitePath(array $config): array protected function createConnection(array $config): ConnectionInterface { // Parser le DSN si nécessaire - if (!empty($config['dsn']) && str_contains($config['dsn'], '://')) { + if (! empty($config['dsn']) && str_contains($config['dsn'], '://')) { $config = $this->parseDSN($config); } @@ -273,12 +273,12 @@ protected function normalizeDriver(string $driver): string { // Enlever 'pdo' du nom si présent $driver = str_ireplace('pdo', '', $driver); - + return match (strtolower($driver)) { 'mysql' => 'MySQL', 'pgsql', 'postgre', 'postgresql' => 'Postgre', 'sqlite' => 'SQLite', - default => throw new InvalidArgumentException("Driver non supporté : {$driver}") + default => throw new InvalidArgumentException("Driver non supporté : {$driver}"), }; } @@ -289,7 +289,7 @@ protected function parseDSN(array $params): array { $dsn = parse_url($params['dsn']); - if (!$dsn) { + if (! $dsn) { throw new InvalidArgumentException('La chaîne DSN est invalide.'); } @@ -303,8 +303,9 @@ protected function parseDSN(array $params): array 'database' => isset($dsn['path']) ? rawurldecode(substr($dsn['path'], 1)) : '', ]; - if (!empty($dsn['query'])) { + if (! empty($dsn['query'])) { parse_str($dsn['query'], $extra); + foreach ($extra as $key => $val) { if (is_string($val) && in_array(strtolower($val), ['true', 'false', 'null'], true)) { $val = $val === 'null' ? null : filter_var($val, FILTER_VALIDATE_BOOLEAN); diff --git a/src/Exceptions/CreatorException.php b/src/Exceptions/CreatorException.php index 83cbb97..1f7444b 100644 --- a/src/Exceptions/CreatorException.php +++ b/src/Exceptions/CreatorException.php @@ -22,7 +22,7 @@ public static function unsupportedFeature(string $feature): self { return new static(static::t( 'La fonctionnalité "%s" n\'est pas supportée par ce pilote de base de données.', - [$feature] + [$feature], )); } @@ -30,7 +30,7 @@ public static function missingFieldDefinition(string $table): self { return new static(static::t( 'Aucun champ défini pour la table "%s".', - [$table] + [$table], )); } @@ -38,7 +38,7 @@ public static function invalidFieldType(string $type): self { return new static(static::t( 'Type de champ invalide : "%s".', - [$type] + [$type], )); } @@ -46,7 +46,7 @@ public static function unableToCreateDatabase(string $name, ?Throwable $previous { return new static(static::t('Impossible de créer la base de données "%s".', [$name]), previous: $previous); } - + public static function unableToDropDatabase(string $name, ?Throwable $previous = null): self { return new static(static::t('Impossible de supprimer la base de données "%s".', [$name]), previous: $previous); diff --git a/src/Exceptions/MultipleRecordsFoundException.php b/src/Exceptions/MultipleRecordsFoundException.php index d1bd32f..57f242f 100644 --- a/src/Exceptions/MultipleRecordsFoundException.php +++ b/src/Exceptions/MultipleRecordsFoundException.php @@ -1,9 +1,9 @@ + * (c) 2022 Dimitri Sitchet Tomkeu * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. diff --git a/src/Exceptions/QueryException.php b/src/Exceptions/QueryException.php index f2df02e..8c077ae 100644 --- a/src/Exceptions/QueryException.php +++ b/src/Exceptions/QueryException.php @@ -20,17 +20,17 @@ class QueryException extends PDOException /** * Create a new query exception instance. * - * @param string $connectionName Le nom de la connexion à la base de données. - * @param string $sql Le SQL de la requête. - * @param array $bindings Les bindings pour la requête. - * @param null|'read'|'write' $readWriteType Le type de lecture/écriture PDO pour la requête exécutée. - * @param array $connectionDetails Les détails de connexion pour la requête (hôte, port, base de données, etc.). + * @param string $connectionName Le nom de la connexion à la base de données. + * @param string $sql Le SQL de la requête. + * @param array $bindings Les bindings pour la requête. + * @param 'read'|'write'|null $readWriteType Le type de lecture/écriture PDO pour la requête exécutée. + * @param array $connectionDetails Les détails de connexion pour la requête (hôte, port, base de données, etc.). */ public function __construct(public string $connectionName, protected string $sql, protected array $bindings, Throwable $previous, protected array $connectionDetails = [], public ?string $readWriteType = null) { parent::__construct('', 0, $previous); - $this->code = $previous->getCode(); + $this->code = $previous->getCode(); $this->message = $this->formatMessage($connectionName, $sql, $bindings, $previous); if ($previous instanceof PDOException) { @@ -45,7 +45,7 @@ protected function formatMessage(string $connectionName, string $sql, array $bin { $details = $this->formatConnectionDetails(); - return $previous->getMessage().' (Connection: '.$connectionName.$details.', SQL: '. Text::replaceArray('?', $bindings, $sql).')'; + return $previous->getMessage() . ' (Connection: ' . $connectionName . $details . ', SQL: ' . Text::replaceArray('?', $bindings, $sql) . ')'; } /** @@ -63,18 +63,18 @@ protected function formatConnectionDetails(): string if ($driver !== 'sqlite') { if (! empty($this->connectionDetails['unix_socket'])) { - $segments[] = 'Socket: '.$this->connectionDetails['unix_socket']; + $segments[] = 'Socket: ' . $this->connectionDetails['unix_socket']; } else { $host = $this->connectionDetails['host'] ?? ''; - $segments[] = 'Host: '.(is_array($host) ? implode(', ', $host) : $host); - $segments[] = 'Port: '.($this->connectionDetails['port'] ?? ''); + $segments[] = 'Host: ' . (is_array($host) ? implode(', ', $host) : $host); + $segments[] = 'Port: ' . ($this->connectionDetails['port'] ?? ''); } } - $segments[] = 'Database: '.($this->connectionDetails['database'] ?? ''); + $segments[] = 'Database: ' . ($this->connectionDetails['database'] ?? ''); - return ', '.implode(', ', $segments); + return ', ' . implode(', ', $segments); } /** diff --git a/src/Exceptions/RecordsNotFoundException.php b/src/Exceptions/RecordsNotFoundException.php index a5a0b27..049ac38 100644 --- a/src/Exceptions/RecordsNotFoundException.php +++ b/src/Exceptions/RecordsNotFoundException.php @@ -1,9 +1,9 @@ + * (c) 2022 Dimitri Sitchet Tomkeu * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. diff --git a/src/Exceptions/SeederException.php b/src/Exceptions/SeederException.php index 07fd034..7afdd99 100644 --- a/src/Exceptions/SeederException.php +++ b/src/Exceptions/SeederException.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace BlitzPHP\Database\Exceptions; class SeederException extends DatabaseException diff --git a/src/Listeners/DatabaseListener.php b/src/Listeners/DatabaseListener.php index c862396..2fd0e04 100644 --- a/src/Listeners/DatabaseListener.php +++ b/src/Listeners/DatabaseListener.php @@ -11,6 +11,7 @@ namespace BlitzPHP\Database\Listeners; +use BlitzPHP\Cli\Commands\Config\About; use BlitzPHP\Contracts\Database\ConnectionInterface; use BlitzPHP\Contracts\Database\ConnectionResolverInterface; use BlitzPHP\Contracts\Event\EventInterface; @@ -37,11 +38,11 @@ public function listen(EventManagerInterface $event): void private function addInfoToAboutCommand() { - if (! class_exists(\BlitzPHP\Cli\Commands\Config\About::class)) { + if (! class_exists(About::class)) { return; } - \BlitzPHP\Cli\Commands\Config\About::add('Gestionnaires', static fn (ConnectionResolverInterface $connectionResolver) => array_filter([ + About::add('Gestionnaires', static fn (ConnectionResolverInterface $connectionResolver) => array_filter([ 'Base de données' => static function () use ($connectionResolver) { [$group, $config] = $connectionResolver->connectionInfo(); @@ -72,7 +73,7 @@ private function addInfoToAboutCommand() private function extendsFramework() { - FileLocator::macro('model', function(string $model, ?ConnectionInterface $connection = null) { + FileLocator::macro('model', static function (string $model, ?ConnectionInterface $connection = null) { if (! class_exists($model) && ! str_ends_with($model, 'Model')) { $model .= 'Model'; } @@ -89,7 +90,7 @@ private function extendsFramework() return service('container')->make($model, ['db' => $connection]); }); - Load::macro('model', function(array|string $model, ?ConnectionInterface $connection = null) { + Load::macro('model', static function (array|string $model, ?ConnectionInterface $connection = null) { if ($model === '' || $model === '0' || $model === []) { throw new LoadException('Veuillez specifier le modele à charger'); } @@ -99,13 +100,13 @@ private function extendsFramework() foreach ($models as $model) { if (null === $result = self::getLoaded('models', $model)) { - $result = FileLocator::model($model, $connection); - self::loaded('models', $model, $result); + $result = FileLocator::model($model, $connection); + self::loaded('models', $model, $result); } $results[] = $result; } - + return count($results) === 1 ? $results[0] : $results; }); } diff --git a/src/Migration/Builder.php b/src/Migration/Builder.php index a8b9f4d..5d7bfbc 100644 --- a/src/Migration/Builder.php +++ b/src/Migration/Builder.php @@ -22,7 +22,7 @@ /** * Constructeur de définition de table - * + * * Cette classe permet de définir la structure d'une table de manière fluide. * Elle est utilisée par les migrations pour décrire les modifications à apporter. * @@ -40,21 +40,21 @@ class Builder /** * Liste des colonnes à ajouter/modifier * - * @var array + * @var list */ protected array $columns = []; /** * Liste des index à ajouter * - * @var array + * @var list */ protected array $indexes = []; /** * Liste des clés étrangères à ajouter * - * @var array + * @var list */ protected array $foreignKeys = []; @@ -67,11 +67,11 @@ class Builder * Éléments à supprimer * * @var array{ - * columns?: array, + * columns?: list, * primary?: string, - * unique?: array, - * index?: array, - * foreign?: array + * unique?: list, + * index?: list, + * foreign?: list * } */ protected array $drops = []; @@ -116,7 +116,7 @@ class Builder /** * Constructeur * - * @param string $table Nom de la table + * @param string $table Nom de la table * @param string $prefix Préfixe de la table */ public function __construct(protected string $table, protected string $prefix = '') @@ -125,19 +125,19 @@ public function __construct(protected string $table, protected string $prefix = /** * Spécifie la connexion utilisée pour ce builder - * + * * @internal */ public function setConnection(BaseConnection $db): self { $this->db = $db; - + return $this; } /** * Récupère l'instance de la connexion utilisée par ce builder - * + * * @internal */ public function getConnection(): BaseConnection @@ -153,7 +153,7 @@ public function getConnection(): BaseConnection /** * Indique que la table doit être créée - * + * * @internal */ public function createTable(bool $ifNotExists = false): void @@ -163,7 +163,7 @@ public function createTable(bool $ifNotExists = false): void /** * Indique que la table doit être modifiée - * + * * @internal */ public function alterTable(): void @@ -173,7 +173,7 @@ public function alterTable(): void /** * Indique que la table doit être supprimée - * + * * @internal */ public function dropTable(bool $ifExists = false): void @@ -183,7 +183,7 @@ public function dropTable(bool $ifExists = false): void /** * Indique que la table doit être renommée - * + * * @internal */ public function renameTable(string $to): void @@ -244,7 +244,7 @@ public function temporary(): static /** * Ajoute un commentaire à la table */ - public function comment(string $comment): static + public function comment(string $comment): static { $this->comment = $comment; @@ -473,7 +473,7 @@ public function foreignId(string $column): ForeignId /** * Ajoute une colonne de clé étrangère pour le modèle donné * - * @param string $model Classe du modèle + * @param string $model Classe du modèle * @param string|null $column Nom de la colonne */ public function foreignIdFor(string $model, ?string $column = null): ForeignId @@ -713,7 +713,7 @@ public function uuid(string $column): Column /** * Ajoute une colonne UUID avec contrainte de clé étrangère */ - public function foreignUuid(string $column): ForeignId + public function foreignUuid(string $column): ForeignId { return $this->addColumnDefinition(new ForeignId($this, [ 'type' => 'uuid', @@ -1015,7 +1015,7 @@ public function dropConstrainedForeignId(string $column): void */ public function dropForeignIdFor(string $model, ?string $column = null): void { - $column = $column ?? strtolower(basename(str_replace('\\', '/', $model))) . '_id'; + $column ??= strtolower(basename(str_replace('\\', '/', $model))) . '_id'; $this->dropForeign([$column]); } @@ -1025,8 +1025,8 @@ public function dropForeignIdFor(string $model, ?string $column = null): void */ public function dropConstrainedForeignIdFor(string $model, ?string $column = null): void { - $column = $column ?? strtolower(basename(str_replace('\\', '/', $model))) . '_id'; - + $column ??= strtolower(basename(str_replace('\\', '/', $model))) . '_id'; + $this->dropConstrainedForeignId($column); } @@ -1164,7 +1164,7 @@ public function dropMorphs(string $name, ?string $indexName = null): void protected function addColumn(string $type, string $name, array $attributes = []): Column { return $this->addColumnDefinition(new Column( - array_merge(['type' => $type, 'name' => $name], $attributes) + array_merge(['type' => $type, 'name' => $name], $attributes), )); } @@ -1190,8 +1190,8 @@ protected function addColumnDefinition(Column $definition): Column protected function addIndex(string $type, array|string $columns, ?string $name, ?string $algorithm = null): Index { $columns = (array) $columns; - $name = $name ?? $this->createIndexName($type, $columns); - $index = new Index(array_filter(compact('type', 'name', 'columns', 'algorithm'))); + $name ??= $this->createIndexName($type, $columns); + $index = new Index(array_filter(compact('type', 'name', 'columns', 'algorithm'))); $this->indexes[] = $index; return $index; @@ -1203,8 +1203,8 @@ protected function addIndex(string $type, array|string $columns, ?string $name, protected function addForeignKey(array|string $columns, ?string $name): ForeignKey { $columns = (array) $columns; - $name = $name ?? $this->createIndexName('foreign', $columns); - $fk = new ForeignKey(compact('name', 'columns')); + $name ??= $this->createIndexName('foreign', $columns); + $fk = new ForeignKey(compact('name', 'columns')); $this->foreignKeys[] = $fk; return $fk; @@ -1218,22 +1218,22 @@ protected function dropNamedIndex(string $type, array|string $columns): void if (is_string($columns)) { $this->drops[$type][] = $columns; } else { - $name = $this->createIndexName($type, $columns); + $name = $this->createIndexName($type, $columns); $this->drops[$type][] = $name; } } /** * Crée un nom d'index par défaut - * + * * Exemple : users_email_unique pour un index unique sur la colonne email de la table users - * + * * @internal */ public function createIndexName(string $type, array $columns): string { $index = strtolower($this->table . '_' . implode('_', $columns) . '_' . $type); - + return str_replace(['-', '.'], '_', $index); } @@ -1242,7 +1242,7 @@ public function createIndexName(string $type, array $columns): string */ public function removeColumn(string $column): self { - $this->columns = array_values(array_filter($this->columns, fn($c) => $c->name != $column)); + $this->columns = array_values(array_filter($this->columns, static fn ($c) => $c->name !== $column)); return $this; } @@ -1280,7 +1280,7 @@ public function getAction(): string /** * Récupère les colonnes * - * @return array + * @return list */ public function getColumns(): array { @@ -1290,27 +1290,27 @@ public function getColumns(): array /** * Récupère les colonnes ajoutées (non modifiées) * - * @return array + * @return list */ public function getAddedColumns(): array { - return array_filter($this->columns, fn($column) => !$column->change); + return array_filter($this->columns, static fn ($column) => ! $column->change); } /** * Récupère les colonnes modifiées * - * @return array + * @return list */ public function getChangedColumns(): array { - return array_filter($this->columns, fn($column) => (bool) $column->change); + return array_filter($this->columns, static fn ($column) => (bool) $column->change); } /** * Récupère les index * - * @return array + * @return list */ public function getIndexes(): array { @@ -1320,7 +1320,7 @@ public function getIndexes(): array /** * Récupère les clés étrangères * - * @return array + * @return list */ public function getForeignKeys(): array { diff --git a/src/Migration/ConnectionProxy.php b/src/Migration/ConnectionProxy.php index 9635fe0..eed366b 100644 --- a/src/Migration/ConnectionProxy.php +++ b/src/Migration/ConnectionProxy.php @@ -15,12 +15,12 @@ /** * Proxy pour permettre les opérations sur différentes connexions - * + * * Permet d'écrire : $this->connection('sqlite')->create('users', ...) */ class ConnectionProxy { - public function __construct(protected BaseConnection $connection, protected Migration $migration) + public function __construct(protected BaseConnection $connection, protected Migration $migration) { } @@ -33,7 +33,7 @@ public function create(string $table, callable $callback, bool $ifNotExists = fa $this->getConnectionName(), $table, $callback, - $ifNotExists + $ifNotExists, ); } @@ -53,7 +53,7 @@ public function alter(string $table, callable $callback): void $this->migration->alterOnConnection( $this->getConnectionName(), $table, - $callback + $callback, ); } @@ -73,7 +73,7 @@ public function drop(string $table, bool $ifExists = false): void $this->migration->dropOnConnection( $this->getConnectionName(), $table, - $ifExists + $ifExists, ); } @@ -93,30 +93,30 @@ public function rename(string $from, string $to): void $this->migration->renameOnConnection( $this->getConnectionName(), $from, - $to + $to, ); } /** * Vérifie si une table existe */ - public function hasTable(string $name): bool + public function hasTable(string $name): bool { return $this->migration->hasTableOnConnection( - $this->getConnectionName(), - $name + $this->getConnectionName(), + $name, ); } /** * Vérifie si un champ existe dans une table */ - public function hasColumn(string $table, string $column): bool + public function hasColumn(string $table, string $column): bool { return $this->migration->hasColumnOnConnection( - $this->getConnectionName(), - $table, - $column + $this->getConnectionName(), + $table, + $column, ); } @@ -131,7 +131,7 @@ protected function getConnectionName(): string return $name; } } - + return 'unknown'; } } diff --git a/src/Migration/Definitions/Column.php b/src/Migration/Definitions/Column.php index 7329c06..bbccec4 100644 --- a/src/Migration/Definitions/Column.php +++ b/src/Migration/Definitions/Column.php @@ -15,33 +15,28 @@ /** * Definition des colonnes de la struture de migrations - * * /** - * @method $this after(string $column) Place la colonne "après" une autre colonne (MySQL) - * @method $this always(bool $value = true) Utilisé comme modificateur pour generatedAs() (PostgreSQL) + * @method $this invisible() Spécifie que la colonne doit être invisible pour "SELECT *" (MySQL) + * @method $this startingValue(int $startingValue) Définit la valeur de départ d'un champ auto-incrémenté (MySQL/PostgreSQL) * @method $this autoIncrement() Définit les colonnes INTEGER comme auto-incrémentées (clé primaire) - * @method $this change() Modifie la colonne - * @method $this charset(string $charset) Spécifie un jeu de caractères pour la colonne (MySQL) - * @method $this collation(string $collation) Spécifie une collation pour la colonne (MySQL/PostgreSQL/SQL Server) + * @method $this change() Modifie la colonne * @method $this comment(string $comment) Ajoute un commentaire à la colonne (MySQL/PostgreSQL) - * @method $this default(mixed $value) Spécifie une valeur "par défaut" pour la colonne - * @method $this first() Place la colonne "en premier" dans la table (MySQL) - * @method $this from(int $startingValue) Définit la valeur de départ d'un champ auto-incrémenté (MySQL/PostgreSQL) - * @method $this generatedAs(string|\Illuminate\Database\Query\Expression $expression = null) Crée une colonne d'identité conforme SQL (PostgreSQL) - * @method $this index(string $indexName = null) Ajoute un index - * @method $this invisible() Spécifie que la colonne doit être invisible pour "SELECT *" (MySQL) - * @method $this nullable(bool $value = true) Autorise l'insertion de valeurs NULL dans la colonne + * @method $this default(mixed $value) Spécifie une valeur "par défaut" pour la colonne + * @method $this fulltext(string $indexName = null) Ajoute un index FULLTEXT + * @method $this always(bool $value = true) Utilisé comme modificateur pour generatedAs() (PostgreSQL) + * @method $this index(string $indexName = null) Ajoute un index + * @method $this useCurrentOnUpdate() Définit la colonne TIMESTAMP pour utiliser CURRENT_TIMESTAMP lors de la mise à jour (MySQL) + * @method $this nullable(bool $value = true) Autorise l'insertion de valeurs NULL dans la colonne * @method $this persisted() Marque la colonne générée calculée comme persistante (SQL Server) - * @method $this primary() Ajoute un index primaire - * @method $this fulltext(string $indexName = null) Ajoute un index FULLTEXT - * @method $this spatialIndex(string $indexName = null) Ajoute un index spatial - * @method $this startingValue(int $startingValue) Définit la valeur de départ d'un champ auto-incrémenté (MySQL/PostgreSQL) - * @method $this storedAs(string $expression) Crée une colonne générée stockée (MySQL/PostgreSQL/SQLite) - * @method $this type(string $type) Spécifie un type pour la colonne - * @method $this unique(string $indexName = null) Ajoute un index unique + * @method $this primary() Ajoute un index primaire * @method $this unsigned() Définit la colonne INTEGER comme NON SIGNÉE (MySQL) - * @method $this useCurrent() Définit la colonne TIMESTAMP pour utiliser CURRENT_TIMESTAMP comme valeur par défaut - * @method $this useCurrentOnUpdate() Définit la colonne TIMESTAMP pour utiliser CURRENT_TIMESTAMP lors de la mise à jour (MySQL) + * @method $this spatialIndex(string $indexName = null) Ajoute un index spatial + * @method $this generatedAs(string|\BlitzPHP\Database\Query\Expression $expression = null) Crée une colonne d'identité conforme SQL (PostgreSQL) + * @method $this storedAs(string $expression) Crée une colonne générée stockée (MySQL/PostgreSQL/SQLite) + * @method $this first() Place la colonne "en premier" dans la table (MySQL) + * @method $this type(string $type) Spécifie un type pour la colonne + * @method $this unique(string $indexName = null) Ajoute un index unique + * @method $this useCurrent() Définit la colonne TIMESTAMP pour utiliser CURRENT_TIMESTAMP comme valeur par défaut * @method $this virtualAs(string $expression) Crée une colonne générée virtuelle (MySQL/PostgreSQL/SQLite) * * @credit Laravel Framework - Illuminate\Database\Schema\ColumnDefinition @@ -60,6 +55,7 @@ public function hasFluentIndexes(): bool return true; } } + return false; } @@ -69,11 +65,13 @@ public function hasFluentIndexes(): bool public function getFluentIndexes(): array { $indexes = []; + foreach ($this->_indexes as $index) { if (isset($this->attributes[$index])) { $indexes[$index] = $this->attributes[$index]; } } + return $indexes; } } diff --git a/src/Migration/Definitions/ForeignKey.php b/src/Migration/Definitions/ForeignKey.php index 55d8eef..5b1739b 100644 --- a/src/Migration/Definitions/ForeignKey.php +++ b/src/Migration/Definitions/ForeignKey.php @@ -14,13 +14,13 @@ use BlitzPHP\Utilities\Support\Fluent; /** - * @method $this deferrable(bool $value = true) Définit la clé étrangère comme différable (PostgreSQL) * @method $this initiallyImmediate(bool $value = true) Définit le moment par défaut pour vérifier la contrainte (PostgreSQL) - * @method $this on(string $table) Spécifie la table référencée - * @method $this onDelete(string $action) Ajoute une action ON DELETE - * @method $this onUpdate(string $action) Ajoute une action ON UPDATE - * @method $this references(string|array $columns) Spécifie la ou les colonnes référencées - * + * @method $this deferrable(bool $value = true) Définit la clé étrangère comme différable (PostgreSQL) + * @method $this on(string $table) Spécifie la table référencée + * @method $this onDelete(string $action) Ajoute une action ON DELETE + * @method $this onUpdate(string $action) Ajoute une action ON UPDATE + * @method $this references(array|string $columns) Spécifie la ou les colonnes référencées + * * @credit Laravel Framework - Illuminate\Database\Schema\ForeignKeyDefinition */ class ForeignKey extends Fluent diff --git a/src/Migration/History.php b/src/Migration/History.php index 628dbd7..93ec484 100644 --- a/src/Migration/History.php +++ b/src/Migration/History.php @@ -16,7 +16,7 @@ /** * Gestionnaire de l'historique des migrations - * + * * Cette classe s'occupe de la table de migrations qui enregistre * toutes les migrations exécutées. */ @@ -27,7 +27,7 @@ class History */ protected BaseConnection $db; - /** + /** * Indique si la table d'historique a été vérifiée/créée */ private bool $tableChecked = false; @@ -35,7 +35,7 @@ class History /** * Constructeur * - * @param string $table Nom de la table d'historique des migrations + * @param string $table Nom de la table d'historique des migrations */ public function __construct(protected DatabaseManager $dbManager, protected string $table = 'migrations') { @@ -62,7 +62,7 @@ protected function ensureTable(): void $builder->unsignedInteger('batch'); $builder->integer('time'); $builder->createTable(true); - + (new Transformer($this->dbManager->creator($this->db)))->process($builder); $this->tableChecked = true; @@ -71,14 +71,14 @@ protected function ensureTable(): void /** * Récupère tout l'historique * - * @return array + * @return list */ public function getAll(?string $group = null): array { return $this->db->table($this->table) ->orderBy('batch', 'ASC') ->orderBy('id', 'ASC') - ->when($group, function ($query) use ($group) { + ->when($group, static function ($query) use ($group) { $query->where('group', $group); }) ->all(); @@ -86,8 +86,8 @@ public function getAll(?string $group = null): array /** * Récupère l'historique d'un lot - * - * @return array + * + * @return list */ public function getBatch(int $batch, string $order = 'asc'): array { @@ -108,7 +108,7 @@ public function getLastBatch(): int /** * Récupère tous les numéros de lots * - * @return array + * @return list */ public function getBatches(): array { diff --git a/src/Migration/Migration.php b/src/Migration/Migration.php index dbf376a..32eb162 100644 --- a/src/Migration/Migration.php +++ b/src/Migration/Migration.php @@ -11,6 +11,7 @@ namespace BlitzPHP\Database\Migration; +use BlitzPHP\Database\Config\Services; use BlitzPHP\Database\Connection\BaseConnection; use BlitzPHP\Database\DatabaseManager; @@ -55,7 +56,7 @@ abstract public function down(): void; /** * Détermine si cette migration doit être exécutée - * + * * Peut être surchargée pour des conditions complexes */ public function shouldRun(): bool @@ -65,7 +66,7 @@ public function shouldRun(): bool /** * Initialise les éléments nécessaire pour le fonctionnement de la migration - * + * * @internal Utilisé par le Runner pour injecter la connexion et le gestionnaire de bd */ public function initialize(DatabaseManager $dbManager, BaseConnection $db): self @@ -81,7 +82,7 @@ public function initialize(DatabaseManager $dbManager, BaseConnection $db): self * Récupère les builders de tables * * @return list - * + * * @internal Utilisé par le Runner */ public function getBuilders(): array @@ -93,7 +94,7 @@ public function getBuilders(): array * Récupère les connexions utilisées par cette migration * * @return array - * + * * @internal Utilisé par le ConnectionProxy */ public function getConnections(): array @@ -103,7 +104,7 @@ public function getConnections(): array /** * Crée une nouvelle table sur une connexion spécifique - * + * * @internal Utilisé par le ConnectionProxy */ public function createOnConnection(string $connection, string $table, callable $callback, bool $ifNotExists = false): void @@ -117,7 +118,7 @@ public function createOnConnection(string $connection, string $table, callable $ /** * Modifie une table existante sur une connexion spécifique - * + * * @internal Utilisé par le ConnectionProxy */ public function alterOnConnection(string $connection, string $table, callable $callback): void @@ -131,46 +132,46 @@ public function alterOnConnection(string $connection, string $table, callable $c /** * Supprime une table existante sur une connexion spécifique - * + * * @internal Utilisé par le ConnectionProxy */ public function dropOnConnection(string $connection, string $table, bool $ifExists): void { $builder = $this->makeBuilderFor($connection, $table); $builder->dropTable($ifExists); - + $this->builders[] = $builder; } /** * renomme une table existante sur une connexion spécifique - * + * * @internal Utilisé par le ConnectionProxy */ public function renameOnConnection(string $connection, string $from, string $to): void { $builder = $this->makeBuilderFor($connection, $from); $builder->renameTable($to); - + $this->builders[] = $builder; } /** * Vérifie si une table existe sur une connexion spécifique - * + * * @internal Utilisé par le ConnectionProxy */ - public function hasTableOnConnection(string $connection, string $table): bool + public function hasTableOnConnection(string $connection, string $table): bool { return ($this->connections[$connection] ?? $this->db)->tableExists($table); } /** * Vérifie si un champ existe dans une table sur une connexion spécifique - * + * * @internal Utilisé par le ConnectionProxy */ - public function hasColumnOnConnection(string $connection, string $table, string $column): bool + public function hasColumnOnConnection(string $connection, string $table, string $column): bool { return ($this->connections[$connection] ?? $this->db)->columnExists($column, $table); } @@ -180,9 +181,9 @@ public function hasColumnOnConnection(string $connection, string $table, string */ protected function connection(string $name): ConnectionProxy { - if (!isset($this->connections[$name])) { + if (! isset($this->connections[$name])) { // Résoudre la connexion via le DatabaseManager - $this->connections[$name] = $this->db->dbManager()->connect($name); + $this->connections[$name] = Services::dbManager()->connect($name); } return new ConnectionProxy($this->connections[$name], $this); @@ -247,7 +248,7 @@ protected function rename(string $from, string $to): void /** * Vérifie si une table existe */ - protected function hasTable(string $name): bool + protected function hasTable(string $name): bool { return $this->hasTableOnConnection('default', $name); } @@ -255,7 +256,7 @@ protected function hasTable(string $name): bool /** * Vérifie si un champ existe dans une table */ - protected function hasColumn(string $table, string $column): bool + protected function hasColumn(string $table, string $column): bool { return $this->hasColumnOnConnection('default', $table, $column); } @@ -264,7 +265,7 @@ protected function hasColumn(string $table, string $column): bool * Crée un builder pour une connexion et une table spécifiques */ private function makeBuilderFor(string $connection, string $table): Builder - { + { $builder = new Builder($table); $builder->setConnection($this->connections[$connection] ?? $this->db); diff --git a/src/Migration/Runner.php b/src/Migration/Runner.php index 194fdfc..45be317 100644 --- a/src/Migration/Runner.php +++ b/src/Migration/Runner.php @@ -14,10 +14,11 @@ use BlitzPHP\Database\Connection\BaseConnection; use BlitzPHP\Database\DatabaseManager; use RuntimeException; +use Throwable; /** * Exécuteur de migrations - * + * * Cette classe orchestre la découverte, l'ordonnancement et l'exécution * des fichiers de migration, avec support des classes anonymes et nommées. */ @@ -63,7 +64,7 @@ class Runner /** * Callbacks pour les événements * - * @var array> + * @var array> */ protected array $listeners = []; @@ -84,12 +85,12 @@ public function __construct(protected DatabaseManager $dbManager, protected ?str */ public function on(string $event, callable $callback): self { - if (!isset($this->listeners[$event])) { + if (! isset($this->listeners[$event])) { $this->listeners[$event] = []; } - + $this->listeners[$event][] = $callback; - + return $this; } @@ -98,7 +99,7 @@ public function on(string $event, callable $callback): self */ protected function fire(string $event, array $payload = []): void { - if (!isset($this->listeners[$event])) { + if (! isset($this->listeners[$event])) { return; } @@ -111,12 +112,14 @@ protected function fire(string $event, array $payload = []): void * Exécute toutes les migrations en attente * * @param string|null $group Groupe de connexion + * * @return int Nombre de migrations exécutées */ public function latest(?string $group = null): int { - if (!$this->enabled) { + if (! $this->enabled) { $this->fire('process.migrations-disabled'); + return 0; } @@ -124,6 +127,7 @@ public function latest(?string $group = null): int if ($migrations === []) { $this->fire('process.empty-migrations'); + return 0; } @@ -132,8 +136,8 @@ public function latest(?string $group = null): int 'group' => $group, 'start' => $start = microtime(true), ]); - - $batch = $this->history->getLastBatch() + 1; + + $batch = $this->history->getLastBatch() + 1; $executed = 0; foreach ($migrations as $migration) { @@ -158,12 +162,14 @@ public function latest(?string $group = null): int * * @param int $steps Nombre de lots à annuler * @param string|null $group Groupe de connexion + * * @return int Nombre de migrations annulées */ public function rollback(int $steps = 1, ?string $group = null): int { - if (!$this->enabled) { + if (! $this->enabled) { $this->fire('process.migrations-disabled'); + return 0; } @@ -171,6 +177,7 @@ public function rollback(int $steps = 1, ?string $group = null): int if ($batches === []) { $this->fire('process.empty-migrations'); + return 0; } @@ -182,16 +189,17 @@ public function rollback(int $steps = 1, ?string $group = null): int ]); $targetBatch = $steps === 0 ? 0 : (count($batches) - $steps); - $rolledBack = 0; + $rolledBack = 0; for ($i = count($batches) - 1; $i >= $targetBatch; $i--) { $batchMigrations = $this->history->getBatch($batches[$i], 'desc'); foreach ($batchMigrations as $history) { $migration = $this->createMigrationFromHistory($history); - - if (!$migration->path) { + + if (! $migration->path) { $this->fire('migration.skipped', ['migration' => $migration]); + continue; } @@ -215,6 +223,7 @@ public function rollback(int $steps = 1, ?string $group = null): int * Annule toutes les migrations * * @param string|null $group Groupe de connexion + * * @return int Nombre de migrations annulées */ public function reset(?string $group = null): int @@ -226,11 +235,12 @@ public function reset(?string $group = null): int * Réinitialise et réexécute toutes les migrations * * @param string|null $group Groupe de connexion + * * @return array{reset: int, latest: int} Nombre de migrations annulées et exécutées */ public function refresh(?string $group = null): array { - $reset = $this->reset($group); + $reset = $this->reset($group); $latest = $this->latest($group); return ['reset' => $reset, 'latest' => $latest]; @@ -251,10 +261,11 @@ protected function createMigrationFromHistory(object $history): object // Trouver le chemin du fichier $files = $this->findMigrationFiles(); + foreach ($files as $file) { - if ($file->version === $history->version && - $file->migration === $history->migration && - $file->namespace === $history->namespace) { + if ($file->version === $history->version + && $file->migration === $history->migration + && $file->namespace === $history->namespace) { $migration->path = $file->path; break; } @@ -265,30 +276,32 @@ protected function createMigrationFromHistory(object $history): object /** * Récupère les migrations en attente - * - * @return array + * + * @return list */ protected function getPendingMigrations(?string $group): array { - $files = $this->findMigrationFiles(); + $files = $this->findMigrationFiles(); $executed = $this->history->getAll($group); - + $executedMap = []; + foreach ($executed as $item) { - $key = implode('.', [$item->namespace, $item->migration, $item->version]); + $key = implode('.', [$item->namespace, $item->migration, $item->version]); $executedMap[$key] = true; } - return array_filter($files, function($file) use ($executedMap) { + return array_filter($files, static function ($file) use ($executedMap) { $key = implode('.', [$file->namespace, $file->migration, $file->version]); - return !isset($executedMap[$key]); + + return ! isset($executedMap[$key]); }); } /** * Trouve tous les fichiers de migration * - * @return array + * @return list */ public function findMigrationFiles(): array { @@ -308,17 +321,17 @@ public function findMigrationFiles(): array } } - usort($files, fn($a, $b) => strcmp($a->version, $b->version)); + usort($files, static fn ($a, $b) => strcmp($a->version, $b->version)); return $files; } /** * Charge une instance de migration - * + * * @param object $migration Données de la migration * @param bool $fresh Forcer un rechargement frais - * + * * @throws RuntimeException */ protected function loadMigration(object $migration, bool $fresh = false): Migration @@ -326,7 +339,7 @@ protected function loadMigration(object $migration, bool $fresh = false): Migrat $cacheKey = $migration->path . ':' . ($fresh ? 'fresh' : 'cached'); // Retourner l'instance en cache si disponible et pas de rechargement frais - if (!$fresh && isset($this->loaded[$cacheKey])) { + if (! $fresh && isset($this->loaded[$cacheKey])) { return clone $this->loaded[$cacheKey]; } @@ -341,31 +354,31 @@ protected function loadMigration(object $migration, bool $fresh = false): Migrat if ($isAnonymous) { // Mode anonyme : le fichier retourne directement l'instance $instance = require $migration->path; - - if (!$instance instanceof Migration) { + + if (! $instance instanceof Migration) { throw new RuntimeException( - "Le fichier {$migration->path} doit retourner une instance de Migration" + "Le fichier {$migration->path} doit retourner une instance de Migration", ); } } else { // Mode classique : chercher la classe déclarée require_once $migration->path; - + $className = $this->extractClassName($content, $migration->namespace); - - if (!$className || !class_exists($className)) { + + if (! $className || ! class_exists($className)) { throw new RuntimeException( - "Impossible de trouver la classe de migration dans {$migration->path}" + "Impossible de trouver la classe de migration dans {$migration->path}", ); } - + $instance = new $className(); } $instance = $instance->initialize($this->dbManager, $this->db); - + // Mettre en cache pour les appels suivants - if (!$fresh) { + if (! $fresh) { $this->loaded[$migration->path . ':cached'] = $instance; } @@ -380,12 +393,12 @@ protected function extractClassName(string $content, string $namespace): ?string // Chercher "class NomDeClasse extends Migration" if (preg_match('/class\s+([a-zA-Z0-9_]+)\s+extends\s+Migration/', $content, $matches)) { $className = $matches[1]; - + // Si le namespace est vide ou déjà présent if (empty($namespace) || str_starts_with($className, $namespace)) { return $className; } - + return $namespace . '\\Database\\Migrations\\' . $className; } @@ -399,6 +412,7 @@ protected function extractClassName(string $content, string $namespace): ?string * @param string $direction Direction (up|down) * @param string|null $group Groupe * @param int|null $batch Lot (pour up) + * * @return bool Succès ou échec */ protected function runMigration(object $migration, string $direction, ?string $group, ?int $batch = null): bool @@ -412,13 +426,13 @@ protected function runMigration(object $migration, string $direction, ?string $g try { // Pour le rollback, on force un rechargement frais - $fresh = ($direction === 'down'); + $fresh = ($direction === 'down'); $instance = $this->loadMigration($migration, $fresh); // Vérifier si la migration doit être exécutée - if ($direction === 'up' && !$instance->shouldRun()) { + if ($direction === 'up' && ! $instance->shouldRun()) { $this->fire('migration.ignored', ['migration' => $migration]); - + // On marque comme réussie pour ne pas bloquer les suivantes return true; } @@ -429,15 +443,15 @@ protected function runMigration(object $migration, string $direction, ?string $g // On doit créer un transformer pour chaque connexion utilisée $transformers = []; - + foreach ($instance->getBuilders() as $builder) { - $conn = $builder->getConnection(); + $conn = $builder->getConnection(); $connKey = spl_object_hash($conn); - - if (!isset($transformers[$connKey])) { + + if (! isset($transformers[$connKey])) { $transformers[$connKey] = new Transformer($this->dbManager->creator($conn)); } - + $transformers[$connKey]->process($builder); } @@ -448,7 +462,7 @@ protected function runMigration(object $migration, string $direction, ?string $g $migration->migration, $migration->namespace, $group ?? 'default', - $batch + $batch, ); } elseif ($direction === 'down') { $this->removeFromHistory($migration, $group); @@ -460,11 +474,11 @@ protected function runMigration(object $migration, string $direction, ?string $g ]); return true; - } catch (\Throwable $e) { + } catch (Throwable $e) { $this->fire('migration.error', [ 'migration' => $migration, 'direction' => $direction, - 'error' => $e->getMessage(), + 'error' => $e->getMessage(), 'exception' => $e, ]); @@ -478,14 +492,13 @@ protected function runMigration(object $migration, string $direction, ?string $g protected function removeFromHistory(object $migration, ?string $group): void { $history = $this->history->getAll($group); - + foreach ($history as $entry) { - if ($entry->migration === $migration->migration && - $entry->version === $migration->version && - $entry->namespace === $migration->namespace) { - + if ($entry->migration === $migration->migration + && $entry->version === $migration->version + && $entry->namespace === $migration->namespace) { $this->history->remove($entry->id); - + break; } } diff --git a/src/Migration/Transformer.php b/src/Migration/Transformer.php index 4ccc6f1..dd7856d 100644 --- a/src/Migration/Transformer.php +++ b/src/Migration/Transformer.php @@ -53,15 +53,15 @@ public function process(Builder $builder) */ protected function createTable(Builder $builder, bool $ifNotExists = false): void { - $primaryKeyColumns = []; - $autoIncrementColumns = []; + $primaryKeyColumns = []; + $autoIncrementColumns = []; $explicitPrimaryColumns = []; foreach ($builder->getColumns() as $column) { $this->creator->addField([ - $column->name => $this->makeColumn($column) + $column->name => $this->makeColumn($column), ]); - + if ($column->autoIncrement ?? false) { $autoIncrementColumns[] = $column->name; } @@ -81,18 +81,18 @@ protected function createTable(Builder $builder, bool $ifNotExists = false): voi } elseif (count($autoIncrementColumns) > 1) { // Plusieurs auto_increment (cas rare) - on prévient trigger_error( - "Plusieurs colonnes auto_increment détectées. Utilisez primary() pour spécifier la clé primaire.", - E_USER_WARNING + 'Plusieurs colonnes auto_increment détectées. Utilisez primary() pour spécifier la clé primaire.', + E_USER_WARNING, ); } if ($primaryKeyColumns !== []) { $this->creator->addPrimaryKey( - $primaryKeyColumns, - $builder->createIndexName('primary', $primaryKeyColumns) + $primaryKeyColumns, + $builder->createIndexName('primary', $primaryKeyColumns), ); } - + foreach ($builder->getIndexes() as $index) { $this->addIndex($index); } @@ -153,22 +153,21 @@ protected function alterTable(Builder $builder): void } } - - /** * Convertit les index fluides des colonnes en commandes explicites */ protected function addFluentIndexes(Builder $builder): void { $existingIndexes = []; + foreach ($builder->getIndexes() as $index) { - $key = $index->type . ':' . implode(',', $index->columns); + $key = $index->type . ':' . implode(',', $index->columns); $existingIndexes[$key] = true; } - + foreach ($builder->getColumns() as $column) { // Ignorer les colonnes qui n'ont pas d'index - if (!$column->hasFluentIndexes()) { + if (! $column->hasFluentIndexes()) { continue; } @@ -197,13 +196,13 @@ protected function addFluentIndexes(Builder $builder): void $builder->{$indexMethod}($column->name); $existingIndexes[$indexKey] = true; } - + // Cas 2: $value === false (suppression d'index) elseif ($value === false && $column->change) { $dropMethod = 'drop' . ucfirst($indexMethod); $builder->{$dropMethod}([$column->name]); } - + // Cas 3: $value est une chaîne (nom d'index explicite) elseif (is_string($value)) { $builder->{$indexMethod}($column->name, $value); @@ -241,27 +240,27 @@ protected function addIndex(Index $index): void protected function addForeignKey(ForeignKey $fk): void { $onDelete = match (true) { - ($fk->cascadeOnDelete ?? null) === true => 'cascade', + ($fk->cascadeOnDelete ?? null) === true => 'cascade', ($fk->restrictOnDelete ?? null) === true => 'restrict', - ($fk->nullOnDelete ?? null) === true => 'set null', + ($fk->nullOnDelete ?? null) === true => 'set null', ($fk->noActionOnDelete ?? null) === true => 'no action', - default => $fk->onDelete ?? '' - }; + default => $fk->onDelete ?? '', + }; $onUpdate = match (true) { - ($fk->cascadeOnUpdate ?? null) === true => 'cascade', + ($fk->cascadeOnUpdate ?? null) === true => 'cascade', ($fk->restrictOnUpdate ?? null) === true => 'restrict', - ($fk->nullOnUpdate ?? null) === true => 'set null', + ($fk->nullOnUpdate ?? null) === true => 'set null', ($fk->noActionOnUpdate ?? null) === true => 'no action', - default => $fk->onUpdate ?? '' + default => $fk->onUpdate ?? '', }; - + $this->creator->addForeignKey( $fk->columns, $fk->on, $fk->references ?? $fk->columns, $onUpdate, $onDelete, - $fk->name ?? '' + $fk->name ?? '', ); } @@ -298,7 +297,7 @@ private function makeColumn(Column $column): array if (is_array($type)) { $attributes['type'] = $type[0]; $attributes['constraint'] = $type[1] ?? null; - } else if (str_contains($type, '|')) { + } elseif (str_contains($type, '|')) { $parts = explode('|', $type); $attributes['type'] = $parts[$column->primary === true ? 1 : 0]; } elseif (str_contains($type, '{precision}')) { @@ -326,7 +325,7 @@ private function makeColumn(Column $column): array if ($column->useCurrent === true) { $attributes['default'] = new Expression('CURRENT_TIMESTAMP'); - } elseif (isset($column->default)) { + } elseif (isset($column->default)) { $attributes['default'] = $column->type === 'boolean' ? (int) $column->default : $column->default; } diff --git a/src/Model.php b/src/Model.php index eb5c50a..689d23f 100644 --- a/src/Model.php +++ b/src/Model.php @@ -44,96 +44,96 @@ abstract class Model implements RepositoryInterface * Nom de la table */ protected string $table = ''; - + /** * Clé primaire */ protected string $primaryKey = 'id'; - + /** * Type de retour par défaut */ protected string $returnType = 'array'; - + /** * Type de retour temporaire */ protected string $tempReturnType; - + /** * Dernier ID inséré */ protected int|string $lastInsertId = 0; - + /** * Groupe de connexion */ protected ?string $group = null; - + /** * Utiliser l'auto-incrément */ protected bool $useAutoIncrement = true; - + /** * Format des dates (Autorisé: 'datetime', 'date', 'int') */ protected string $dateFormat = 'datetime'; - + /** * Utiliser les soft deletes */ protected bool $useSoftDeletes = false; - + /** * Champ de suppression logique */ protected string $deletedField = 'deleted_at'; - + /** * Temporaire pour soft deletes */ protected bool $tempUseSoftDeletes; - + /** * Utiliser les timestamps */ protected bool $useTimestamps = false; - + /** * Champ de création */ protected string $createdField = 'created_at'; - + /** * Champ de mise à jour */ protected string $updatedField = 'updated_at'; - + /** * Champs autorisés pour l'assignation de masse - * + * * @var list */ protected array $fillable = []; - + /** * Champs protégés (non assignables) - * + * * @var list */ protected array $guarded = ['id']; - + /** * Règles de validation - * + * * @var array */ protected array $rules = []; - + /** * Messages de validation personnalisés - * + * * @var array */ protected array $messages = []; @@ -142,24 +142,24 @@ abstract class Model implements RepositoryInterface * Le nombre de données à renvoyer pour la pagination. */ protected int $perPage = 15; - + /** * Connexion à la base de données */ protected BaseConnection $db; - + /** * Query Builders par table - * + * * @var array */ protected array $builders = []; - + /** * Builder actuel */ protected ?BaseBuilder $currentBuilder = null; - + /** * Alias de la table actuellement utilisée */ @@ -169,17 +169,17 @@ abstract class Model implements RepositoryInterface * Erreurs de validation */ protected ?ErrorBag $errors = null; - + /** * Activer les callbacks */ protected bool $allowCallbacks = true; - + /** * Temporaire pour callbacks */ protected bool $tempAllowCallbacks; - + /** * Callbacks disponibles */ @@ -197,23 +197,23 @@ abstract class Model implements RepositoryInterface 'beforeBulkUpdate', 'afterBulkUpdate', ]; - + /** * Callbacks enregistrés */ protected array $callbacks = []; - + public function __construct(protected ConnectionResolverInterface $resolver, ?ConnectionInterface $db = null) { $this->db = $db ?: $this->resolver->connection($this->group); - + $this->tempReturnType = $this->returnType; $this->tempUseSoftDeletes = $this->useSoftDeletes; $this->tempAllowCallbacks = $this->allowCallbacks; - + $this->initializeCallbacks(); } - + /** * Initialise les callbacks */ @@ -225,7 +225,7 @@ protected function initializeCallbacks(): void } } } - + /** * Sélectionne une table spécifique pour les prochaines opérations */ @@ -237,27 +237,27 @@ public function table(string $table): static $table = $matches[1]; $alias = $matches[2]; } - + $key = $alias ?: $table; - - if (!isset($this->builders[$key])) { + + if (! isset($this->builders[$key])) { $this->builders[$key] = $this->db->table($table); } - + $this->currentBuilder = $this->builders[$key]->reset(); $this->currentAlias = $key; - + return $this; } - + /** - * {@inheritdoc} + * {@inheritDoc} */ public function query(): BaseBuilder { return $this->builder(); } - + /** * Récupère le Query Builder pour une table spécifique ou la table par défaut */ @@ -271,70 +271,70 @@ public function builder(?string $table = null): BaseBuilder return $this->currentBuilder; } - + // Extraire l'alias si présent $alias = null; if (preg_match('/^(.+?)(?:\s+as\s+|\s+)(\w+)$/i', $table, $matches)) { $table = $matches[1]; $alias = $matches[2]; } - + $key = $alias ?: $table; - - if (!isset($this->builders[$key])) { + + if (! isset($this->builders[$key])) { $this->builders[$key] = $this->db->table($table); } - + return $this->builders[$key]; } - + /** - * {@inheritdoc} + * {@inheritDoc} * * @param array|int|string|null $id Une clé primaire ou un tableau de clés primaires * - * @return ($id is int|string ? array|object|null : Collection) + * @return ($id is int|string ? array|object|null : Collection) */ public function find($id = null): mixed { $singleton = is_numeric($id) || is_string($id); - + $eventData = $this->fire('beforeFind', [ 'id' => $id, 'method' => 'find', 'singleton' => $singleton, ]); - + if ($eventData['cancelled'] ?? false) { return $eventData['data'] ?? null; } - + $builder = $this->query(); - + $this->applySoftDeleteCondition($builder); - + if ($id !== null && $id !== 0 && $id !== '0') { $builder->whereIn($this->primaryKey, (array) $id) - ->when($singleton, fn($b) => $b->limit(1)); + ->when($singleton, static fn ($b) => $b->limit(1)); } - + $results = $builder->collect($this->tempReturnType); - $data = $singleton ? $results->first() : $results; - + $data = $singleton ? $results->first() : $results; + $eventData = $this->fire('afterFind', [ 'id' => $id, 'data' => $data, 'method' => 'find', 'singleton' => $singleton, ]); - + $this->resetTemporaryStates(); - + return $eventData['data'] ?? $data; } - + /** - * {@inheritdoc} + * {@inheritDoc} */ public function findAll(?int $limit = null, int $offset = 0): Collection { @@ -344,21 +344,21 @@ public function findAll(?int $limit = null, int $offset = 0): Collection 'offset' => $offset, 'singleton' => false, ]); - + if ($eventData['cancelled'] ?? false) { return new Collection($eventData['data'] ?? []); } - + $builder = $this->query(); - + $this->applySoftDeleteCondition($builder); - + if ($limit !== null) { $builder->limit($limit, $offset); } - + $results = $builder->collect($this->tempReturnType); - + $eventData = $this->fire('afterFind', [ 'data' => $results, 'limit' => $limit, @@ -366,165 +366,162 @@ public function findAll(?int $limit = null, int $offset = 0): Collection 'method' => 'findAll', 'singleton' => false, ]); - + $this->resetTemporaryStates(); - + return $eventData['data'] ?? $results; } - + /** - * {@inheritdoc} + * {@inheritDoc} */ public function create(array|object $data, bool $returnId = true) { $this->lastInsertId = 0; - + // Filtrer les données selon fillable/guarded $data = $this->filterFillable($data); - + // Valider les données if (! $this->validate($data, 'create')) { return false; } - + // Ajouter les timestamps $data = $this->addTimestamps($data, 'create'); - + $eventData = $this->fire('beforeInsert', ['data' => $data]); - + if ($eventData['cancelled'] ?? false) { return false; } - + $data = $eventData['data'] ?? $data; - + $builder = $this->query(); - + if ($returnId) { $result = $builder->insertGetId($data); $this->lastInsertId = is_numeric($result) ? (int) $result : 0; } else { $result = $builder->insert($data); } - + $this->fire('afterInsert', [ - 'id' => $this->lastInsertId, - 'data' => $data, + 'id' => $this->lastInsertId, + 'data' => $data, 'result' => $result, ]); - + $this->resetTemporaryStates(); - - if (!$result) { + + if (! $result) { return false; } - + return $returnId ? $this->lastInsertId : $result; } - + /** - * {@inheritdoc} - * - * @param array|int|string|null $id - * + * {@inheritDoc} + * + * @param array|int|string $id + * * @return bool|mixed */ - public function modify($id = null, array|object $data) + public function modify($id, array|object $data) { - $id = $id ?: $this->primaryKeyValue; $id = $id ?: $this->idValue($data); - + // Filtrer les données selon fillable/guarded $data = $this->filterFillable($data); - + // Ne pas permettre la mise à jour de la clé primaire unset($data[$this->primaryKey]); - + if ($data === []) { return true; // Rien à mettre à jour } - + // Valider les données - if (!$this->validate($data, 'update')) { + if (! $this->validate($data, 'update')) { return false; } - + // Ajouter les timestamps $data = $this->addTimestamps($data, 'update'); - + $eventData = $this->fire('beforeUpdate', [ 'id' => $id, 'data' => $data, ]); - + if ($eventData['cancelled'] ?? false) { return false; } - + $data = $eventData['data'] ?? $data; $id = $eventData['id'] ?? $id; - + $builder = $this->query(); - - if (!in_array($id, [null, '', 0, '0', []], true)) { + + if (! in_array($id, [null, '', 0, '0', []], true)) { $ids = is_array($id) ? $id : [$id]; $builder->whereIn($this->primaryKey, $ids); } - + if ($builder->wheres === []) { throw new DatabaseException('Updates require a WHERE clause for safety.'); } - + $result = $builder->update($data); - + $this->fire('afterUpdate', [ 'id' => $id, 'data' => $data, 'result' => $result, ]); - + $this->resetTemporaryStates(); - + return (bool) $result; } - + /** - * {@inheritdoc} - * - * @param array|int|string|null $id - * + * {@inheritDoc} + * + * @param array|int|string $id + * * @return bool|mixed */ - public function remove($id = null, bool $force = false): bool + public function remove($id, bool $force = false): bool { - $id = $id ?: $this->primaryKeyValue; - $eventData = $this->fire('beforeDelete', [ 'id' => $id, 'force' => $force, ]); - + if ($eventData['cancelled'] ?? false) { return false; } - - $id = $eventData['id'] ?? $id; + + $id = $eventData['id'] ?? $id; $force = $eventData['force'] ?? $force; - + $builder = $this->query(); - - if (!in_array($id, [null, '', 0, '0', []], true)) { + + if (! in_array($id, [null, '', 0, '0', []], true)) { $ids = is_array($id) ? $id : [$id]; $builder->whereIn($this->primaryKey, $ids); } - + if ($builder->wheres === []) { throw new DatabaseException('Deletes require a WHERE clause for safety.'); } - - if ($this->useSoftDeletes && !$force) { + + if ($this->useSoftDeletes && ! $force) { $this->applySoftDeleteCondition($builder); - + $set = [$this->deletedField => $this->freshTimestamp()]; if ($this->useTimestamps && $this->updatedField !== '') { $set[$this->updatedField] = $set[$this->deletedField]; @@ -534,27 +531,27 @@ public function remove($id = null, bool $force = false): bool } else { $result = $builder->delete(); } - + $this->fire('afterDelete', [ 'id' => $id, 'result' => $result, 'force' => $force, ]); - + $this->resetTemporaryStates(); - + return $result > 0; } - + /** * Purge définitivement les éléments supprimés */ public function purge(): int { - if (!$this->useSoftDeletes) { + if (! $this->useSoftDeletes) { return 0; } - + return $this->query() ->whereNotNull($this->deletedField) ->delete(); @@ -578,16 +575,16 @@ public function save(array|object $data): bool return $response; } - + /** * Pagination - * + * * @return array{ - * data: Collection, + * data: Collection, * pagination: array{ * total: int, - * per_page: int, - * current_page: int, + * per_page: int, + * current_page: int, * from: int, * to: int * } @@ -598,13 +595,13 @@ public function paginate(?int $limit = null, ?int $page = null, ?int $total = nu $page = max((int) $page, 1); $limit = $limit ?: $this->perPage; $offset = ($page - 1) * $limit; - + $total = $total ?: $this->countAllResults(false); - + $data = $this->limit($limit, $offset)->collect($this->tempReturnType); - + $this->resetTemporaryStates(); - + return [ 'data' => $data, 'pagination' => [ @@ -617,51 +614,51 @@ public function paginate(?int $limit = null, ?int $page = null, ?int $total = nu ], ]; } - + /** * Traitement par lots */ public function chunk(int $size, Closure $callback): bool { - return $this->query()->chunk($size, function(Collection $rows, int $page) use ($callback) { + return $this->query()->chunk($size, function (Collection $rows, int $page) use ($callback) { if (class_exists($this->tempReturnType)) { $rows = $rows->mapInto($this->tempReturnType); } - + $this->resetTemporaryStates(); - + return $callback($rows, $page); }); } - + /** * Compte tous les résultats */ public function countAllResults(bool $reset = true): int { $builder = $this->query(); - + $this->applySoftDeleteCondition($builder); - + $count = $builder->countAllResults(); - + if ($reset) { $this->resetTemporaryStates(); } - + return (int) $count; } - + /** * Active temporairement les soft deletes */ public function withDeleted(bool $enabled = true): static { - $this->tempUseSoftDeletes = !$enabled; + $this->tempUseSoftDeletes = ! $enabled; return $this; } - + /** * Ne récupère que les éléments supprimés */ @@ -670,39 +667,39 @@ public function onlyDeleted(): static $this->tempUseSoftDeletes = false; $this->query()->whereNotNull($this->deletedField); - + return $this; } - + /** * Définit le type de retour */ public function asArray(): static { $this->tempReturnType = 'array'; - + return $this; } - + /** * Définit le type de retour comme objet - * + * * @param 'object'|class-string $class */ public function asObject(string $class = 'object'): static { $this->tempReturnType = $class; - + return $this; } - + /** * Active/désactive les callbacks temporairement */ - public function allowCallbacks(): static + public function allowCallbacks(bool $state): static { - $this->tempAllowCallbacks = false; - + $this->tempAllowCallbacks = $state; + return $this; } @@ -713,7 +710,7 @@ public function withoutCallbacks(): static { return $this->allowCallbacks(false); } - + /** * Applique la condition de soft delete */ @@ -723,39 +720,39 @@ protected function applySoftDeleteCondition(BaseBuilder $builder): void $builder->whereNull($this->deletedField); } } - + /** * Ajoute les timestamps */ protected function addTimestamps(array $data, string $action): array { - if (!$this->useTimestamps) { + if (! $this->useTimestamps) { return $data; } - + $timestamp = $this->freshTimestamp(); - - if ($action === 'create' && $this->createdField && !isset($data[$this->createdField])) { + + if ($action === 'create' && $this->createdField && ! isset($data[$this->createdField])) { $data[$this->createdField] = $timestamp; } - - if ($this->updatedField && !isset($data[$this->updatedField])) { + + if ($this->updatedField && ! isset($data[$this->updatedField])) { $data[$this->updatedField] = $timestamp; } - + return $data; } - + /** * Timestamp formaté */ - protected function freshTimestamp(): string|int + protected function freshTimestamp(): int|string { $now = Date::now(); - - return match($this->dateFormat) { - 'int' => $now->getTimestamp(), - 'date' => $now->format('Y-m-d'), + + return match ($this->dateFormat) { + 'int' => $now->getTimestamp(), + 'date' => $now->format('Y-m-d'), default => $now->format('Y-m-d H:i:s'), }; } @@ -793,7 +790,7 @@ protected function shouldUpdate(array|object $data): bool return $this->where($this->primaryKey, $id)->countAllResults() === 1; } - + /** * Filtre les données selon fillable/guarded */ @@ -806,18 +803,18 @@ protected function filterFillable(array|object $data): array if (is_object($data) && ! $data instanceof stdClass) { $data = $this->objectToArray($data); } - + if (is_object($data)) { $data = (array) $data; } if ($this->fillable !== []) { return array_intersect_key($data, array_flip($this->fillable)); - } + } if ($this->guarded !== []) { return array_diff_key($data, array_flip($this->guarded)); } - + return $data; } @@ -836,6 +833,7 @@ protected function objectToArray(object $object): array $props = $mirror->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED); $properties = []; + foreach ($props as $prop) { $properties[$prop->getName()] = $prop->getValue($object); } @@ -843,7 +841,7 @@ protected function objectToArray(object $object): array return $properties; } - + /** * Validation des données */ @@ -852,34 +850,34 @@ protected function validate(array $data, string $action): bool if ($this->rules === []) { return true; } - + // Si un validateur est disponible if (class_exists(Validator::class)) { $validator = Validator::make($data, $this->rules, $this->messages); - + if ($validator->fails()) { $this->errors = $validator->errors(); return false; } } - + return true; } - + /** * Déclenche un événement */ protected function fire(string $event, array $payload = []): array { - if (!$this->tempAllowCallbacks || !isset($this->callbacks[$event])) { + if (! $this->tempAllowCallbacks || ! isset($this->callbacks[$event])) { return $payload; } - + foreach ($this->callbacks[$event] as $callback) { if (is_string($callback) && method_exists($this, $callback)) { $result = $this->{$callback}($payload); - + if (is_array($result)) { $payload = $result; } elseif ($result === false) { @@ -888,10 +886,10 @@ protected function fire(string $event, array $payload = []): array } } } - + return $payload; } - + /** * Réinitialise les états temporaires */ @@ -901,7 +899,7 @@ protected function resetTemporaryStates(): void $this->tempUseSoftDeletes = $this->useSoftDeletes; $this->tempAllowCallbacks = $this->allowCallbacks; } - + /** * Magic getter */ @@ -910,16 +908,16 @@ public function __get(string $name) if (property_exists($this, $name)) { return $this->{$name}; } - + if (isset($this->db->{$name})) { return $this->db->{$name}; } - + $builder = $this->builder(); - + return $builder->{$name} ?? null; } - + /** * Magic isset */ @@ -928,14 +926,14 @@ public function __isset(string $name): bool if (property_exists($this, $name)) { return true; } - + if (isset($this->db->{$name})) { return true; } - + return isset($this->builder()->{$name}); } - + /** * Magic call pour proxy vers le Query Builder */ @@ -944,20 +942,20 @@ public function __call(string $name, array $arguments) // Méthodes du Query Builder if (method_exists($this->builder(), $name)) { $result = $this->builder()->{$name}(...$arguments); - + // Si le résultat est une instance du builder, retourner $this pour la fluidité if ($result instanceof BaseBuilder) { return $this; } - + return $result; } - + // Méthodes de la connexion if (method_exists($this->db, $name)) { return $this->db->{$name}(...$arguments); } - + throw new BadMethodCallException("Method {$name} not found in " . static::class); } } diff --git a/src/Providers/DatabaseProvider.php b/src/Providers/DatabaseProvider.php index 3e5df18..98eaf83 100644 --- a/src/Providers/DatabaseProvider.php +++ b/src/Providers/DatabaseProvider.php @@ -26,10 +26,10 @@ class DatabaseProvider extends AbstractProvider public static function definitions(): array { return [ - 'database' => static fn () => Services::database(), - DatabaseManager::class => static fn () => Services::dbManager(), - ConnectionResolverInterface::class => static fn (ContainerInterface $container) => $container->get(DatabaseManager::class), - ConnectionInterface::class => static fn (ConnectionResolverInterface $resolver) => $resolver->connect(), + 'database' => static fn () => Services::database(), + DatabaseManager::class => static fn () => Services::dbManager(), + ConnectionResolverInterface::class => static fn (ContainerInterface $container) => $container->get(DatabaseManager::class), + ConnectionInterface::class => static fn (ConnectionResolverInterface $resolver) => $resolver->connect(), ]; } } diff --git a/src/Query/Expression.php b/src/Query/Expression.php index f91d479..c155f52 100644 --- a/src/Query/Expression.php +++ b/src/Query/Expression.php @@ -42,4 +42,4 @@ public function with(string $newSql): static return $new; } -} \ No newline at end of file +} diff --git a/src/Query/Result.php b/src/Query/Result.php index cc515ec..842b37c 100644 --- a/src/Query/Result.php +++ b/src/Query/Result.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace BlitzPHP\Database\Query; use BadMethodCallException; @@ -15,7 +24,7 @@ class Result implements ResultInterface { /** * Details de la requete - * + * * @var array{num_rows: int, affected_rows: int, insert_id: int} */ private array $details = [ @@ -38,10 +47,10 @@ class Result implements ResultInterface ]; private array $proxy = [ - 'all' => 'get', - 'one' => 'first', + 'all' => 'get', + 'one' => 'first', 'columnCount' => 'countColumn', - 'lastId' => 'insertID', + 'lastId' => 'insertID', ]; public function __construct(protected BaseConnection $db, protected PDOStatement $statement, protected bool $success = true) @@ -60,7 +69,7 @@ public function sql(): string /** * {@inheritDoc} */ - public function successful(): bool + public function successful(): bool { return $this->success; } @@ -68,7 +77,7 @@ public function successful(): bool /** * Détermine si la requête est une requête qui écrit des données en BD */ - public function isWritableQuery(): bool + public function isWritableQuery(): bool { return Utils::isWritableSql($this->sql()); } @@ -78,18 +87,18 @@ public function isWritableQuery(): bool */ public function first(int|string $type = PDO::FETCH_OBJ): mixed { - if (in_array($type, ['array', PDO::FETCH_ASSOC])) { + if (in_array($type, ['array', PDO::FETCH_ASSOC], true)) { return $this->fetchAssoc(); } - if (in_array($type, ['object', PDO::FETCH_OBJ])) { + if (in_array($type, ['object', PDO::FETCH_OBJ], true)) { return $this->fetchObject(); } if (is_string($type) && class_exists($type)) { return $this->fetchObject($type); } - + return null; } @@ -134,7 +143,7 @@ public function previous(int|string $type = PDO::FETCH_OBJ): mixed /** * {@inheritDoc} */ - public function row(int $index, null|int|string $type = PDO::FETCH_OBJ): mixed + public function row(int $index, int|string|null $type = PDO::FETCH_OBJ): mixed { $records = $this->result($type); @@ -166,7 +175,7 @@ public function columnNames(): array $names = []; for ($i = 0; $i < $count; $i++) { - $column = $this->statement->getColumnMeta($i); + $column = $this->statement->getColumnMeta($i); $names[] = $column['name'] ?? "column_{$i}"; } @@ -185,18 +194,18 @@ public function columnData(): array } $count = $this->countColumn(); - $data = []; + $data = []; for ($i = 0; $i < $count; $i++) { $meta = $this->statement->getColumnMeta($i); - + $column = new stdClass(); $column->name = $meta['name'] ?? "column_{$i}"; $column->type = $meta['native_type'] ?? 'unknown'; $column->length = $meta['len'] ?? null; $column->precision = $meta['precision'] ?? null; $column->flags = $meta['flags'] ?? []; - + $data[] = $column; } @@ -211,7 +220,7 @@ public function columnData(): array public function get(int|string $type = PDO::FETCH_OBJ): array { $data = is_string($type) ? $this->resultClass($type) : $this->result($type); - + $this->details['num_rows'] = count($data); return $data; @@ -228,9 +237,9 @@ public function result(int $mode = PDO::FETCH_OBJ, ?string $className = null): a } else { $this->statement->setFetchMode($mode); } - + $data = $this->statement->fetchAll(); - + $this->statement->closeCursor(); return $data; @@ -285,7 +294,7 @@ protected function fetchObject(string $className = 'stdClass') if ($this->isWritableQuery()) { return null; } - + $this->statement->setFetchMode(PDO::FETCH_CLASS, $className); return $this->statement->fetch(); @@ -342,4 +351,4 @@ public function __call(string $name, array $arguments): mixed throw new BadMethodCallException("Méthode {$name} non trouvée"); } -} \ No newline at end of file +} diff --git a/src/Seeder/Factory.php b/src/Seeder/Factory.php index e3ca025..ed2fb03 100644 --- a/src/Seeder/Factory.php +++ b/src/Seeder/Factory.php @@ -16,7 +16,7 @@ /** * Générateur de configurations pour les seeders - * + * * @mixin FakerGenerator */ class Factory @@ -71,6 +71,7 @@ public function optional(float $weight = 0.5, mixed $default = null, mixed $valu // Si pas de valeur fournie, on utilisera une closure return ['optional', $weight, $default]; } + return ['optional', $weight, $default, $value]; } diff --git a/src/Seeder/Seed.php b/src/Seeder/Seed.php index 6d6e5ba..8cd1746 100644 --- a/src/Seeder/Seed.php +++ b/src/Seeder/Seed.php @@ -34,14 +34,14 @@ class Seed /** * Définition des colonnes - * + * * @var array */ protected array $columns = []; /** * Closures pour les cas complexes - * + * * @var array */ protected array $closures = []; @@ -68,14 +68,14 @@ class Seed /** * Callbacks avant insertion - * + * * @var list */ protected array $beforeInsertCallbacks = []; /** * Callbacks après insertion - * + * * @var list */ protected array $afterInsertCallbacks = []; @@ -92,9 +92,9 @@ class Seed /** * Constructeur - * - * @param BaseConnection $db Connexion à la base de données - * @param string $table Nom de la table + * + * @param BaseConnection $db Connexion à la base de données + * @param string $table Nom de la table * @param FakerGenerator $faker Générateur Faker */ public function __construct(protected BaseConnection $db, protected string $table, protected FakerGenerator $faker) @@ -120,7 +120,7 @@ public function columns(array $columns): self public function column(string $name, mixed $definition): self { // Si c'est une closure, on la garde à part pour la flexibilité - if (is_callable($definition) && !is_string($definition)) { + if (is_callable($definition) && ! is_string($definition)) { $this->closures[$name] = $definition; } else { $this->columns[$name] = $definition; @@ -159,7 +159,7 @@ public function data(array $data): self } $firstRow = reset($data); - + if (! is_array($firstRow)) { $this->rawData = [$data]; } else { @@ -181,25 +181,25 @@ public function truncate(bool $truncate = true): self /** * Ajoute un callback avant chaque insertion - * - * @param Closure(array $data, int $index, ?int $insertId): array|null|void $callback + * + * @param Closure(array $data, int $index, ?int $insertId): array|void|null $callback */ public function beforeInsert(callable $callback): self { $this->beforeInsertCallbacks[] = $callback; - + return $this; } /** * Ajoute un callback après chaque insertion - * - * @param Closure(array $data, int $index, ?int $insertId): array|null|void $callback + * + * @param Closure(array $data, int $index, ?int $insertId): array|void|null $callback */ public function afterInsert(callable $callback): self { $this->afterInsertCallbacks[] = $callback; - + return $this; } @@ -227,23 +227,26 @@ protected function insertRawData(): void $columns = array_keys(reset($this->rawData)); $chunks = array_chunk($this->rawData, $this->bulkSize); - + foreach ($chunks as $chunkIndex => $chunk) { $data = []; + foreach ($chunk as $rowIndex => $row) { $preparedRow = []; + foreach ($columns as $column) { $preparedRow[$column] = $row[$column] ?? null; } - + $this->executeCallbacks($this->beforeInsertCallbacks, $preparedRow, $rowIndex); $data[] = $preparedRow; } - + $this->builder->bulkInsert($data); - + // Callbacks after (avec l'ID du premier élément comme approximation) $firstId = $this->db->lastId(); + foreach ($data as $index => $row) { $this->executeCallbacks($this->afterInsertCallbacks, $row, $index, $firstId + $index); } @@ -255,44 +258,45 @@ protected function insertRawData(): void */ protected function generateAndInsertData(): void { - // Résoudre les dépendances une seule fois - $dependencies = $this->resolveDependencies(); - - // Générer les données par lots - $batches = (int) ceil($this->rowCount / $this->bulkSize); - - for ($batch = 0; $batch < $batches; $batch++) { - $batchData = []; - $start = $batch * $this->bulkSize; - $end = min($start + $this->bulkSize, $this->rowCount); - - for ($i = $start; $i < $end; $i++) { - $row = []; - - // Traiter les configurations d'abord (plus rapides) - foreach ($this->columns as $column => $definition) { - $row[$column] = $this->resolveConfig($definition, $dependencies); - } - - // Traiter les closures ensuite (plus flexibles) - foreach ($this->closures as $column => $closure) { - $row[$column] = $closure($this->faker, $dependencies, $i); - } - - $this->executeCallbacks($this->beforeInsertCallbacks, $row, $i); - - $batchData[] = $row; - } - - $this->builder->bulkInsert($batchData); - - // Callbacks after (avec approximation des IDs) - $firstId = $this->db->lastId(); - foreach ($batchData as $offset => $row) { - $this->executeCallbacks($this->afterInsertCallbacks, $row, $start + $offset, $firstId + $offset); - } - } - } + // Résoudre les dépendances une seule fois + $dependencies = $this->resolveDependencies(); + + // Générer les données par lots + $batches = (int) ceil($this->rowCount / $this->bulkSize); + + for ($batch = 0; $batch < $batches; $batch++) { + $batchData = []; + $start = $batch * $this->bulkSize; + $end = min($start + $this->bulkSize, $this->rowCount); + + for ($i = $start; $i < $end; $i++) { + $row = []; + + // Traiter les configurations d'abord (plus rapides) + foreach ($this->columns as $column => $definition) { + $row[$column] = $this->resolveConfig($definition, $dependencies); + } + + // Traiter les closures ensuite (plus flexibles) + foreach ($this->closures as $column => $closure) { + $row[$column] = $closure($this->faker, $dependencies, $i); + } + + $this->executeCallbacks($this->beforeInsertCallbacks, $row, $i); + + $batchData[] = $row; + } + + $this->builder->bulkInsert($batchData); + + // Callbacks after (avec approximation des IDs) + $firstId = $this->db->lastId(); + + foreach ($batchData as $offset => $row) { + $this->executeCallbacks($this->afterInsertCallbacks, $row, $start + $offset, $firstId + $offset); + } + } + } /** * Résout une configuration @@ -301,17 +305,17 @@ protected function resolveConfig(mixed $config, array $dependencies): mixed { // Cache pour les appels répétés $cacheKey = is_array($config) ? md5(serialize($config)) : null; - + if ($cacheKey && isset($this->cache[$cacheKey])) { return $this->cache[$cacheKey]; } $result = match (true) { // Valeur simple (pas de configuration) - !is_array($config) => $config, + ! is_array($config) => $config, // Configuration standard - default => $this->resolveArrayConfig($config, $dependencies) + default => $this->resolveArrayConfig($config, $dependencies), }; if ($cacheKey) { @@ -329,11 +333,11 @@ protected function resolveArrayConfig(array $config, array $dependencies): mixed $type = $config[0] ?? null; return match ($type) { - 'faker' => $this->resolveFaker($config), + 'faker' => $this->resolveFaker($config), 'faker:unique' => $this->resolveUniqueFaker($config), - 'relation' => $this->resolveRelation($config, $dependencies), - 'optional' => $this->resolveOptional($config, $dependencies), - default => $config // Retourne tel quel si non reconnu + 'relation' => $this->resolveRelation($config, $dependencies), + 'optional' => $this->resolveOptional($config, $dependencies), + default => $config, // Retourne tel quel si non reconnu }; } @@ -342,10 +346,10 @@ protected function resolveArrayConfig(array $config, array $dependencies): mixed */ protected function resolveFaker(array $config): mixed { - $method = $config[1] ?? null; + $method = $config[1] ?? null; $arguments = $config[2] ?? []; - if (!$method) { + if (! $method) { throw SeederException::unspecifiedFakerMethod(); } @@ -357,11 +361,11 @@ protected function resolveFaker(array $config): mixed */ protected function resolveUniqueFaker(array $config): mixed { - $method = $config[1] ?? null; - $arguments = $config[2] ?? []; + $method = $config[1] ?? null; + $arguments = $config[2] ?? []; $maxRetries = $config[3] ?? 10000; - if (!$method) { + if (! $method) { throw SeederException::unspecifiedFakerMethod(); } @@ -373,14 +377,15 @@ protected function resolveUniqueFaker(array $config): mixed */ protected function resolveRelation(array $config, array $dependencies): mixed { - $table = $config[1] ?? null; + $table = $config[1] ?? null; $column = $config[2] ?? 'id'; - if (!$table || !isset($dependencies[$table])) { + if (! $table || ! isset($dependencies[$table])) { throw SeederException::relationTableNotFound($table); } $values = $dependencies[$table]; + return $values[array_rand($values)]; } @@ -389,9 +394,9 @@ protected function resolveRelation(array $config, array $dependencies): mixed */ protected function resolveOptional(array $config, array $dependencies): mixed { - $weight = $config[1] ?? 0.5; + $weight = $config[1] ?? 0.5; $default = $config[2] ?? null; - $value = $config[3] ?? null; + $value = $config[3] ?? null; if (mt_rand() / mt_getrandmax() <= $weight) { if ($value === null) { @@ -399,6 +404,7 @@ protected function resolveOptional(array $config, array $dependencies): mixed // ou on retourne null return null; } + return $this->resolveConfig($value, $dependencies); } @@ -443,6 +449,8 @@ protected function getTableValues(string $table, string $column): array /** * Exécute les callbacks + * + * @param mixed|null $insertId */ protected function executeCallbacks(array $callbacks, array &$data, int $index, $insertId = null): void { diff --git a/src/Seeder/Seeder.php b/src/Seeder/Seeder.php index 1cbe188..334d277 100644 --- a/src/Seeder/Seeder.php +++ b/src/Seeder/Seeder.php @@ -46,7 +46,7 @@ abstract class Seeder /** * Seeders appelés - * + * * @var list */ protected array $called = []; @@ -63,7 +63,7 @@ abstract class Seeder /** * Constructeur - * + * * @param BaseConnection $db Connexion à la base de données */ public function __construct(BaseConnection $db) @@ -98,9 +98,9 @@ public function setSilent(bool $silent): self */ public function setLocale(string $locale): self { - $this->locale = $locale; + $this->locale = $locale; $this->factory = new Factory($locale); - + return $this; } @@ -114,7 +114,7 @@ public function getLocale(): string /** * Récupère les seeders appelés - * + * * @return list */ public function getCalled(): array @@ -130,7 +130,7 @@ public function __get(string $name): mixed if ($name === 'faker') { return $this->factory; } - + throw SeederException::propertyNotFound($name); } @@ -144,7 +144,7 @@ abstract public function run(): void; */ protected function table(string $table): Seed { - if (!isset($this->seeds[$table])) { + if (! isset($this->seeds[$table])) { $this->seeds[$table] = new Seed($this->db, $table, $this->factory->faker); } @@ -158,12 +158,12 @@ protected function call(array|string $seeders): self { foreach ((array) $seeders as $seeder) { $seeder = $this->resolve($seeder); - + $seeder->setSilent($this->silent) - ->setCommand($this->command) - ->setLocale($this->locale) - ->run(); - + ->setCommand($this->command) + ->setLocale($this->locale) + ->run(); + $this->called[] = $seeder::class; } @@ -175,10 +175,10 @@ protected function call(array|string $seeders): self */ protected function resolve(string $class): self { - if (!class_exists($class)) { + if (! class_exists($class)) { throw SeederException::seederClassDoesNotExist($class); } - + return new $class($this->db); } @@ -207,7 +207,7 @@ protected function output(string $message, string $type = 'info'): void if ($this->command) { $this->command->{$type}($message); - } else if(defined('STDOUT')) { + } elseif (defined('STDOUT')) { fwrite(STDOUT, $message . PHP_EOL); } else { echo $message . PHP_EOL; diff --git a/src/Utils.php b/src/Utils.php index f67b93e..79e57f2 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -1,4 +1,13 @@ - + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ namespace BlitzPHP\Database; @@ -13,15 +22,13 @@ class Utils '<=', '>=', '<>', '!=', '<', '>', '=', 'IS NULL', 'IS NOT NULL', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', ]; - public const CUSTOM_OPERATORS_MAP = [ '%' => 'LIKE', '!%' => 'NOT LIKE', '@' => 'IN', '!@' => 'NOT IN', ]; - - public const SQL_FUNCTIONS = [ + public const SQL_FUNCTIONS = [ /** Agrégations statistique */ 'AVG', 'COUNT', 'MAX', 'MIN', 'SUM', 'EVERY', 'SOME', 'ANY', /** Fonctions systeme */ @@ -42,8 +49,7 @@ class Utils 'NOT EXISTS', 'EXISTS', ]; - private static $expressionPattern = null; - + private static ?string $expressionPattern = null; public static function isSqlFunction(string $value): bool { @@ -57,11 +63,10 @@ public static function isWritableSql(string $value): bool { return (bool) preg_match( '/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD|COPY|ALTER|RENAME|GRANT|REVOKE|LOCK|UNLOCK|REINDEX|MERGE)\s/i', - $value + $value, ); } - /** * Vérifie si une chaîne contient un opérateur SQL */ @@ -85,18 +90,18 @@ public static function translateOperator(string $operator): string */ public static function invertOperator(string $operator): string { - return match($operator) { - '=' => '!=', - '!=' => '=', - '<' => '>=', - '>' => '<=', - '<=' => '>', - '>=' => '<', + return match ($operator) { + '=' => '!=', + '!=' => '=', + '<' => '>=', + '>' => '<=', + '<=' => '>', + '>=' => '<', 'LIKE', '%' => 'NOT LIKE', 'NOT LIKE', '!%' => 'LIKE', - 'IN', '@' => 'NOT IN', + 'IN', '@' => 'NOT IN', 'NOT IN', '!@' => 'IN', - default => $operator + default => $operator, }; } @@ -107,7 +112,7 @@ public static function isAlias(string $value): bool { // Un alias peut être précédé ou non de "AS" $clean = static::extractAlias($value); - + // Un alias valide ne contient que des lettres, chiffres, underscore return preg_match('/^[a-zA-Z0-9_]+$/', $clean) === 1; } @@ -126,8 +131,8 @@ public static function extractAlias(string $value): string public static function isRawExpression(mixed $value): bool { // Une expression brute est souvent entre parenthèses ou contient des fonctions complexes - return $value instanceof Expression - || str_contains($value, '(') && str_contains($value, ')') + return $value instanceof Expression + || str_contains($value, '(') && str_contains($value, ')') || preg_match('/[+\-*\/<>!=]/', $value); } @@ -139,8 +144,8 @@ public static function formatQualifiedColumn(BaseConnection $db, string $column) if (! str_contains($column, '.')) { return $db->escapeIdentifiers($column); } - - $parts = explode('.', $column, 2); + + $parts = explode('.', $column, 2); [$table] = $db->getTableAlias($parts[0]); if (empty($table)) { @@ -160,7 +165,7 @@ public static function extractOperatorFromColumn(string $column, ?string $operat if (empty($operator) || ! in_array($operator, static::OPERATORS, true)) { $operator = '='; } - + $operator = static::translateOperator($operator); return [$column, $operator]; @@ -169,8 +174,8 @@ public static function extractOperatorFromColumn(string $column, ?string $operat public static function parseExpression(string $expression) { if (self::$expressionPattern === null) { - $escaped = array_map(fn($op) => preg_quote($op, '/'), static::OPERATORS); - usort($escaped, fn($a, $b) => strlen($b) <=> strlen($a)); + $escaped = array_map(static fn ($op) => preg_quote($op, '/'), static::OPERATORS); + usort($escaped, static fn ($a, $b) => strlen($b) <=> strlen($a)); self::$expressionPattern = '/^(.*?)\s*(' . implode('|', $escaped) . ')\s*(.*)$/i'; } @@ -178,7 +183,7 @@ public static function parseExpression(string $expression) $column = trim($matches[1]); $operator = static::translateOperator($matches[2]); $rawValue = $matches[3] ?? ''; - + // Cas des opérateurs sans valeur if (in_array($operator, ['', 'IS NULL', 'IS NOT NULL'], true)) { $value = null; @@ -194,8 +199,7 @@ public static function parseExpression(string $expression) if (preg_match('/^(.*?)\s+AND\s+(.*)$/i', $rawValue, $m)) { $value = [static::castValue($m[1]), static::castValue($m[2])]; } - } - else { // Cas général + } else { // Cas général $value = $rawValue === '' ? null : static::castValue($rawValue); } @@ -203,7 +207,7 @@ public static function parseExpression(string $expression) } return null; - } + } public static function castValue(string $value): mixed {