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
40 changes: 32 additions & 8 deletions src/Builder/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ public function fromSubquery(BuilderInterface $from, string $alias = ''): self

$this->reset();
$this->tables = [$table];
$this->bindings->merge($from->bindings);

return $this;
}
Expand Down Expand Up @@ -489,7 +490,7 @@ public function set($key, $value = ''): self
$this->values[$k] = $v;
} else {
$this->values[$k] = $v;
$this->bindings->add($v);
$this->bindings->add($v, 'values');
}
}

Expand Down Expand Up @@ -756,11 +757,11 @@ public function execute(): ResultInterface
{
$this->applyBeforeQueryCallbacks();

$result = $this->query($this->toSql(), $this->bindings->getValues());

$this->reset();

return $result;
try {
return $this->query($this->toSql(), $this->getBindings());
} finally {
$this->reset();
}
}

/**
Expand Down Expand Up @@ -1078,7 +1079,7 @@ public function explain(): array
{
$sql = 'EXPLAIN ' . $this->toSql();

return $this->query($sql, $this->bindings->getValues())->resultArray();
return $this->query($sql, $this->getBindings())->resultArray();
}

/**
Expand All @@ -1103,9 +1104,32 @@ public function sql(bool $preserve = false): string
return $sql;
}

/**
* Nettoyage des bindings
*/
public function cleanBindings(array $bindings): array
{
return $this->bindings->clean($bindings);
}

/**
* Récupère les bindings utilisés
*/
public function getBindings(): array
{
return $this->db->prepareBindings($this->bindings->getValues());
$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
? []
: $this->db->prepareBindings($this->bindings->getOrdered($types));
}

/**
Expand Down
206 changes: 164 additions & 42 deletions src/Builder/BindingCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,71 @@

namespace BlitzPHP\Database\Builder;

use BlitzPHP\Database\Query\Expression;
use InvalidArgumentException;
use PDO;
use PDOStatement;

class BindingCollection
{
protected array $values = [];
/**
* Types de bindings supportés
*/
public const TYPES = [
'select', 'from', 'join', 'where', 'having',
'order', 'union', 'values', 'uniqueBy',
];

/**
* Bindings organisés par type
*
* @var array<string, list<mixed>>
*/
protected array $bindings = [];

/**
* Types PDO pour chaque binding (optionnel)
*
* @var array<string, list<int>>
*/
protected array $types = [];

public function __construct()
{
foreach (self::TYPES as $type) {
$this->bindings[$type] = [];
$this->types[$type] = [];
}
}

/**
* Ajoute un binding
* Ajoute un binding dans un contexte spécifique
*
* @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, ?int $type = null): self
public function add(mixed $value, string $type = 'where', ?int $pdoType = null): self
{
$this->values[] = $value;
$this->types[] = $type ?? $this->guessType($value);

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);

return $this;
}

/**
* Ajoute plusieurs bindings
* Ajoute plusieurs bindings dans un contexte
*/
public function addMany(array $values): self
public function addMany(array $values, string $type = 'where'): self
{
foreach ($values as $value) {
$this->add($value);
$this->add($value, $type);
}

return $this;
Expand All @@ -45,84 +84,156 @@ public function addMany(array $values): self
/**
* Ajoute un binding nommé
*/
public function addNamed(string $name, mixed $value, ?int $type = null): self
public function addNamed(string $name, mixed $value, string $type = 'where', ?int $pdoType = null): self
{
$this->values[$name] = $value;
$this->types[$name] = $type ?? $this->guessType($value);
$this->bindings[$type][$name] = $value;
$this->types[$type][$name] = $pdoType ?? $this->guessType($value);

return $this;
}

/**
* Récupère toutes les valeurs
* Récupère tous les bindings d'un contexte
*
* @return list<mixed>|mixed
*/
public function getValues(): array
public function get(string $type, ?string $name = null)
{
return $this->values;
$bindings = $this->bindings[$type] ?? [];

return $name ? ($bindings[$name] ?? null) : $bindings;
}

/**
* Récupère tous les bindings dans l'ordre de compilation
*
* @param list<string> $types
*
* @return list<mixed>
*/
public function getOrdered(array $types = []): array
{
if ($types === []) {
$types = self::TYPES;
}

$result = [];
foreach ($types as $type) {
if (!empty($this->bindings[$type])) {
array_push($result, ...$this->bindings[$type]);
}
}

return $result;
}

/**
* Récupère tous les types
* Récupère tous les types dans l'ordre
*
* @param list<string> $types
*
* @return list<int>
*/
public function getTypes(): array
public function getTypesOrdered(array $types = []): array
{
return $this->types;
if ($types === []) {
$types = self::TYPES;
}

$result = [];
foreach ($types as $type) {
if (!empty($this->types[$type])) {
array_push($result, ...$this->types[$type]);
}
}

return $result;
}

/**
* Récupère un binding
* Vérifie si un contexte a des bindings
*/
public function get(string|int $key): mixed
public function has(string $type): bool
{
return $this->values[$key] ?? null;
return !empty($this->bindings[$type]);
}

/**
* Récupère le type d'un binding
* Compte le nombre total de bindings
*/
public function getType(string|int $key): ?int
public function count(?string $type = null): int
{
return $this->types[$key] ?? null;
if ($type !== null) {
return count($this->bindings[$type] ?? []);
}

return array_sum(array_map('count', $this->bindings));
}

/**
* Vérifie si des bindings existent
* Vérifie si un contexte est vide
*/
public function isEmpty(): bool
public function isEmpty(?string $type = null): bool
{
return empty($this->values);
return $this->count($type) === 0;
}

/**
* Vide la collection
* Vide tous les bindings
*/
public function clear(): self
public function clear(?string $type = null): self
{
$this->values = [];
$this->types = [];

if ($type !== null) {
$this->bindings[$type] = [];
$this->types[$type] = [];
} else {
foreach (self::TYPES as $t) {
$this->bindings[$t] = [];
$this->types[$t] = [];
}
}

return $this;
}

/**
* Compte le nombre de bindings
* Vide les bindings d'un contexte spécifique
*/
public function count(): int
public function clearType(string $type): self
{
return count($this->values);
if (isset($this->bindings[$type])) {
$this->bindings[$type] = [];
$this->types[$type] = [];
}

return $this;
}

/**
* Fusionne une autre collection
*/
public function merge(self $bindings): self
public function merge(self $collection): self
{
$this->values = array_merge($this->values, $bindings->values);
$this->types = array_merge($this->types, $bindings->types);

foreach (self::TYPES as $type) {
array_push($this->bindings[$type], ...$collection->bindings[$type]);
array_push($this->types[$type], ...$collection->types[$type]);
}

return $this;
}

/**
* Retire les expressions des bindings (elles ne doivent pas être bindées)
*
* @param list<mixed> $bindings
*
* @return list<mixed>
*/
public function clean(array $bindings): array
{
return array_filter($bindings, fn($binding) => !$binding instanceof Expression);
}

/**
* Devine le type PDO d'une valeur
*/
Expand All @@ -138,10 +249,21 @@ protected function guessType(mixed $value): int
}

/**
* Clone la collection
* Pour le débogage
*/
public function toArray(): array
{
return $this->bindings;
}

public function __clone()
{
// Rien de spécial à faire, les tableaux sont copiés
foreach ($this->bindings as $type => $bindings) {
$this->bindings[$type] = $bindings;
}

foreach ($this->types as $type => $types) {
$this->types[$type] = $types;
}
}
}
}
Loading