|
| 1 | +<?php |
| 2 | + |
| 3 | +/** |
| 4 | + * This file is part of SQLBuilder. |
| 5 | + * |
| 6 | + * @author Chopin Ngo <consatan@gmail.com> |
| 7 | + * @license https://opensource.org/licenses/bsd-3-clause BSD-3-Clause |
| 8 | + */ |
| 9 | + |
| 10 | +declare(strict_types=1); |
| 11 | + |
| 12 | +namespace Consatan\SQLBuilder; |
| 13 | + |
| 14 | +use PDO; |
| 15 | +use InvalidArgumentException; |
| 16 | + |
| 17 | +class Bind |
| 18 | +{ |
| 19 | + /** @var string */ |
| 20 | + const NAMED_PLACEHOLDER_PATTERN = '/^:[a-zA-Z0-9_]+$/'; |
| 21 | + |
| 22 | + /** @var array Bind values. */ |
| 23 | + protected $values = []; |
| 24 | + |
| 25 | + /** |
| 26 | + * @param array $values |
| 27 | + * @param int $type |
| 28 | + */ |
| 29 | + protected function __construct(array $values, int $type) |
| 30 | + { |
| 31 | + self::assertPDOParamType($type); |
| 32 | + |
| 33 | + $isAssocArray = false; |
| 34 | + foreach ($values as $value) { |
| 35 | + if (is_array($value)) { |
| 36 | + if (!($isMap = is_map($value)) && !is_list($value)) { |
| 37 | + throw new InvalidArgumentException('Mixed array is not allow.'); |
| 38 | + } |
| 39 | + |
| 40 | + if ($isMap) { |
| 41 | + if (!$isAssocArray && !empty($this->values)) { |
| 42 | + throw new InvalidArgumentException( |
| 43 | + 'Mixed array is not allow, string key not allowed push to indexed array.' |
| 44 | + ); |
| 45 | + } |
| 46 | + |
| 47 | + $isAssocArray = true; |
| 48 | + foreach ($value as $key => $val) { |
| 49 | + self::assertNamedPlaceholder($key); |
| 50 | + if (is_array($val)) { |
| 51 | + // where in |
| 52 | + self::assertArrayBindValue($val = array_values($val)); |
| 53 | + } else { |
| 54 | + self::assertBindValue($val); |
| 55 | + } |
| 56 | + |
| 57 | + $this->values[$key] = [$val, $type]; |
| 58 | + } |
| 59 | + } else { |
| 60 | + $this->throwIfIsAssocArray($isAssocArray); |
| 61 | + |
| 62 | + self::assertArrayBindValue($value = array_values($value)); |
| 63 | + $this->values[] = [$value, $type]; |
| 64 | + } |
| 65 | + } else { |
| 66 | + $this->throwIfIsAssocArray($isAssocArray); |
| 67 | + |
| 68 | + self::assertBindValue($value); |
| 69 | + $this->values[] = [$value, $type]; |
| 70 | + } |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + /** |
| 75 | + * Get bind values. |
| 76 | + * |
| 77 | + * @return array |
| 78 | + */ |
| 79 | + public function getValues(): array |
| 80 | + { |
| 81 | + return $this->values; |
| 82 | + } |
| 83 | + |
| 84 | + /** |
| 85 | + * If is an associative array throw InvalidArgumentException. |
| 86 | + * |
| 87 | + * @param bool $isAssoc |
| 88 | + * @throws \InvalidArgumentException If true throw this exception. |
| 89 | + */ |
| 90 | + private function throwIfIsAssocArray(bool $isAssoc): void |
| 91 | + { |
| 92 | + if ($isAssoc) { |
| 93 | + throw new InvalidArgumentException( |
| 94 | + 'Mixed array is not allow, numeric key not allowed push to associative array.' |
| 95 | + ); |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + /** |
| 100 | + * Bind int values. |
| 101 | + * |
| 102 | + * @param mixed ...$bind Bind values. |
| 103 | + * @return Bind |
| 104 | + * @throws \InvalidArgumentException If values invalid. |
| 105 | + */ |
| 106 | + final public static function int(...$bind): Bind |
| 107 | + { |
| 108 | + return new self($bind, PDO::PARAM_INT); |
| 109 | + } |
| 110 | + |
| 111 | + /** |
| 112 | + * Bind string values. |
| 113 | + * |
| 114 | + * @param mixed ...$bind Bind values. |
| 115 | + * @return Bind |
| 116 | + * @throws \InvalidArgumentException If values invalid. |
| 117 | + */ |
| 118 | + final public static function str(...$bind): Bind |
| 119 | + { |
| 120 | + return new self($bind, PDO::PARAM_STR); |
| 121 | + } |
| 122 | + |
| 123 | + /** |
| 124 | + * Bind null values. |
| 125 | + * |
| 126 | + * @param mixed $val |
| 127 | + * @param mixed ...$bind Bind values. |
| 128 | + * @return Bind |
| 129 | + * @throws \InvalidArgumentException If values invalid. |
| 130 | + */ |
| 131 | + final public static function null($val = null, ...$bind): Bind |
| 132 | + { |
| 133 | + array_unshift($bind, $val); |
| 134 | + return new self($bind, PDO::PARAM_NULL); |
| 135 | + } |
| 136 | + |
| 137 | + /** |
| 138 | + * Bind boolean values. |
| 139 | + * |
| 140 | + * @param mixed ...$bind Bind values. |
| 141 | + * @return Bind |
| 142 | + * @throws \InvalidArgumentException If values invalid. |
| 143 | + */ |
| 144 | + final public static function bool(...$bind): Bind |
| 145 | + { |
| 146 | + return new self($bind, PDO::PARAM_BOOL); |
| 147 | + } |
| 148 | + |
| 149 | + /** |
| 150 | + * Bind SQL large object data values. |
| 151 | + * |
| 152 | + * @param mixed ...$bind Bind values. |
| 153 | + * @return Bind |
| 154 | + * @throws \InvalidArgumentException If values invalid. |
| 155 | + */ |
| 156 | + final public static function lob(...$bind): Bind |
| 157 | + { |
| 158 | + return new self($bind, PDO::PARAM_LOB); |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * Assert bind value is a scalar or null. |
| 163 | + * |
| 164 | + * @param mixed $value |
| 165 | + * @throws \InvalidArgumentException If values invalid. |
| 166 | + */ |
| 167 | + final public static function assertBindValue($value): void |
| 168 | + { |
| 169 | + if (!is_scalar($value) && null !== $value) { |
| 170 | + throw new InvalidArgumentException(sprintf( |
| 171 | + 'Bind value must be a scalar or null, %s given.', |
| 172 | + gettype($value) |
| 173 | + )); |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + /** |
| 178 | + * Assert array values is valid bind value. |
| 179 | + * |
| 180 | + * @param array $array |
| 181 | + * @throws \InvalidArgumentException If values invalid. |
| 182 | + */ |
| 183 | + final public static function assertArrayBindValue(array $array): void |
| 184 | + { |
| 185 | + foreach ($array as $val) { |
| 186 | + self::assertBindValue($val); |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + /** |
| 191 | + * Assert named placeholder is valid. |
| 192 | + * |
| 193 | + * @param string $placeholder |
| 194 | + * @throws \InvalidArgumentException If placeholder invalid. |
| 195 | + */ |
| 196 | + final public static function assertNamedPlaceholder(string $placeholder): void |
| 197 | + { |
| 198 | + if (1 !== preg_match(self::NAMED_PLACEHOLDER_PATTERN, $placeholder)) { |
| 199 | + throw new InvalidArgumentException("Invalid SQL named placeholder, \"{$placeholder}\"."); |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + /** |
| 204 | + * Assert type is an invalid data type for PDO parameter. |
| 205 | + * |
| 206 | + * @param int $type |
| 207 | + * @throws \InvalidArgumentException If invalid data type. |
| 208 | + */ |
| 209 | + final public static function assertPDOParamType(int $type): void |
| 210 | + { |
| 211 | + if (PDO::PARAM_STR !== $type |
| 212 | + && PDO::PARAM_INT !== $type |
| 213 | + && PDO::PARAM_NULL !== $type |
| 214 | + && PDO::PARAM_BOOL !== $type |
| 215 | + && PDO::PARAM_LOB !== $type |
| 216 | + ) { |
| 217 | + throw new InvalidArgumentException("Invalid data type for PDO parameter, \"{$type}\"."); |
| 218 | + } |
| 219 | + } |
| 220 | +} |
0 commit comments