Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
| 1 | <?php |
||
| 12 | trait SqlPatternRecipeMacros |
||
| 13 | { |
||
| 14 | /** @var SqlPattern */ |
||
| 15 | private $sqlPattern; |
||
| 16 | /** @var array map: parameter name or position => supplied value */ |
||
| 17 | private $params; |
||
| 18 | /** @var bool[] map: name of parameter which has not been set any value yet => <tt>true</tt> value */ |
||
| 19 | private $unsatisfiedParams; |
||
| 20 | |||
| 21 | |||
| 22 | /** |
||
| 23 | * Creates an SQL recipe from an SQL string. |
||
| 24 | * |
||
| 25 | * No parameter substitution is performed on the string - it is used as is. |
||
| 26 | * |
||
| 27 | * @param string $sql SQL string |
||
| 28 | * @return static |
||
| 29 | */ |
||
| 30 | public static function fromSql(string $sql): self |
||
| 35 | |||
| 36 | /** |
||
| 37 | * Creates a new recipe from an SQL pattern. |
||
| 38 | * |
||
| 39 | * Values for all positional parameters required by the pattern must be given. |
||
| 40 | * |
||
| 41 | * Example: |
||
| 42 | * <pre> |
||
| 43 | * <?php |
||
| 44 | * $recipe = new SqlRecipe( |
||
| 45 | * 'SELECT *, %s:status FROM person WHERE role = %d AND email = %s', |
||
| 46 | * 4, '[email protected]' |
||
| 47 | * ); |
||
| 48 | * // results in "SELECT * FROM person WHERE role = 4 AND email = '[email protected]'" |
||
| 49 | * </pre> |
||
| 50 | * |
||
| 51 | * Performance considerations: parsing the SQL pattern, if given as a string, is done by the parser obtained by |
||
| 52 | * {@link \Ivory\Ivory::getSqlPatternParser()}. Depending on Ivory configuration, the parser will cache the results |
||
| 53 | * and reuse them for the same pattern next time. |
||
| 54 | * |
||
| 55 | * @param string|SqlPattern $sqlPattern |
||
| 56 | * @param array ...$positionalParameters |
||
| 57 | * @return static |
||
| 58 | * @throws \InvalidArgumentException when the number of provided positional parameters differs from the number of |
||
| 59 | * positional parameters required by the pattern |
||
| 60 | */ |
||
| 61 | public static function fromPattern($sqlPattern, ...$positionalParameters): self |
||
| 78 | |||
| 79 | /** |
||
| 80 | * Creates an SQL recipe from one or more fragments, each with its own positional parameters. |
||
| 81 | * |
||
| 82 | * Each fragment must be immediately followed by values for all positional parameters it requires. Then, another |
||
| 83 | * fragment may follow. As the very last argument, a map of values for named parameters may optionally be given (or |
||
| 84 | * {@link SqlRecipe::setParams()} may be used to set them later). |
||
| 85 | * |
||
| 86 | * The fragments get concatenated to form the resulting SQL pattern. A single space is added between each two |
||
| 87 | * fragments the latter of which does not start with whitespace. |
||
| 88 | * |
||
| 89 | * Named parameters are shared among fragments. In other words, if two fragments use the same named parameter, |
||
| 90 | * specifying the parameter by {@link setParam()} will substitute the same value to both fragments. |
||
| 91 | * |
||
| 92 | * Example: |
||
| 93 | * <pre> |
||
| 94 | * <?php |
||
| 95 | * $recipe = SqlRecipe::fromFragments( |
||
| 96 | * 'SELECT * FROM person WHERE role = %d', 4, 'AND email = %s', '[email protected]' |
||
| 97 | * ); |
||
| 98 | * // results in "SELECT * FROM person WHERE role = 4 AND email = '[email protected]'" |
||
| 99 | * </pre> |
||
| 100 | * |
||
| 101 | * Performance considerations: parsing the SQL pattern, if given as a string, is done by the parser obtained by |
||
| 102 | * {@link \Ivory\Ivory::getSqlPatternParser()}. Depending on Ivory configuration, the parser will cache the results |
||
| 103 | * and reuse them for the same pattern next time. |
||
| 104 | * |
||
| 105 | * @internal Ivory design note: The single space added between each two fragments aspires to be more practical than |
||
| 106 | * a mere concatenation, which would require the user to specify spaces where the next fragment immediately |
||
| 107 | * continued with the query. |
||
| 108 | * |
||
| 109 | * @param string|SqlPattern $fragment |
||
| 110 | * @param array ...$fragmentsAndPositionalParams |
||
| 111 | * further fragments (each of which is either a <tt>string</tt> or an |
||
| 112 | * {@link SqlPattern} object) and values of their parameters; |
||
| 113 | * the very last argument may be a map of values for named parameters to set |
||
| 114 | * immediately |
||
| 115 | * |
||
| 116 | * @return static |
||
| 117 | * @throws \InvalidArgumentException when any fragment is not followed by the exact number of parameter values it |
||
| 118 | * requires |
||
| 119 | */ |
||
| 120 | public static function fromFragments($fragment, ...$fragmentsAndPositionalParams): self |
||
| 209 | |||
| 210 | final private function __construct(SqlPattern $sqlPattern, array $positionalParameters) |
||
| 216 | |||
| 217 | public function setParam($nameOrPosition, $value) |
||
| 228 | |||
| 229 | public function setParams($paramMap) |
||
| 236 | |||
| 237 | public function getSqlPattern(): SqlPattern |
||
| 241 | |||
| 242 | public function getParams(): array |
||
| 246 | |||
| 247 | /** |
||
| 248 | * @param ITypeDictionary $typeDictionary |
||
| 249 | * @return string |
||
| 250 | * @throws InvalidStateException when values for one or more named parameters has not been set |
||
| 251 | * @throws UndefinedTypeException when some of the types used in the pattern are not defined |
||
| 252 | */ |
||
| 253 | public function toSql(ITypeDictionary $typeDictionary): string |
||
| 304 | } |
||
| 305 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.