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 |
||
| 19 | abstract class SqlRecipe |
||
| 20 | { |
||
| 21 | /** @var SqlPattern */ |
||
| 22 | private $sqlPattern; |
||
| 23 | /** @var array map: parameter name or position => supplied value */ |
||
| 24 | private $params; |
||
| 25 | /** @var bool[] map: name of parameter which has not been set any value yet => <tt>true</tt> value */ |
||
| 26 | private $unsatisfiedParams; |
||
| 27 | |||
| 28 | |||
| 29 | /** |
||
| 30 | * Creates an SQL recipe from an SQL string. |
||
| 31 | * |
||
| 32 | * No parameter substitution is performed on the string - it is used as is. |
||
| 33 | * |
||
| 34 | * @param string $sql SQL string |
||
| 35 | * @return static |
||
| 36 | */ |
||
| 37 | public static function fromSql(string $sql) : self |
||
| 42 | |||
| 43 | /** |
||
| 44 | * Creates a new recipe from an SQL pattern. |
||
| 45 | * |
||
| 46 | * Values for all positional parameters required by the pattern must be given. |
||
| 47 | * |
||
| 48 | * Example: |
||
| 49 | * <pre> |
||
| 50 | * <?php |
||
| 51 | * $recipe = new SqlRecipe( |
||
| 52 | * 'SELECT *, %s:status FROM person WHERE role = %d AND email = %s', |
||
| 53 | * 4, '[email protected]' |
||
| 54 | * ); |
||
| 55 | * // results in "SELECT * FROM person WHERE role = 4 AND email = '[email protected]'" |
||
| 56 | * </pre> |
||
| 57 | * |
||
| 58 | * Performance considerations: parsing the SQL pattern, if given as a string, is done by the parser obtained by |
||
| 59 | * {@link \Ivory\Ivory::getSqlPatternParser()}. Depending on Ivory configuration, the parser will cache the results |
||
| 60 | * and reuse them for the same pattern next time. |
||
| 61 | * |
||
| 62 | * @param string|SqlPattern $sqlPattern |
||
| 63 | * @param array ...$positionalParameters |
||
| 64 | * @return static |
||
| 65 | * @throws \InvalidArgumentException when the number of provided positional parameters differs from the number of |
||
| 66 | * positional parameters required by the pattern |
||
| 67 | */ |
||
| 68 | public static function fromPattern($sqlPattern, ...$positionalParameters) : self |
||
| 85 | |||
| 86 | /** |
||
| 87 | * Creates an SQL recipe from one or more fragments, each with its own positional parameters. |
||
| 88 | * |
||
| 89 | * Each fragment must be immediately followed by values for all positional parameters it requires. Then, another |
||
| 90 | * fragment may follow. As the very last argument, a map of values for named parameters may optionally be given (or |
||
| 91 | * {@link SqlRecipe::setParams()} may be used to set them later). |
||
| 92 | * |
||
| 93 | * The fragments get concatenated to form the resulting SQL pattern. A single space is added between each two |
||
| 94 | * fragments the latter of which does not start with whitespace. |
||
| 95 | * |
||
| 96 | * Named parameters are shared among fragments. In other words, if two fragments use the same named parameter, |
||
| 97 | * specifying the parameter by {@link setParam()} will substitute the same value to both fragments. |
||
| 98 | * |
||
| 99 | * Example: |
||
| 100 | * <pre> |
||
| 101 | * <?php |
||
| 102 | * $recipe = SqlRecipe::fromFragments( |
||
| 103 | * 'SELECT * FROM person WHERE role = %d', 4, 'AND email = %s', '[email protected]' |
||
| 104 | * ); |
||
| 105 | * // results in "SELECT * FROM person WHERE role = 4 AND email = '[email protected]'" |
||
| 106 | * </pre> |
||
| 107 | * |
||
| 108 | * Performance considerations: parsing the SQL pattern, if given as a string, is done by the parser obtained by |
||
| 109 | * {@link \Ivory\Ivory::getSqlPatternParser()}. Depending on Ivory configuration, the parser will cache the results |
||
| 110 | * and reuse them for the same pattern next time. |
||
| 111 | * |
||
| 112 | * @internal Ivory design note: The single space added between each two fragments aspires to be more practical than |
||
| 113 | * a mere concatenation, which would require the user to specify spaces where the next fragment immediately |
||
| 114 | * continued with the query. |
||
| 115 | * |
||
| 116 | * @param string|SqlPattern $fragment |
||
| 117 | * @param array ...$fragmentsAndPositionalParams |
||
| 118 | * further fragments (each of which is either a <tt>string</tt> or an |
||
| 119 | * {@link SqlPattern} object) and values of their parameters; |
||
| 120 | * the very last argument may be a map of values for named parameters to set |
||
| 121 | * immediately |
||
| 122 | * |
||
| 123 | * @return static |
||
| 124 | * @throws \InvalidArgumentException when any fragment is not followed by the exact number of parameter values it |
||
| 125 | * requires |
||
| 126 | */ |
||
| 127 | public static function fromFragments($fragment, ...$fragmentsAndPositionalParams) : self |
||
| 217 | |||
| 218 | final private function __construct(SqlPattern $sqlPattern, array $positionalParameters) |
||
| 224 | |||
| 225 | /** |
||
| 226 | * Sets the value of a parameter in the SQL pattern. |
||
| 227 | * |
||
| 228 | * @param string|int $nameOrPosition name of the named parameter, or (zero-based) position of the positional |
||
| 229 | * parameter, respectively |
||
| 230 | * @param mixed $value value of the parameter; |
||
| 231 | * if the parameter is specified explicitly with its type, <tt>$value</tt> must correspond to |
||
| 232 | * the type; |
||
| 233 | * otherwise, the type of the parameter (and thus the conversion to be used) is inferred from |
||
| 234 | * the type of <tt>$value</tt> |
||
| 235 | * @return $this |
||
| 236 | * @throws \InvalidArgumentException when the SQL pattern has no parameter of a given name or position |
||
| 237 | */ |
||
| 238 | public function setParam($nameOrPosition, $value) : self |
||
| 250 | |||
| 251 | /** |
||
| 252 | * Sets values of several parameters in the SQL pattern. |
||
| 253 | * |
||
| 254 | * @param array|\Traversable $paramMap map: parameter name (or zero-based position) => parameter value |
||
| 255 | * @return $this |
||
| 256 | */ |
||
| 257 | public function setParams($paramMap) : self // PHP 7.1: declare $paramMap as iterable |
||
| 264 | |||
| 265 | public function getSqlPattern() : SqlPattern |
||
| 269 | |||
| 270 | /** |
||
| 271 | * @return array map: parameter name or zero-based position => parameter value |
||
| 272 | */ |
||
| 273 | public function getParams() : array |
||
| 277 | |||
| 278 | /** |
||
| 279 | * @param ITypeDictionary $typeDictionary |
||
| 280 | * @return string |
||
| 281 | * @throws InvalidStateException when values for one or more named parameters has not been set |
||
| 282 | * @throws UndefinedTypeException when some of the types used in the pattern are not defined |
||
| 283 | */ |
||
| 284 | public function toSql(ITypeDictionary $typeDictionary) : string |
||
| 338 | } |
||
| 339 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.