From 2738a99ed878d0677ad2c8222996c7840a2b7dff Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Fri, 27 Mar 2026 12:25:29 +0100 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20ajout=20de=20la=20classe=20Model=20?= =?UTF-8?q?et=20du=20template=20de=20g=C3=A9n=C3=A9ration=20de=20mod=C3=A8?= =?UTF-8?q?le?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Commands/Generators/Model.php | 125 ++++++++++++++++++++ src/Commands/Generators/Views/model.tpl.php | 49 ++++++++ 2 files changed, 174 insertions(+) create mode 100644 src/Commands/Generators/Model.php create mode 100644 src/Commands/Generators/Views/model.tpl.php diff --git a/src/Commands/Generators/Model.php b/src/Commands/Generators/Model.php new file mode 100644 index 0000000..1d005e5 --- /dev/null +++ b/src/Commands/Generators/Model.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace BlitzPHP\Database\Commands\Generators; + +use BlitzPHP\Cli\Commands\Generators\GeneratorCommand; +use BlitzPHP\Utilities\Helpers; + +/** + * Génère un squelette de fichier de modèle. + */ +class Model extends GeneratorCommand +{ + /** + * {@inheritDoc} + */ + protected string $name = 'make:model'; + + /** + * {@inheritDoc} + */ + protected string $description = 'Génère un nouveau fichier de modèle.'; + + /** + * {@inheritDoc} + */ + protected array $arguments = [ + 'name' => 'Le nom de la classe de modèle', + ]; + + /** + * {@inheritDoc} + */ + protected array $options = [ + '--table' => ['Indiquez un nom de table. Par défaut : "le pluriel en minuscules du nom de la classe".'], + '--dbgroup' => ['Groupe de base de données à utiliser.'], + '--return' => ['Définit le type de retour des résultats, Options: [array, object, entity].', 'array'], + '--namespace' => ['Définit le namespace du modèle.', APP_NAMESPACE], + '--suffix' => 'Ajoute "Model" au nom de la classe (par exemple, User => UserModel).', + '--force' => 'Forcer l\'écrasement du fichier existant.', + ]; + + /** + * {@inheritDoc} + */ + protected string $component = 'Model'; + + /** + * {@inheritDoc} + */ + protected string $directory = 'Models'; + + /** + * {@inheritDoc} + */ + protected string $templatePath = __DIR__ . '/Views'; + + /** + * {@inheritDoc} + */ + protected string $template = 'model.tpl.php'; + + /** + * {@inheritDoc} + */ + protected string $classNameLang = 'CLI.generator.className.model'; + + /** + * {@inheritDoc} + */ + protected function prepare(string $class): string + { + $table = $this->option('table'); + $dbGroup = $this->option('dbgroup'); + $return = $this->option('return', 'array'); + + $baseClass = Helpers::classBasename($class); + + if (preg_match('/^(\S+)Model$/i', $baseClass, $match) === 1) { + $baseClass = $match[1]; + } + + helper('inflector'); + + $table = is_string($table) ? $table : plural(strtolower($baseClass)); + + if (! in_array($return, ['array', 'object', 'entity'], true)) { + $return = $this->choice(lang('CLI.generator.returnType'), ['array', 'object', 'entity'], 'array'); + $this->newLine(); + } + + if ($return === 'entity') { + $return = str_replace('Models', 'Entities', $class); + + if (preg_match('/^(\S+)Model$/i', $return, $match) === 1) { + $return = $match[1]; + + if ($this->option('suffix')) { + $return .= 'Entity'; + } + } + + $return = '\\' . trim($return, '\\') . '::class'; + + if ($this->commandExists('make:entity')) { + $this->call('make:entity', ['name' => $baseClass], [ + '--namespace' => $this->option('namespace'), + '--suffix' => $this->option('suffix'), + ]); + } + } else { + $return = "'{$return}'"; + } + + return $this->parseTemplate($class, ['{dbGroup}', '{table}', '{return}'], [$dbGroup, $table, $return], compact('dbGroup')); + } +} diff --git a/src/Commands/Generators/Views/model.tpl.php b/src/Commands/Generators/Views/model.tpl.php new file mode 100644 index 0000000..6804a7a --- /dev/null +++ b/src/Commands/Generators/Views/model.tpl.php @@ -0,0 +1,49 @@ +<@php + +namespace {namespace}; + +use BlitzPHP\Database\Model; + +class {class} extends Model +{ + + protected ?string $group = '{dbGroup}'; + + protected string $table = '{table}'; + protected string $primaryKey = 'id'; + protected bool $useAutoIncrement = true; + protected string $returnType = {return}; + protected bool $useSoftDeletes = false; + protected array $fillable = []; + + protected bool $allowEmptyInserts = false; + protected bool $updateOnlyChanged = true; + + protected array $casts = []; + protected array $castHandlers = []; + + // Dates + protected bool $useTimestamps = false; + protected string $dateFormat = 'datetime'; + protected string $createdField = 'created_at'; + protected string $updatedField = 'updated_at'; + protected string $deletedField = 'deleted_at'; + + // Validation + protected array $rules = []; + protected array $messages = []; + + protected $skipValidation = false; + protected $cleanValidationRules = true; + + // Callbacks + protected bool $allowCallbacks = true; + protected array $beforeInsert = []; + protected array $afterInsert = []; + protected array $beforeUpdate = []; + protected array $afterUpdate = []; + protected array $beforeDelete = []; + protected array $afterDelete = []; + protected array $beforeFind = []; + protected array $afterFind = []; +} From c4a7b75aaf4f00d7349d13a3d20fcccf154912e6 Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Fri, 27 Mar 2026 12:28:40 +0100 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20ajout=20de=20la=20classe=20Registra?= =?UTF-8?q?r=20pour=20g=C3=A9rer=20les=20fichiers=20de=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Config/Registrar.php | 23 ++++++++++++++++++ src/Publishers/DatabasePublisher.php | 35 ---------------------------- 2 files changed, 23 insertions(+), 35 deletions(-) create mode 100644 src/Config/Registrar.php delete mode 100644 src/Publishers/DatabasePublisher.php diff --git a/src/Config/Registrar.php b/src/Config/Registrar.php new file mode 100644 index 0000000..99e5e35 --- /dev/null +++ b/src/Config/Registrar.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace BlitzPHP\Database\Config; + +class Registrar +{ + /** + * Enregistre les fichiers de configurations publiable + */ + public static function config(): array + { + return ['database', 'dump', 'migrations']; + } +} diff --git a/src/Publishers/DatabasePublisher.php b/src/Publishers/DatabasePublisher.php deleted file mode 100644 index c01267a..0000000 --- a/src/Publishers/DatabasePublisher.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace BlitzPHP\Database\Publishers; - -use BlitzPHP\Publisher\Publisher; - -class DatabasePublisher extends Publisher -{ - /** - * {@inheritDoc} - */ - protected string $source = __DIR__ . '/../Config/'; - - /** - * {@inheritDoc} - */ - protected string $destination = CONFIG_PATH; - - /** - * {@inheritDoc} - */ - public function publish(): bool - { - return $this->addPaths(['database.php', 'migrations.php'])->merge(false); - } -} From 34fd04d5296fb497cb7744e0a9889dde5074033e Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Fri, 27 Mar 2026 12:28:54 +0100 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20mise=20=C3=A0=20jour=20des=20annotat?= =?UTF-8?q?ions=20de=20m=C3=A9thode=20pour=20utiliser=20'static'=20dans=20?= =?UTF-8?q?ProxyMethods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Builder/Concerns/ProxyMethods.php | 90 +++++++++++++-------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Builder/Concerns/ProxyMethods.php b/src/Builder/Concerns/ProxyMethods.php index 8926daa..44fd6fb 100644 --- a/src/Builder/Concerns/ProxyMethods.php +++ b/src/Builder/Concerns/ProxyMethods.php @@ -19,8 +19,8 @@ * 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 + * @method static 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 static 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 @@ -28,63 +28,63 @@ * @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 + * @method static order(array|string $columns, string $direction = 'ASC') Alias de orderBy() - Ajoute une clause ORDER BY + * @method static group(array|string $columns) Alias de groupBy() - Ajoute une clause GROUP BY + * @method static addSelect(array|string $columns) Alias de select() - Ajoute des colonnes à la sélection + * @method static selectSub(\BlitzPHP\Contracts\Database\BuilderInterface $subquery, string $as) Alias de selectSubquery() - Ajoute une sous-requête dans la sélection + * @method static skip(int $offset) Alias de offset() - Ajoute une clause OFFSET + * @method static 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 + * @method static notWhere(array|string $column, mixed $operator = null, mixed $value = null, string $boolean = 'and') Alias de whereNot() - Ajoute une clause WHERE NOT + * @method static 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 static in(string $column, array|\Closure $values, string $boolean = 'and', bool $not = false) Alias de whereIn() - Ajoute une clause WHERE IN + * @method static notIn(string $column, array|\Closure $values, string $boolean = 'and') Alias de whereNotIn() - Ajoute une clause WHERE NOT IN + * @method static orIn(string $column, array|\Closure $values) Alias de orWhereIn() - Ajoute une clause WHERE IN avec OR + * @method static orNotIn(string $column, array|\Closure $values) Alias de orWhereNotIn() - Ajoute une clause WHERE NOT IN avec OR * * // 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 + * @method static 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 static 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 static orLike(string|array $column, string $value = '', string $side = 'both', bool $caseSensitive = false) Alias de orWhereLike() - Ajoute une clause WHERE LIKE avec OR + * @method static 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 + * @method static between(string $column, mixed $value1, mixed $value2, string $boolean = 'and', bool $not = false) Alias de whereBetween() - Ajoute une clause WHERE BETWEEN + * @method static notBetween(string $column, mixed $value1, mixed $value2, string $boolean = 'and') Alias de whereNotBetween() - Ajoute une clause WHERE NOT BETWEEN + * @method static orBetween(string $column, mixed $value1, mixed $value2) Alias de orWhereBetween() - Ajoute une clause WHERE BETWEEN avec OR + * @method static 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 + * @method static 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 static 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 + * @method static havingIn(string $column, array|\Closure $values, string $boolean = 'and', bool $not = false) Alias de havingIn() - Ajoute une clause HAVING IN + * @method static havingNotIn(string $column, array|\Closure $values, string $boolean = 'and') Alias de havingNotIn() - Ajoute une clause HAVING NOT IN + * @method static orHavingIn(string $column, array|\Closure $values) Alias de orHavingIn() - Ajoute une clause HAVING IN avec OR + * @method static orHavingNotIn(string $column, array|\Closure $values) Alias de orHavingNotIn() - Ajoute une clause HAVING NOT IN avec OR + * @method static 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 static notHavingLike(string $column, string $value, string $side = 'both', string $boolean = 'and', bool $caseSensitive = false) Alias de havingNotLike() - Ajoute une clause HAVING NOT LIKE + * @method static orHavingLike(string $column, string $value, string $side = 'both', bool $caseSensitive = false) Alias de orHavingLike() - Ajoute une clause HAVING LIKE avec OR + * @method static orHavingNotLike(string $column, string $value, string $side = 'both', bool $caseSensitive = false) Alias de orHavingNotLike() - Ajoute une clause HAVING NOT LIKE avec OR + * @method static havingBetween(string $column, mixed $value1, mixed $value2, string $boolean = 'and', bool $not = false) Alias de havingBetween() - Ajoute une clause HAVING BETWEEN + * @method static havingNotBetween(string $column, mixed $value1, mixed $value2, string $boolean = 'and') Alias de havingNotBetween() - Ajoute une clause HAVING NOT BETWEEN + * @method static orHavingBetween(string $column, mixed $value1, mixed $value2) Alias de orHavingBetween() - Ajoute une clause HAVING BETWEEN avec OR + * @method static orHavingNotBetween(string $column, mixed $value1, mixed $value2) Alias de orHavingNotBetween() - Ajoute une clause HAVING NOT BETWEEN avec OR + * @method static havingNull(string $column, string $boolean = 'and', bool $not = false) Alias de havingNull() - Ajoute une clause HAVING NULL + * @method static havingNotNull(string $column, string $boolean = 'and') Alias de havingNotNull() - Ajoute une clause HAVING NOT NULL + * @method static orHavingNull(string $column) Alias de orHavingNull() - Ajoute une clause HAVING NULL avec OR + * @method static 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 + * @method static sortAsc(string|array $column) Alias de orderBy() avec direction ASC - Ajoute un tri croissant + * @method static sortDesc(string|array $column) Alias de orderBy() avec direction DESC - Ajoute un tri décroissant + * @method static sortRand(?int $digit = null) Alias de rand() - Ajoute un tri aléatoire + * @method static inRandomOrder(?int $digit = null) Alias de rand() - Ajoute un tri aléatoire + * @method static 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 From a45933e9173209dd23c856d958f5b1a89a1aa3a2 Mon Sep 17 00:00:00 2001 From: Dimitri Sitchet Tomkeu Date: Fri, 27 Mar 2026 15:30:52 +0100 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20am=C3=A9lioration=20de=20la=20gesti?= =?UTF-8?q?on=20des=20index=20et=20des=20colonnes=20dans=20les=20connexion?= =?UTF-8?q?s=20MySQL,=20PostgreSQL=20et=20SQLite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Connection/BaseConnection.php | 4 ++ src/Connection/MySQL.php | 74 ++++++++++++++++++++++--------- src/Connection/Postgre.php | 65 +++++++++++++++++++-------- src/Connection/SQLite.php | 54 ++++++++++++++++------ 4 files changed, 144 insertions(+), 53 deletions(-) diff --git a/src/Connection/BaseConnection.php b/src/Connection/BaseConnection.php index 08014e2..49b1242 100644 --- a/src/Connection/BaseConnection.php +++ b/src/Connection/BaseConnection.php @@ -648,12 +648,16 @@ abstract public function _listTables(bool $constrainByPrefix = false): string; /** * Retourne la requête SQL pour lister les index * + * @return list, type: string, unique: bool, primary: bool}> + * * @internal */ abstract public function _listIndexes(string $table): array; /** * Retourne la requête SQL pour lister les colonnes + * + * @return list * * @internal */ diff --git a/src/Connection/MySQL.php b/src/Connection/MySQL.php index a8a63a9..65ff9b7 100644 --- a/src/Connection/MySQL.php +++ b/src/Connection/MySQL.php @@ -127,19 +127,30 @@ public function _listIndexes(string $table): array $indexes = []; foreach ($rows as $row) { - $index = new stdClass(); - $index->name = $row->Key_name; - $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', - }; - - $indexes[] = $index; + $indexName = $row->Key_name; + + if (! isset($indexes[$indexName])) { + $type = match (true) { + $indexName === 'PRIMARY' => 'PRIMARY', + $row->Index_type === 'FULLTEXT' => 'FULLTEXT', + $row->Index_type === 'SPATIAL' => 'SPATIAL', + isset($row->Non_unique) && (int)$row->Non_unique === 0 => 'UNIQUE', + default => 'INDEX', + }; + + $indexes[$indexName] = (object) [ + 'name' => $indexName, + 'columns' => [], + 'type' => $type, + 'unique' => $type === 'UNIQUE', + 'primary' => $type === 'PRIMARY', + ]; + } + + $indexes[$indexName]->columns[] = $row->Column_name; } - - return $indexes; + + return array_values($indexes); } /** @@ -147,19 +158,42 @@ public function _listIndexes(string $table): array */ public function _listColumns(string $table): array { - $sql = "SHOW COLUMNS FROM {$this->escapeIdentifiers($table)}"; + $sql = "SHOW FULL COLUMNS FROM {$this->escapeIdentifiers($table)}"; $rows = $this->query($sql)->resultObject(); $columns = []; foreach ($rows as $row) { - $column = new stdClass(); - $column->name = $row->Field; - $column->nullable = $row->Null === 'YES'; - $column->default = $row->Default; - $column->primary_key = $row->Key === 'PRI'; - - sscanf($row->Type, '%[a-z](%d)', $column->type, $column->max_length); + $typeName = $row->Type; + $maxLength = null; + + if (preg_match('/^([a-z]+)(?:\((\d+)\))?/', $row->Type, $matches)) { + $typeName = $matches[1]; + $maxLength = isset($matches[2]) ? (int) $matches[2] : null; + } + + $autoIncrement = stripos($row->Extra, 'auto_increment') !== false; + + // Gérer la génération (virtuelle/stored) + $generation = null; + if (stripos($row->Extra, 'GENERATED') !== false) { + $generation = [ + 'type' => stripos($row->Extra, 'VIRTUAL') !== false ? 'VIRTUAL' : 'STORED', + 'expression' => null, // MySQL ne fournit pas l'expression via SHOW COLUMNS + ]; + } + + $column = new stdClass(); + $column->name = $row->Field; + $column->type = $typeName; + $column->type_name = $typeName; + $column->nullable = $row->Null === 'YES'; + $column->default = $row->Default; + $column->auto_increment = $autoIncrement; + $column->max_length = $maxLength; + $column->comment = $row->Comment ?: null; + $column->primary_key = $row->Key === 'PRI'; + $column->generation = $generation; $columns[] = $column; } diff --git a/src/Connection/Postgre.php b/src/Connection/Postgre.php index e672535..3a65cf5 100644 --- a/src/Connection/Postgre.php +++ b/src/Connection/Postgre.php @@ -99,17 +99,21 @@ 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->columns = array_map(static fn ($v) => trim($v), $_columns); - if (str_starts_with($row->indexdef, 'CREATE UNIQUE INDEX pk')) { - $index->type = 'PRIMARY'; + $type = 'PRIMARY'; } else { - $index->type = (str_starts_with($row->indexdef, 'CREATE UNIQUE')) ? 'UNIQUE' : 'INDEX'; + $type = (str_starts_with($row->indexdef, 'CREATE UNIQUE')) ? 'UNIQUE' : 'INDEX'; } + $_columns = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef))); + + $index = new stdClass(); + $index->name = $row->indexname; + $index->columns = array_map(static fn ($v) => trim($v), $_columns); + $index->type = $type; + $index->unique = $type === 'UNIQUE'; + $index->primary = $type === 'PRIMARY'; + $indexes[] = $index; } @@ -121,23 +125,46 @@ public function _listIndexes(string $table): array */ public function _listColumns(string $table): array { - $sql = 'SELECT "column_name", "data_type", "character_maximum_length", "numeric_precision", "column_default", "is_nullable" - FROM "information_schema"."columns" - WHERE LOWER("table_name") = ' - . $this->escape(strtolower($this->prefix . $table)) - . ' ORDER BY "ordinal_position"'; + $sql = 'SELECT + c.column_name, + c.data_type, + c.character_maximum_length, + c.numeric_precision, + c.column_default, + c.is_nullable, + pg_catalog.col_description(pgc.oid, c.ordinal_position) as column_comment, + c.is_identity, + c.generation_expression + FROM information_schema.columns c + LEFT JOIN pg_class pgc ON pgc.relname = ' . $this->escape(strtolower($this->prefix . $table)) . ' + WHERE LOWER(c.table_name) = ' . $this->escape(strtolower($this->prefix . $table)) . ' + AND c.table_schema = \'public\' + ORDER BY c.ordinal_position'; $rows = $this->query($sql)->resultObject(); $columns = []; foreach ($rows as $row) { - $column = new stdClass(); - $column->name = $row->column_name; - $column->type = $row->data_type; - $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; - + // Gérer la génération (GENERATED ALWAYS AS ...) + $generation = null; + if (!empty($row->generation_expression)) { + $generation = [ + 'type' => $row->is_identity === 'YES' ? 'IDENTITY' : 'GENERATED', + 'expression' => $row->generation_expression, + ]; + } + + $column = new stdClass(); + $column->name = $row->column_name; + $column->type = $row->data_type; + $column->type_name = $row->data_type; + $column->nullable = $row->is_nullable === 'YES'; + $column->default = $row->column_default; + $column->auto_increment = $row->is_identity === 'YES'; + $column->comment = $row->column_comment ?: null; + $column->max_length = $row->character_maximum_length > 0 ? $row->character_maximum_length : $row->numeric_precision; + $column->generation = $generation; + $columns[] = $column; } diff --git a/src/Connection/SQLite.php b/src/Connection/SQLite.php index eb08eb5..6b9785b 100644 --- a/src/Connection/SQLite.php +++ b/src/Connection/SQLite.php @@ -108,13 +108,24 @@ public function _listIndexes(string $table): array $indexes = []; foreach ($rows as $row) { - $index = new stdClass(); - $index->name = $row->indexname; - $index->type = $row->indextype; - $index->columns = $row->fieldname; + $indexName = $row->indexname; + + if (!isset($indexes[$indexName])) { + $type = $row->indextype; + + $indexes[$indexName] = (object) [ + 'name' => $indexName, + 'columns' => [], + 'type' => $type, + 'unique' => $type === 'UNIQUE', + 'primary' => $type === 'PRIMARY', + ]; + } + + $indexes[$indexName]->columns[] = $row->fieldname; } - - return $indexes; + + return array_values($indexes); } /** @@ -128,14 +139,29 @@ 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->primary_key = (bool) $row->pk; - $column->max_length = null; - + // SQLite utilise un compteur spécial pour auto_increment + $autoIncrement = false; + $generation = null; + + // Vérifier si la colonne est AUTOINCREMENT (via la table sqlite_sequence) + if ($row->pk && $row->type === 'INTEGER') { + $seqCheck = $this->query("SELECT name FROM sqlite_sequence WHERE name = " . $this->escape($table))->resultObject(); + $autoIncrement = !empty($seqCheck); + } + + $typeName = strtolower(explode('(', $row->type)[0] ?? $row->type); + + $column = new stdClass(); + $column->name = $row->name; + $column->type = $typeName; + $column->type_name = $typeName; + $column->nullable = !$row->notnull; + $column->default = $row->dflt_value; + $column->auto_increment = $autoIncrement; + $column->primary_key = (bool) $row->pk; + $column->comment = null; // SQLite ne supporte pas les commentaires de colonnes nativement + $column->generation = $generation; // SQLite ne supporte pas les colonnes générées avant la version 3.31.0 + $columns[] = $column; }