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 |
||
35 | class AccountMapper extends Mapper { |
||
36 | |||
37 | /* @var IConfig */ |
||
38 | protected $config; |
||
39 | |||
40 | /* @var AccountTermMapper */ |
||
41 | protected $termMapper; |
||
42 | |||
43 | public function __construct(IConfig $config, IDBConnection $db, AccountTermMapper $termMapper) { |
||
44 | parent::__construct($db, 'accounts', Account::class); |
||
45 | $this->config = $config; |
||
46 | $this->termMapper = $termMapper; |
||
47 | } |
||
48 | |||
49 | /** |
||
50 | * Delegate to term mapper to avoid needing to inject term mapper |
||
51 | * @param $account_id |
||
52 | * @param array $terms |
||
53 | */ |
||
54 | public function setTermsForAccount($account_id, array $terms) { |
||
55 | $this->termMapper->setTermsForAccount($account_id, $terms); |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * Delegate to term mapper to avoid needing to inject term mapper |
||
60 | * @param $account_id |
||
61 | * @return AccountTerm[] $terms |
||
62 | */ |
||
63 | public function findByAccountId($account_id) { |
||
64 | return $this->termMapper->findByAccountId($account_id); |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * @param Account $entity |
||
69 | * @return Entity the saved entity with the set id |
||
70 | */ |
||
71 | public function insert(Entity $entity) { |
||
72 | // run the normal entity insert operation to get an id |
||
73 | $entity = parent::insert($entity); |
||
74 | |||
75 | /** @var Account $entity */ |
||
76 | if ($entity->haveTermsChanged()) { |
||
77 | $this->termMapper->setTermsForAccount($entity->getId(), $entity->getSearchTerms()); |
||
78 | } |
||
79 | return $entity; |
||
80 | } |
||
81 | |||
82 | /** |
||
83 | * @param Account $entity |
||
84 | * @return Entity the deleted entity |
||
85 | */ |
||
86 | public function delete(Entity $entity) { |
||
87 | // First delete the search terms for this account |
||
88 | $this->termMapper->deleteTermsForAccount($entity->getId()); |
||
89 | return parent::delete($entity); |
||
90 | } |
||
91 | |||
92 | /** |
||
93 | * @param Account $entity |
||
94 | * @return Entity the updated entity |
||
95 | */ |
||
96 | public function update(Entity $entity) { |
||
97 | if ($entity->haveTermsChanged()) { |
||
|
|||
98 | $this->termMapper->setTermsForAccount($entity->getId(), $entity->getSearchTerms()); |
||
99 | } |
||
100 | // Then run the normal entity insert operation |
||
101 | return parent::update($entity); |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * @param string $email |
||
106 | * @return Account[] |
||
107 | */ |
||
108 | public function getByEmail($email) { |
||
109 | if ($email === null || trim($email) === '') { |
||
110 | throw new \InvalidArgumentException('$email must be defined'); |
||
111 | } |
||
112 | $qb = $this->db->getQueryBuilder(); |
||
113 | // RFC 5321 says that only domain name is case insensitive, but in practice |
||
114 | // it's the whole email |
||
115 | $qb->select('*') |
||
116 | ->from($this->getTableName()) |
||
117 | ->where($qb->expr()->eq($qb->createFunction('LOWER(`email`)'), $qb->createFunction('LOWER(' . $qb->createNamedParameter($email) . ')'))); |
||
118 | |||
119 | return $this->findEntities($qb->getSQL(), $qb->getParameters()); |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * @param string $uid |
||
124 | * @throws DoesNotExistException if the account does not exist |
||
125 | * @throws MultipleObjectsReturnedException if more than one account exists |
||
126 | * @return Account |
||
127 | */ |
||
128 | View Code Duplication | public function getByUid($uid) { |
|
129 | $qb = $this->db->getQueryBuilder(); |
||
130 | $qb->select('*') |
||
131 | ->from($this->getTableName()) |
||
132 | ->where($qb->expr()->eq('lower_user_id', $qb->createNamedParameter(strtolower($uid)))); |
||
133 | |||
134 | return $this->findEntity($qb->getSQL(), $qb->getParameters()); |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * @param string $fieldName |
||
139 | * @param string $pattern |
||
140 | * @param integer $limit |
||
141 | * @param integer $offset |
||
142 | * @return Account[] |
||
143 | */ |
||
144 | public function search($fieldName, $pattern, $limit, $offset) { |
||
145 | $qb = $this->db->getQueryBuilder(); |
||
146 | $qb->select('*') |
||
147 | ->from($this->getTableName()) |
||
148 | ->where($qb->expr()->iLike($fieldName, $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%'))) |
||
149 | ->orderBy($fieldName); |
||
150 | |||
151 | return $this->findEntities($qb->getSQL(), $qb->getParameters(), $limit, $offset); |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * @param string $pattern |
||
156 | * @param integer $limit |
||
157 | * @param integer $offset |
||
158 | * @return Account[] |
||
159 | */ |
||
160 | public function find($pattern, $limit = null, $offset = null) { |
||
161 | |||
162 | $allowMedialSearches = $this->config->getSystemValue('accounts.enable_medial_search', true); |
||
163 | if ($allowMedialSearches) { |
||
164 | $parameter = '%' . $this->db->escapeLikeParameter($pattern) . '%'; |
||
165 | $loweredParameter = '%' . $this->db->escapeLikeParameter(strtolower($pattern)) . '%'; |
||
166 | } else { |
||
167 | $parameter = $this->db->escapeLikeParameter($pattern) . '%'; |
||
168 | $loweredParameter = $this->db->escapeLikeParameter(strtolower($pattern)) . '%'; |
||
169 | } |
||
170 | |||
171 | $qb = $this->db->getQueryBuilder(); |
||
172 | $qb->selectAlias('DISTINCT a.id', 'id') |
||
173 | ->addSelect(['user_id', 'lower_user_id', 'display_name', 'email', 'last_login', 'backend', 'state', 'quota', 'home']) |
||
174 | ->from($this->getTableName(), 'a') |
||
175 | ->leftJoin('a', 'account_terms', 't', $qb->expr()->eq('a.id', 't.account_id')) |
||
176 | ->orderBy('display_name') |
||
177 | ->where($qb->expr()->like('lower_user_id', $qb->createNamedParameter($loweredParameter))) |
||
178 | ->orWhere($qb->expr()->iLike('display_name', $qb->createNamedParameter($parameter))) |
||
179 | ->orWhere($qb->expr()->iLike('email', $qb->createNamedParameter($parameter))) |
||
180 | ->orWhere($qb->expr()->like('t.term', $qb->createNamedParameter($loweredParameter))); |
||
181 | |||
182 | return $this->findEntities($qb->getSQL(), $qb->getParameters(), $limit, $offset); |
||
183 | } |
||
184 | |||
185 | public function getUserCountPerBackend($hasLoggedIn) { |
||
186 | $qb = $this->db->getQueryBuilder(); |
||
187 | $qb->select(['backend', $qb->createFunction('count(*) as `count`')]) |
||
188 | ->from($this->getTableName()) |
||
189 | ->groupBy('backend'); |
||
190 | |||
191 | if ($hasLoggedIn) { |
||
192 | $qb->where($qb->expr()->gt('last_login', new Literal(0))); |
||
193 | } |
||
194 | |||
195 | $result = $qb->execute(); |
||
196 | $data = $result->fetchAll(); |
||
197 | $result->closeCursor(); |
||
198 | |||
199 | $return = []; |
||
200 | foreach ($data as $d) { |
||
201 | $return[$d['backend']] = $d['count']; |
||
202 | } |
||
203 | |||
204 | return $return; |
||
205 | } |
||
206 | |||
207 | public function getUserCount($hasLoggedIn) { |
||
208 | $qb = $this->db->getQueryBuilder(); |
||
209 | $qb->select([$qb->createFunction('count(*) as `count`')]) |
||
210 | ->from($this->getTableName()); |
||
211 | |||
212 | if ($hasLoggedIn) { |
||
213 | $qb->where($qb->expr()->gt('last_login', new Literal(0))); |
||
214 | } |
||
215 | |||
216 | $result = $qb->execute(); |
||
217 | $data = $result->fetch(); |
||
218 | $result->closeCursor(); |
||
219 | |||
220 | return (int) $data['count']; |
||
221 | } |
||
222 | |||
223 | public function callForAllUsers($callback, $search, $onlySeen) { |
||
245 | |||
246 | /** |
||
247 | * @param string $backend |
||
248 | * @param bool $hasLoggedIn |
||
249 | * @param integer $limit |
||
250 | * @param integer $offset |
||
251 | * @return string[] |
||
252 | */ |
||
253 | public function findUserIds($backend = null, $hasLoggedIn = null, $limit = null, $offset = null) { |
||
254 | $qb = $this->db->getQueryBuilder(); |
||
255 | $qb->select('user_id') |
||
256 | ->from($this->getTableName()) |
||
257 | ->orderBy('user_id'); // needed for predictable limit & offset |
||
279 | |||
280 | } |
||
281 |
If you implement
__call
and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.This is often the case, when
__call
is implemented by a parent class and only the child class knows which methods exist: