Total Complexity | 52 |
Total Lines | 290 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like EventLoggerSubscriber 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.
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 EventLoggerSubscriber, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
52 | class EventLoggerSubscriber implements EventSubscriber |
||
53 | { |
||
54 | /** |
||
55 | * @var array The given fields will not be saved, because they contain sensitive informations |
||
56 | */ |
||
57 | protected const FIELD_BLACKLIST = [ |
||
58 | User::class => ['password', 'need_pw_change', 'googleAuthenticatorSecret', 'backupCodes', 'trustedDeviceCookieVersion', 'pw_reset_token', 'backupCodesGenerationDate'], |
||
59 | ]; |
||
60 | |||
61 | /** |
||
62 | * @var array If elements of the given class are deleted, a log for the given fields will be triggered |
||
63 | */ |
||
64 | protected const TRIGGER_ASSOCIATION_LOG_WHITELIST = [ |
||
65 | PartLot::class => ['part'], |
||
66 | Orderdetail::class => ['part'], |
||
67 | Pricedetail::class => ['orderdetail'], |
||
68 | Attachment::class => ['element'], |
||
69 | AbstractParameter::class => ['element'], |
||
70 | ]; |
||
71 | |||
72 | protected const MAX_STRING_LENGTH = 2000; |
||
73 | |||
74 | protected EventLogger $logger; |
||
75 | protected SerializerInterface $serializer; |
||
76 | protected EventCommentHelper $eventCommentHelper; |
||
77 | protected EventUndoHelper $eventUndoHelper; |
||
78 | protected bool $save_changed_fields; |
||
79 | protected bool $save_changed_data; |
||
80 | protected bool $save_removed_data; |
||
81 | protected PropertyAccessorInterface $propertyAccessor; |
||
82 | |||
83 | public function __construct(EventLogger $logger, SerializerInterface $serializer, EventCommentHelper $commentHelper, |
||
96 | } |
||
97 | |||
98 | public function onFlush(OnFlushEventArgs $eventArgs): void |
||
99 | { |
||
100 | $em = $eventArgs->getEntityManager(); |
||
|
|||
101 | $uow = $em->getUnitOfWork(); |
||
102 | |||
103 | /* |
||
104 | * Log changes and deletions of entites. |
||
105 | * We can not log persist here, because the entities do not have IDs yet... |
||
106 | */ |
||
107 | |||
108 | foreach ($uow->getScheduledEntityUpdates() as $entity) { |
||
109 | if ($this->validEntity($entity)) { |
||
110 | $this->logElementEdited($entity, $em); |
||
111 | } |
||
112 | } |
||
113 | |||
114 | foreach ($uow->getScheduledEntityDeletions() as $entity) { |
||
115 | if ($this->validEntity($entity)) { |
||
116 | $this->logElementDeleted($entity, $em); |
||
117 | } |
||
118 | } |
||
119 | |||
120 | /* Do not call $uow->computeChangeSets() in this function, only individual entities should be computed! |
||
121 | * Otherwise we will run into very strange issues, that some entity changes are no longer updated! |
||
122 | * This is almost impossible to debug, because it only happens in some cases, and it looks very unrelated to |
||
123 | * this code (which caused the problem and which took me very long time to find out). |
||
124 | * So just do not call $uow->computeChangeSets() here ever, even if it is tempting!! |
||
125 | * If you need to log something from inside here, just call logFromOnFlush() instead of the normal log() function. |
||
126 | */ |
||
127 | } |
||
128 | |||
129 | public function postPersist(LifecycleEventArgs $args): void |
||
154 | } |
||
155 | } |
||
156 | |||
157 | public function postFlush(PostFlushEventArgs $args): void |
||
158 | { |
||
159 | $em = $args->getEntityManager(); |
||
160 | $uow = $em->getUnitOfWork(); |
||
161 | // If the we have added any ElementCreatedLogEntries added in postPersist, we flush them here. |
||
162 | $uow->computeChangeSets(); |
||
163 | if ($uow->hasPendingInsertions() || !empty($uow->getScheduledEntityUpdates())) { |
||
164 | $em->flush(); |
||
165 | } |
||
166 | |||
167 | //Clear the message provided by user. |
||
168 | $this->eventCommentHelper->clearMessage(); |
||
169 | $this->eventUndoHelper->clearUndoneEvent(); |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Check if the given element class has restrictions to its fields. |
||
174 | * |
||
175 | * @return bool True if there are restrictions, and further checking is needed |
||
176 | */ |
||
177 | public function hasFieldRestrictions(AbstractDBElement $element): bool |
||
178 | { |
||
179 | foreach (array_keys(static::FIELD_BLACKLIST) as $class) { |
||
180 | if (is_a($element, $class)) { |
||
181 | return true; |
||
182 | } |
||
183 | } |
||
184 | |||
185 | return false; |
||
186 | } |
||
187 | |||
188 | /** |
||
189 | * Checks if the field of the given element should be saved (if it is not blacklisted). |
||
190 | */ |
||
191 | public function shouldFieldBeSaved(AbstractDBElement $element, string $field_name): bool |
||
192 | { |
||
193 | foreach (static::FIELD_BLACKLIST as $class => $blacklist) { |
||
194 | if (is_a($element, $class) && in_array($field_name, $blacklist, true)) { |
||
195 | return false; |
||
196 | } |
||
197 | } |
||
198 | |||
199 | //By default allow every field. |
||
200 | return true; |
||
201 | } |
||
202 | |||
203 | public function getSubscribedEvents(): array |
||
204 | { |
||
205 | return[ |
||
206 | Events::onFlush, |
||
207 | Events::postPersist, |
||
208 | Events::postFlush, |
||
209 | ]; |
||
210 | } |
||
211 | |||
212 | protected function logElementDeleted(AbstractDBElement $entity, EntityManagerInterface $em): void |
||
213 | { |
||
214 | $log = new ElementDeletedLogEntry($entity); |
||
215 | //Add user comment to log entry |
||
216 | if ($this->eventCommentHelper->isMessageSet()) { |
||
217 | $log->setComment($this->eventCommentHelper->getMessage()); |
||
218 | } |
||
219 | if ($this->eventUndoHelper->isUndo()) { |
||
220 | $log->setUndoneEvent($this->eventUndoHelper->getUndoneEvent(), $this->eventUndoHelper->getMode()); |
||
221 | } |
||
222 | if ($this->save_removed_data) { |
||
223 | //The 4th param is important here, as we delete the element... |
||
224 | $this->saveChangeSet($entity, $log, $em, true); |
||
225 | } |
||
226 | $this->logger->logFromOnFlush($log); |
||
227 | |||
228 | //Check if we have to log CollectionElementDeleted entries |
||
229 | if ($this->save_changed_data) { |
||
230 | $metadata = $em->getClassMetadata(get_class($entity)); |
||
231 | $mappings = $metadata->getAssociationMappings(); |
||
232 | //Check if class is whitelisted for CollectionElementDeleted entry |
||
233 | foreach (static::TRIGGER_ASSOCIATION_LOG_WHITELIST as $class => $whitelist) { |
||
234 | if (is_a($entity, $class)) { |
||
235 | //Check names |
||
236 | foreach ($mappings as $field => $mapping) { |
||
237 | if (in_array($field, $whitelist, true)) { |
||
238 | $changed = $this->propertyAccessor->getValue($entity, $field); |
||
239 | $log = new CollectionElementDeleted($changed, $mapping['inversedBy'], $entity); |
||
240 | if ($this->eventUndoHelper->isUndo()) { |
||
241 | $log->setUndoneEvent($this->eventUndoHelper->getUndoneEvent(), $this->eventUndoHelper->getMode()); |
||
242 | } |
||
243 | $this->logger->logFromOnFlush($log); |
||
244 | } |
||
245 | } |
||
246 | } |
||
247 | } |
||
248 | } |
||
249 | } |
||
250 | |||
251 | protected function logElementEdited(AbstractDBElement $entity, EntityManagerInterface $em): void |
||
252 | { |
||
253 | $uow = $em->getUnitOfWork(); |
||
254 | |||
255 | /* We have to call that here again, so the foreign entity timestamps, that were changed in updateTimestamp |
||
256 | get persisted */ |
||
257 | $changeSet = $uow->getEntityChangeSet($entity); |
||
258 | |||
259 | //Skip log entry, if only the lastModified field has changed... |
||
260 | if (isset($changeSet['lastModified']) && count($changeSet)) { |
||
261 | return; |
||
262 | } |
||
263 | |||
264 | $log = new ElementEditedLogEntry($entity); |
||
265 | if ($this->save_changed_data) { |
||
266 | $this->saveChangeSet($entity, $log, $em); |
||
267 | } elseif ($this->save_changed_fields) { |
||
268 | $changed_fields = array_keys($uow->getEntityChangeSet($entity)); |
||
269 | //Remove lastModified field, as this is always changed (gives us no additional info) |
||
270 | $changed_fields = array_diff($changed_fields, ['lastModified']); |
||
271 | $log->setChangedFields($changed_fields); |
||
272 | } |
||
273 | //Add user comment to log entry |
||
274 | if ($this->eventCommentHelper->isMessageSet()) { |
||
275 | $log->setComment($this->eventCommentHelper->getMessage()); |
||
276 | } |
||
277 | if ($this->eventUndoHelper->isUndo()) { |
||
278 | $log->setUndoneEvent($this->eventUndoHelper->getUndoneEvent(), $this->eventUndoHelper->getMode()); |
||
279 | } |
||
280 | $this->logger->logFromOnFlush($log); |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Filter out every forbidden field and return the cleaned array. |
||
285 | */ |
||
286 | protected function filterFieldRestrictions(AbstractDBElement $element, array $fields): array |
||
287 | { |
||
288 | unset($fields['lastModified']); |
||
289 | |||
290 | if (!$this->hasFieldRestrictions($element)) { |
||
291 | return $fields; |
||
292 | } |
||
293 | |||
294 | return array_filter($fields, function ($value, $key) use ($element) { |
||
295 | //Associative array (save changed data) case |
||
296 | if (is_string($key)) { |
||
297 | return $this->shouldFieldBeSaved($element, $key); |
||
298 | } |
||
299 | |||
300 | return $this->shouldFieldBeSaved($element, $value); |
||
301 | }, ARRAY_FILTER_USE_BOTH); |
||
302 | } |
||
303 | |||
304 | protected function saveChangeSet(AbstractDBElement $entity, AbstractLogEntry $logEntry, EntityManagerInterface $em, bool $element_deleted = false): void |
||
305 | { |
||
306 | $uow = $em->getUnitOfWork(); |
||
307 | |||
308 | if (!$logEntry instanceof ElementEditedLogEntry && !$logEntry instanceof ElementDeletedLogEntry) { |
||
309 | throw new \InvalidArgumentException('$logEntry must be ElementEditedLogEntry or ElementDeletedLogEntry!'); |
||
310 | } |
||
311 | |||
312 | if ($element_deleted) { //If the element was deleted we can use getOriginalData to save its content |
||
313 | $old_data = $uow->getOriginalEntityData($entity); |
||
314 | } else { //Otherwise we have to get it from entity changeset |
||
315 | $changeSet = $uow->getEntityChangeSet($entity); |
||
316 | $old_data = array_combine(array_keys($changeSet), array_column($changeSet, 0)); |
||
317 | } |
||
318 | $old_data = $this->filterFieldRestrictions($entity, $old_data); |
||
319 | |||
320 | //Restrict length of string fields, to save memory... |
||
321 | $old_data = array_map( |
||
322 | static function ($value) { |
||
323 | if (is_string($value)) { |
||
324 | return mb_strimwidth($value, 0, self::MAX_STRING_LENGTH, '...'); |
||
325 | } |
||
326 | |||
327 | return $value; |
||
328 | }, $old_data); |
||
329 | |||
330 | $logEntry->setOldData($old_data); |
||
331 | } |
||
332 | |||
333 | /** |
||
334 | * Check if the given entity can be logged. |
||
335 | * |
||
336 | * @return bool true, if the given entity can be logged |
||
337 | */ |
||
338 | protected function validEntity(object $entity): bool |
||
342 | } |
||
343 | } |
||
344 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.