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:
Complex classes like SqlPatternDefinitionMacros often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SqlPatternDefinitionMacros, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | trait SqlPatternDefinitionMacros |
||
14 | { |
||
15 | /** @var SqlPattern */ |
||
16 | private $sqlPattern; |
||
17 | /** @var array map: parameter name or position => supplied value */ |
||
18 | private $params; |
||
19 | /** @var bool[] map: name of parameter which has not been set any value yet => <tt>true</tt> value */ |
||
20 | private $unsatisfiedParams; |
||
21 | |||
22 | |||
23 | /** |
||
24 | * Creates an SQL definition from an SQL string. |
||
25 | * |
||
26 | * No parameter substitution is performed on the string - it is used as is. |
||
27 | * |
||
28 | * @param string $sql SQL string |
||
29 | * @return static |
||
30 | */ |
||
31 | public static function fromSql(string $sql): self |
||
36 | |||
37 | /** |
||
38 | * Creates a new definition from an SQL pattern. |
||
39 | * |
||
40 | * Values for all positional parameters required by the pattern must be given. |
||
41 | * |
||
42 | * Example: |
||
43 | * <code> |
||
44 | * // relation definition given by "SELECT * FROM person WHERE role = 4 AND email = '[email protected]'" |
||
45 | * $relDef = SqlRelationDefinition::fromPattern( |
||
46 | * 'SELECT * FROM person WHERE role = %i AND email = %s', |
||
47 | * 4, '[email protected]' |
||
48 | * ); |
||
49 | * |
||
50 | * // command defined by "DELETE FROM mytable WHERE id < 100" |
||
51 | * $cmd = SqlCommand::fromPattern( |
||
52 | * 'DELETE FROM %ident WHERE id < %i', |
||
53 | * 'mytable', 100 |
||
54 | * ); |
||
55 | * </code> |
||
56 | * |
||
57 | * Performance considerations: parsing the SQL pattern, if given as a string, is done by the parser obtained by |
||
58 | * {@link \Ivory\Ivory::getSqlPatternParser()}. Depending on Ivory configuration, the parser will cache the results |
||
59 | * and reuse them for the same pattern next time. |
||
60 | * |
||
61 | * @param string|SqlPattern $sqlPattern |
||
62 | * @param array ...$positionalParameters |
||
63 | * @return static |
||
64 | * @throws \InvalidArgumentException when the number of provided positional parameters differs from the number of |
||
65 | * positional parameters required by the pattern |
||
66 | */ |
||
67 | public static function fromPattern($sqlPattern, ...$positionalParameters): self |
||
84 | |||
85 | /** |
||
86 | * Creates an SQL definition from one or more fragments, each with its own positional parameters. |
||
87 | * |
||
88 | * Each fragment must be immediately followed by values for all positional parameters it requires. Then, another |
||
89 | * fragment may follow. As the very last argument, a map of values for named parameters may optionally be given (or |
||
90 | * {@link setParams()} may be used to set them later). |
||
91 | * |
||
92 | * The fragments get concatenated to form the resulting SQL pattern. A single space is added between each two |
||
93 | * fragments the former of which ends with a non-whitespace character and the latter of which starts with a |
||
94 | * non-whitespace character. |
||
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 | * <code> |
||
101 | * // relation definition given by "SELECT * FROM person WHERE role = 4 AND email = '[email protected]'" |
||
102 | * $relDef = SqlRelationDefinition::fromFragments( |
||
103 | * 'SELECT * FROM person WHERE role = %i', 4, 'AND email = %s', '[email protected]' |
||
104 | * ); |
||
105 | * |
||
106 | * // command defined by "DELETE FROM mytable WHERE id < 100" |
||
107 | * $cmd = SqlCommand::fromFragments( |
||
108 | * 'DELETE FROM %ident', 'mytable', |
||
109 | * 'WHERE id < %i', 100 |
||
110 | * ); |
||
111 | * </code> |
||
112 | * |
||
113 | * Performance considerations: parsing the SQL pattern, if given as a string, is done by the parser obtained by |
||
114 | * {@link \Ivory\Ivory::getSqlPatternParser()}. Depending on Ivory configuration, the parser will cache the results |
||
115 | * and reuse them for the same pattern next time. |
||
116 | * |
||
117 | * @internal Ivory design note: The single space added between each two fragments aspires to be more practical than |
||
118 | * a mere concatenation, which would require the user to specify spaces where the next fragment immediately |
||
119 | * continued with the query. After all, the method has ambitions to at least partly understand the user wants to |
||
120 | * compose an SQL query from several parts, thus, it is legitimate the query is modified appropriately. |
||
121 | * |
||
122 | * @param string|SqlPattern $fragment |
||
123 | * @param array ...$fragmentsAndParamValues |
||
124 | * further fragments (each of which is either a <tt>string</tt> or an |
||
125 | * {@link SqlPattern} object) and values of their parameters; |
||
126 | * the very last argument may be a map of values for named parameters to set |
||
127 | * immediately |
||
128 | * @return static |
||
129 | * @throws \InvalidArgumentException when any fragment is not followed by the exact number of parameter values it |
||
130 | * requires |
||
131 | */ |
||
132 | public static function fromFragments($fragment, ...$fragmentsAndParamValues): self |
||
230 | |||
231 | private static function needsSpaceAsGlue( |
||
263 | |||
264 | final private function __construct(SqlPattern $sqlPattern, array $positionalParameters) |
||
270 | |||
271 | public function setParam($nameOrPosition, $value) |
||
282 | |||
283 | public function setParams(iterable $paramMap) |
||
290 | |||
291 | public function getSqlPattern(): SqlPattern |
||
295 | |||
296 | public function getParams(): array |
||
300 | |||
301 | public function toSql(ITypeDictionary $typeDictionary, array $namedParameterValues = []): string |
||
368 | } |
||
369 |
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.