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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion spec/Connection/BaseConnection.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,17 @@

it(": Échappement des identifiants", function() {
expect($this->connection->escapeIdentifiers('users.id'))->toBe('`users`.`id`');
// expect($this->connection->escapeIdentifiers('count(*)'))->toBe('count(*)');
expect($this->connection->escapeIdentifiers('count(id)'))->toBe('count(`id`)');
expect($this->connection->escapeIdentifiers('count(users.id)'))->toBe('count(`users`.`id`)');
expect($this->connection->escapeIdentifiers(['users.id', 'name']))->toBe(['`users`.`id`', '`name`']);
});

it(": Échappement des identifiants reservés", function() {
expect($this->connection->escapeIdentifiers('users.*'))->toBe('`users`.*');
expect($this->connection->escapeIdentifiers(['count(*)', 'count(users.*)']))->toBe(['count(*)', 'count(`users`.*)']);
expect($this->connection->escapeIdentifiers(['users.id', 'name', '*']))->toBe(['`users`.`id`', '`name`', '*']);
});

it(": Échappement des chaînes", function() {
$escaped = $this->connection->escapeString("O'Reilly");
expect($escaped)->toBe("'O''Reilly'");
Expand Down
5 changes: 3 additions & 2 deletions spec/Utils.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,17 @@
});

it(": isAlias", function() {
expect(Utils::isAlias('user_alias'))->toBe(true);
expect(Utils::isAlias('user_alias'))->toBe(false);
expect(Utils::isAlias('AS user_alias'))->toBe(true);
expect(Utils::isAlias('user-alias'))->toBe(false); // tiret pas autorisé
expect(Utils::isAlias('user.alias'))->toBe(false); // point pas autorisé
});

it(": extractAlias", function() {
expect(Utils::extractAlias('AS alias'))->toBe('alias');
expect(Utils::extractAlias('alias'))->toBe('alias');
expect(Utils::extractAlias('alias'))->toBeNull();
expect(Utils::extractAlias(' AS alias '))->toBe('alias');
expect(Utils::extractAlias('users AS alias '))->toBe('alias');
});

it(": extractOperatorFromColumn", function() {
Expand Down
10 changes: 9 additions & 1 deletion src/Builder/Concerns/DataMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public function insertUsing(array $columns, BuilderInterface|Closure $query)
public function insertGetId(array $values, ?string $sequence = null)
{
if (is_bool($inserted = $this->insert($values))) {
return $inserted === true ? $this->db->lastId($this->getTable()) : null;
return $inserted === true ? $this->lastId($this->getTable()) : null;
}

return $inserted;
Expand Down Expand Up @@ -195,6 +195,14 @@ protected function getKeyName(): string
return 'id';
}

/**
* Récupère le dernier ID généré par l'auto-incrémentation
*/
public function lastId(?string $table = null): ?int
{
return $this->db->lastId($table);
}

/*
|--------------------------------------------------------------------------
| RAW EXPRESSIONS
Expand Down
38 changes: 7 additions & 31 deletions src/Commands/Generators/Migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@

namespace BlitzPHP\Database\Commands\Generators;

use BlitzPHP\Cli\Console\Command;
use BlitzPHP\Cli\Traits\GeneratorTrait;
use BlitzPHP\Cli\Commands\Generators\GeneratorCommand;
use InvalidArgumentException;

/**
Expand All @@ -21,15 +20,8 @@
* Analyse le nom de la migration pour déterminer automatiquement
* l'action (create/modify) et la table concernée.
*/
class Migration extends Command
class Migration extends GeneratorCommand
{
use GeneratorTrait;

/**
* {@inheritDoc}
*/
protected string $group = 'Générateurs';

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -61,6 +53,11 @@ class Migration extends Command
'--suffix' => 'Ajoute "Migration" au nom de la classe (par exemple, User => UserMigration)',
];

protected string $component = 'Migration';
protected string $directory = 'Database\Migrations';
protected string $template = 'migration.tpl.php';
protected string $templatePath = __DIR__ . '/Views';

/**
* Mots-clés pour les actions de création
*/
Expand All @@ -82,27 +79,6 @@ class Migration extends Command
*/
protected array $dropKeywords = ['drop', 'delete', 'remove'];

/**
* {@inheritDoc}
*/
public function handle()
{
$this->component = 'Migration';
$this->directory = 'Database\Migrations';
$this->template = 'migration.tpl.php';
$this->templatePath = __DIR__ . '/Views';

try {
$this->generateClass($this->parameters());

return EXIT_SUCCESS;
} catch (InvalidArgumentException $e) {
$this->error($e->getMessage());

return EXIT_ERROR;
}
}

/**
* Prépare les options et effectue les remplacements nécessaires.
*/
Expand Down
32 changes: 8 additions & 24 deletions src/Commands/Generators/Seeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,14 @@

namespace BlitzPHP\Database\Commands\Generators;

use BlitzPHP\Cli\Console\Command;
use BlitzPHP\Cli\Traits\GeneratorTrait;
use BlitzPHP\Cli\Commands\Generators\GeneratorCommand;

/**
* Génère un fichier squelette de seeder.
*/
class Seeder extends Command
class Seeder extends GeneratorCommand
{
use GeneratorTrait;

/**
* {@inheritDoc}
*/
protected string $group = 'Generateurs';

/**
/**
* {@inheritDoc}
*/
protected string $name = 'make:seeder';
Expand Down Expand Up @@ -57,17 +49,9 @@ class Seeder extends Command
'--force' => 'Forcer l\'écrasement du fichier existant.',
];

/**
* {@inheritDoc}
*/
public function handle()
{
$this->component = 'Seeder';
$this->directory = 'Database\Seeds';
$this->template = 'seeder.tpl.php';
$this->templatePath = __DIR__ . '/Views';

$this->classNameLang = 'CLI.generator.className.seeder';
$this->generateClass($this->parameters());
}
protected string $component = 'Seeder';
protected string $directory = 'Database\Seeds';
protected string $template = 'seeder.tpl.php';
protected string $templatePath = __DIR__ . '/Views';
protected string $classNameLang = 'CLI.generator.className.seeder';
}
124 changes: 119 additions & 5 deletions src/Connection/BaseConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,17 @@ public function escapeIdentifiers(mixed $item): mixed
return array_map([$this, 'escapeIdentifiers'], $item);
}

