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 |
||
31 | class QueryOperations { |
||
32 | |||
33 | /** |
||
34 | * |
||
35 | * @var RecordWrapper |
||
36 | */ |
||
37 | private $wrapper; |
||
38 | private $adapter; |
||
39 | private $queryParameters; |
||
40 | private $pendingMethod; |
||
41 | private $dynamicMethods = [ |
||
42 | "/(?<method>filterBy)(?<variable>[A-Z][A-Za-z]+){1}/", |
||
43 | "/(?<method>sort)(?<direction>Asc|Desc)?(By)(?<variable>[A-Z][A-Za-z]+){1}/", |
||
44 | "/(?<method>fetch)(?<first>First)?(With)(?<variable>[A-Za-z]+)/" |
||
45 | ]; |
||
46 | private $dataOperations; |
||
47 | private $driver; |
||
48 | |||
49 | /** |
||
50 | * |
||
51 | * @param RecordWrapper $wrapper |
||
52 | * @param DriverAdapter $adapter |
||
53 | * @param DataOperations $dataOperations |
||
54 | */ |
||
55 | 35 | View Code Duplication | public function __construct(ORMContext $context, RecordWrapper $wrapper, DriverAdapter $adapter, $dataOperations) { |
56 | 35 | $this->wrapper = $wrapper; |
|
57 | 35 | $this->adapter = $adapter; |
|
58 | 35 | $this->dataOperations = $dataOperations; |
|
59 | 35 | $this->driver = $context->getDbContext()->getDriver(); |
|
60 | 35 | } |
|
61 | |||
62 | 25 | public function doFetch($id = null) { |
|
63 | 25 | $parameters = $this->getFetchQueryParameters($id); |
|
64 | 25 | $data = $this->adapter->select($parameters); |
|
65 | 25 | $this->wrapper->setData($data); |
|
66 | 25 | $this->resetQueryParameters(); |
|
67 | 25 | return $this->wrapper; |
|
68 | } |
||
69 | |||
70 | 27 | private function getFetchQueryParameters($arg, $instantiate = true) { |
|
71 | 27 | if ($arg instanceof \ntentan\nibii\QueryParameters) { |
|
72 | 12 | return $arg; |
|
73 | } |
||
74 | |||
75 | 27 | $parameters = $this->getQueryParameters($instantiate); |
|
76 | |||
77 | 27 | if (is_numeric($arg)) { |
|
78 | 6 | $description = $this->wrapper->getDescription(); |
|
79 | 6 | $parameters->addFilter($description->getPrimaryKey()[0], $arg); |
|
80 | 6 | $parameters->setFirstOnly(true); |
|
81 | 23 | } else if (is_array($arg)) { |
|
82 | 6 | foreach ($arg as $field => $value) { |
|
83 | 6 | $parameters->addFilter($field, $value); |
|
84 | } |
||
85 | } |
||
86 | |||
87 | 27 | return $parameters; |
|
88 | } |
||
89 | |||
90 | /** |
||
91 | * |
||
92 | * @return \ntentan\nibii\QueryParameters |
||
93 | */ |
||
94 | 33 | private function getQueryParameters($instantiate = true) { |
|
95 | 33 | if ($this->queryParameters === null && $instantiate) { |
|
96 | 33 | $this->queryParameters = new QueryParameters($this->wrapper->getDBStoreInformation()['quoted_table']); |
|
97 | } |
||
98 | 33 | return $this->queryParameters; |
|
99 | } |
||
100 | |||
101 | 33 | private function resetQueryParameters() { |
|
102 | 33 | $this->queryParameters = null; |
|
103 | 33 | } |
|
104 | |||
105 | 10 | public function doFetchFirst($id = null) { |
|
106 | 10 | $this->getQueryParameters()->setFirstOnly(true); |
|
107 | 10 | return $this->doFetch($id); |
|
108 | } |
||
109 | |||
110 | 12 | public function doFields() { |
|
111 | 12 | $fields = []; |
|
112 | 12 | $arguments = func_get_args(); |
|
113 | 12 | foreach ($arguments as $argument) { |
|
114 | 12 | if (is_array($argument)) { |
|
115 | 6 | $fields = array_merge($fields, $argument); |
|
116 | } else { |
||
117 | 6 | $fields[] = $argument; |
|
118 | } |
||
119 | } |
||
120 | 12 | $this->getQueryParameters()->setFields($fields); |
|
121 | 12 | return $this->wrapper; |
|
122 | } |
||
123 | |||
124 | public function doSortBy($field, $direction = 'ASC') { |
||
125 | $this->getQueryParameters()->addSort($field, $direction); |
||
126 | } |
||
127 | |||
128 | 10 | private function getFilter($arguments) { |
|
129 | 10 | if (count($arguments) == 2 && is_array($arguments[1])) { |
|
130 | 2 | $filter = $arguments[0]; |
|
131 | 2 | $data = $arguments[1]; |
|
132 | } else { |
||
133 | 10 | $filter = array_shift($arguments); |
|
134 | 10 | $data = $arguments; |
|
135 | } |
||
136 | 10 | return ['filter' => $filter, 'data' => $data]; |
|
137 | } |
||
138 | |||
139 | 6 | View Code Duplication | public function doFilter() { |
|
|||
140 | 6 | $arguments = func_get_args(); |
|
141 | 6 | $details = $this->getFilter($arguments); |
|
142 | 6 | $this->getQueryParameters()->setFilter($details['filter'], $details['data']); |
|
143 | 6 | return $this->wrapper; |
|
144 | } |
||
145 | |||
146 | 4 | View Code Duplication | public function doFilterBy() { |
147 | 4 | $arguments = func_get_args(); |
|
148 | 4 | $details = $this->getFilter($arguments); |
|
149 | 4 | $this->getQueryParameters()->addFilter($details['filter'], $details['data']); |
|
150 | 4 | return $this->wrapper; |
|
151 | } |
||
152 | |||
153 | 6 | public function doUpdate($data) { |
|
154 | 6 | $this->driver->beginTransaction(); |
|
155 | 6 | $parameters = $this->getQueryParameters(); |
|
156 | 6 | $this->adapter->bulkUpdate($data, $parameters); |
|
157 | 6 | $this->driver->commit(); |
|
158 | 6 | $this->resetQueryParameters(); |
|
159 | 6 | } |
|
160 | |||
161 | 2 | public function doDelete($args = null) { |
|
162 | 2 | $this->driver->beginTransaction(); |
|
163 | 2 | $parameters = $this->getFetchQueryParameters($args); |
|
164 | |||
165 | 2 | if ($parameters === null) { |
|
166 | $primaryKey = $this->wrapper->getDescription()->getPrimaryKey(); |
||
167 | $parameters = $this->getQueryParameters(); |
||
168 | $data = $this->wrapper->getData(); |
||
169 | $keys = []; |
||
170 | |||
171 | foreach ($data as $datum) { |
||
172 | if ($this->dataOperations->isItemDeletable($primaryKey, $datum)) { |
||
173 | $keys[] = $datum[$primaryKey[0]]; |
||
174 | } |
||
175 | } |
||
176 | |||
177 | $parameters->addFilter($primaryKey[0], $keys); |
||
178 | $this->adapter->delete($parameters); |
||
179 | } else { |
||
180 | 2 | $this->adapter->delete($parameters); |
|
181 | } |
||
182 | |||
183 | 2 | $this->driver->commit(); |
|
184 | 2 | $this->resetQueryParameters(); |
|
185 | 2 | } |
|
186 | |||
187 | 10 | public function runDynamicMethod($arguments) { |
|
188 | 10 | $arguments = count($arguments) > 1 ? $arguments : $arguments[0]; |
|
189 | 10 | switch ($this->pendingMethod['method']) { |
|
190 | 10 | case 'filterBy': |
|
191 | 4 | $this->getQueryParameters()->addFilter(Text::deCamelize($this->pendingMethod['variable']), $arguments); |
|
192 | 4 | return $this->wrapper; |
|
193 | 8 | case 'sort': |
|
194 | $this->getQueryParameters()->addSort(Text::deCamelize($this->pendingMethod['variable']), $this->pendingMethod['direction']); |
||
195 | return $this->wrapper; |
||
196 | 8 | case 'fetch': |
|
197 | 8 | $parameters = $this->getQueryParameters(); |
|
198 | 8 | $parameters->addFilter(Text::deCamelize($this->pendingMethod['variable']), $arguments); |
|
199 | 8 | if ($this->pendingMethod['first'] === 'First') { |
|
200 | 8 | $parameters->setFirstOnly(true); |
|
201 | } |
||
202 | 8 | return $this->doFetch(); |
|
203 | } |
||
204 | } |
||
205 | |||
206 | 10 | public function initDynamicMethod($method) { |
|
219 | |||
220 | public function doCount() { |
||
223 | |||
224 | public function doLimit($numItems) { |
||
228 | |||
229 | public function doOffset($offset) { |
||
233 | |||
234 | public function doWith($model) { |
||
235 | $relationship = $this->wrapper->getDescription()->getRelationships()[$model]; |
||
236 | return $relationship->getQuery(); |
||
237 | } |
||
238 | |||
239 | } |
||
240 |
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.