if ($item instanceof Stringable) {
$item = (string) $item;
}

$item = trim($item);

// Vérifier d'abord si c'est un appel de fonction SQL
if ($processed = $this->processSqlFunctionCall($item)) {
return $processed;
}

if (! isset($this->escapeCache[$item])) {
$this->escapeCache[$item] = $this->doEscapeIdentifiers($item);
}
Expand All @@ -777,10 +788,6 @@ public function escapeIdentifiers(mixed $item): mixed

protected function doEscapeIdentifiers(string $item): string
{
if ($this->isReserved($item) || Utils::isSqlFunction($item)) {
return $item;
}

if (str_contains($item, '.')) {
$parts = explode('.', $item);

Expand All @@ -795,13 +802,116 @@ protected function doEscapeIdentifiers(string $item): string
*/
protected function escapeIdentifier(string $item): string
{
if ($this->isReserved($item) || Utils::isSqlFunction($item)) {
return $item;
}

if ($this->isEscapedIdentifier($item)) {
return $item;
}

return $this->escapeChar . $item . $this->escapeChar;
}

/**
* Traite un appel de fonction SQL et échappe ses paramètres si nécessaire
*/
protected function processSqlFunctionCall(string $value): ?string
{
// Pattern pour capturer: functionName(param1, param2, ...)
if (preg_match('/^(\w+)\s*\((.*)\)$/', $value, $matches)) {
$functionName = $matches[1];
$parameters = $matches[2];

if (Utils::isSqlFunction($functionName)) {
// Si pas de paramètres, retourner tel quel
if (trim($parameters) === '') {
return $value;
}

// Traiter chaque paramètre
$processedParams = [];
$params = $this->splitParameters($parameters);

foreach ($params as $param) {
$param = trim($param);
$processedParams[] = $this->processSqlFunctionParameter($param);
}

return $functionName . '(' . implode(', ', $processedParams) . ')';
}
}

return null;
}

/**
* Traite un paramètre d'appel de fonction SQL
*/
protected function processSqlFunctionParameter(string $param): string
{
// Si c'est un wildcard
if ($param === '*') {
return $param;
}

// Si le paramètre contient un point, c'est un identifiant qualifié
if (str_contains($param, '.')) {
// Séparer les parties et échapper chaque partie individuellement
$parts = explode('.', $param);
$parts = array_map($this->escapeIdentifier(...), $parts);

return implode('.', $parts);
}

// Si le paramètre est un identifiant simple
if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $param)) {
return $this->escapeIdentifier($param);
}

// Si c'est un appel de fonction imbriqué
if (preg_match('/^(\w+)\s*\(.*\)$/', $param)) {
return $this->processSqlFunctionCall($param) ?? $param;
}

// Sinon, garder tel quel (nombres, chaînes entre quotes, etc.)
return $param;
}

/**
* Sépare les paramètres d'une fonction en gérant les virgules dans les parenthèses
*/
protected function splitParameters(string $parameters): array
{
$params = [];
$current = '';
$depth = 0;
$length = strlen($parameters);

for ($i = 0; $i < $length; $i++) {
$char = $parameters[$i];

if ($char === '(') {
$depth++;
$current .= $char;
} elseif ($char === ')') {
$depth--;
$current .= $char;
} elseif ($char === ',' && $depth === 0) {
$params[] = trim($current);
$current = '';
} else {
$current .= $char;
}
}

if (trim($current) !== '') {
$params[] = trim($current);
}

return $params;
}

/**
* Vérifie si un identifiant est réservé
*/
Expand Down Expand Up @@ -1133,7 +1243,11 @@ public function error(): array
public function lastId(?string $table = null): ?int
{
try {
return (int) $this->pdo->lastInsertId($table);
if (-1 === $id = $this->result?->lastId() ?? -1) {
$id = $this->pdo->lastInsertId($table);
}

return (int) $id;
} catch (PDOException) {
return null;
}
Expand Down
Loading