Total Complexity | 46 |
Total Lines | 309 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like ConferenceActivity 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 ConferenceActivity, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | #[ApiResource( |
||
20 | operations: [ |
||
21 | new Post( |
||
22 | uriTemplate: '/videoconference/callback', |
||
23 | controller: VideoConferenceCallbackController::class, |
||
24 | read: false, |
||
25 | deserialize: false, |
||
26 | validate: false |
||
27 | ), |
||
28 | ] |
||
29 | )] |
||
30 | #[ORM\Entity(repositoryClass: ConferenceActivityRepository::class)] |
||
31 | #[ORM\Table(name: 'conference_activity')] |
||
32 | class ConferenceActivity |
||
33 | { |
||
34 | #[ORM\Id] |
||
35 | #[ORM\GeneratedValue] |
||
36 | #[ORM\Column(type: 'integer')] |
||
37 | protected int $id; |
||
38 | |||
39 | #[ORM\ManyToOne(targetEntity: ConferenceMeeting::class)] |
||
40 | #[ORM\JoinColumn(name: 'meeting_id', referencedColumnName: 'id', onDelete: 'CASCADE')] |
||
41 | protected ?ConferenceMeeting $meeting = null; |
||
42 | |||
43 | #[ORM\ManyToOne(targetEntity: User::class)] |
||
44 | #[ORM\JoinColumn(name: 'participant_id', referencedColumnName: 'id', onDelete: 'CASCADE')] |
||
45 | protected ?User $participant = null; |
||
46 | |||
47 | #[ORM\Column(name: 'in_at', type: 'datetime', nullable: true)] |
||
48 | protected ?DateTime $inAt = null; |
||
49 | |||
50 | #[ORM\Column(name: 'out_at', type: 'datetime', nullable: true)] |
||
51 | protected ?DateTime $outAt = null; |
||
52 | |||
53 | #[ORM\Column(name: 'close', type: 'boolean')] |
||
54 | protected bool $close = false; |
||
55 | |||
56 | #[ORM\Column(name: 'type', type: 'string', length: 50)] |
||
57 | protected string $type = ''; |
||
58 | |||
59 | #[ORM\Column(name: 'event', type: 'string', length: 255)] |
||
60 | protected string $event = ''; |
||
61 | |||
62 | #[ORM\Column(name: 'activity_data', type: 'text', nullable: true)] |
||
63 | protected ?string $activityData = null; |
||
64 | |||
65 | #[ORM\Column(name: 'signature_file', type: 'string', length: 255, nullable: true)] |
||
66 | protected ?string $signatureFile = null; |
||
67 | |||
68 | #[ORM\Column(name: 'signed_at', type: 'datetime', nullable: true)] |
||
69 | protected ?DateTime $signedAt = null; |
||
70 | |||
71 | /** Stores per-user analytics for the meeting (dashboard metrics). */ |
||
72 | #[ORM\Column(name: 'metrics', type: 'json', nullable: true)] |
||
73 | protected ?array $metrics = null; |
||
74 | |||
75 | public function __construct() |
||
76 | { |
||
77 | $this->close = false; |
||
78 | $this->type = ''; |
||
79 | $this->event = ''; |
||
80 | $this->activityData = null; |
||
81 | $this->signatureFile = null; |
||
82 | $this->inAt = new DateTime(); |
||
83 | $this->outAt = null; |
||
84 | $this->signedAt = null; |
||
85 | $this->metrics = null; |
||
86 | } |
||
87 | |||
88 | public function getId(): int |
||
89 | { |
||
90 | return $this->id; |
||
91 | } |
||
92 | |||
93 | public function getMeeting(): ?ConferenceMeeting |
||
94 | { |
||
95 | return $this->meeting; |
||
96 | } |
||
97 | |||
98 | public function setMeeting(?ConferenceMeeting $meeting): self |
||
99 | { |
||
100 | $this->meeting = $meeting; |
||
101 | |||
102 | return $this; |
||
103 | } |
||
104 | |||
105 | public function getParticipant(): ?User |
||
106 | { |
||
107 | return $this->participant; |
||
108 | } |
||
109 | |||
110 | public function setParticipant(?User $participant): self |
||
111 | { |
||
112 | $this->participant = $participant; |
||
113 | |||
114 | return $this; |
||
115 | } |
||
116 | |||
117 | public function getInAt(): ?DateTime |
||
118 | { |
||
119 | return $this->inAt; |
||
120 | } |
||
121 | |||
122 | public function setInAt(?DateTime $inAt): self |
||
123 | { |
||
124 | $this->inAt = $inAt; |
||
125 | |||
126 | return $this; |
||
127 | } |
||
128 | |||
129 | public function getOutAt(): ?DateTime |
||
130 | { |
||
131 | return $this->outAt; |
||
132 | } |
||
133 | |||
134 | public function setOutAt(?DateTime $outAt): self |
||
135 | { |
||
136 | $this->outAt = $outAt; |
||
137 | |||
138 | return $this; |
||
139 | } |
||
140 | |||
141 | public function isClose(): bool |
||
144 | } |
||
145 | |||
146 | public function setClose(bool $close): self |
||
147 | { |
||
148 | $this->close = $close; |
||
149 | |||
150 | return $this; |
||
151 | } |
||
152 | |||
153 | public function getType(): string |
||
154 | { |
||
155 | return $this->type; |
||
156 | } |
||
157 | |||
158 | public function setType(string $type): self |
||
159 | { |
||
160 | $this->type = $type; |
||
161 | |||
162 | return $this; |
||
163 | } |
||
164 | |||
165 | public function getEvent(): string |
||
166 | { |
||
167 | return $this->event; |
||
168 | } |
||
169 | |||
170 | public function setEvent(string $event): self |
||
171 | { |
||
172 | $this->event = $event; |
||
173 | |||
174 | return $this; |
||
175 | } |
||
176 | |||
177 | public function getActivityData(): ?string |
||
178 | { |
||
179 | return $this->activityData; |
||
180 | } |
||
181 | |||
182 | public function setActivityData(?string $activityData): self |
||
183 | { |
||
184 | $this->activityData = $activityData; |
||
185 | |||
186 | return $this; |
||
187 | } |
||
188 | |||
189 | public function getSignatureFile(): ?string |
||
190 | { |
||
191 | return $this->signatureFile; |
||
192 | } |
||
193 | |||
194 | public function setSignatureFile(?string $signatureFile): self |
||
195 | { |
||
196 | $this->signatureFile = $signatureFile; |
||
197 | |||
198 | return $this; |
||
199 | } |
||
200 | |||
201 | public function getSignedAt(): ?DateTime |
||
202 | { |
||
203 | return $this->signedAt; |
||
204 | } |
||
205 | |||
206 | public function setSignedAt(?DateTime $signedAt): self |
||
207 | { |
||
208 | $this->signedAt = $signedAt; |
||
209 | |||
210 | return $this; |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * Returns the full metrics array (never null to simplify callers). |
||
215 | * Keep in mind: this method does NOT create defaults; it just returns stored data. |
||
216 | */ |
||
217 | public function getMetrics(): array |
||
218 | { |
||
219 | return $this->metrics ?? []; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Replaces the entire metrics array. Null or empty arrays will store NULL to keep DB small. |
||
224 | */ |
||
225 | public function setMetrics(?array $metrics): self |
||
226 | { |
||
227 | $this->metrics = $metrics ? $this->pruneEmpty($metrics) : null; |
||
228 | |||
229 | return $this; |
||
230 | } |
||
231 | |||
232 | /** Read a value from metrics by dot path, with a default if missing. */ |
||
233 | public function getMetric(string $path, mixed $default = null): mixed |
||
244 | } |
||
245 | |||
246 | /** Set a value into metrics by dot path, creating nested arrays as needed. */ |
||
247 | public function setMetric(string $path, mixed $value): self |
||
248 | { |
||
249 | $metrics = $this->getMetrics(); |
||
250 | $ref =& $metrics; |
||
251 | |||
252 | $parts = $path === '' ? [] : explode('.', $path); |
||
253 | foreach ($parts as $seg) { |
||
254 | if (!isset($ref[$seg]) || !is_array($ref[$seg])) { |
||
255 | $ref[$seg] = []; |
||
256 | } |
||
257 | $ref =& $ref[$seg]; |
||
258 | } |
||
259 | |||
260 | $ref = $value; |
||
261 | |||
262 | return $this->setMetrics($metrics); |
||
263 | } |
||
264 | |||
265 | /** Increment an integer metric by dot path (initializes to 0 if missing). */ |
||
266 | public function incMetric(string $path, int $by = 1): self |
||
267 | { |
||
268 | $current = (int) $this->getMetric($path, 0); |
||
269 | |||
270 | return $this->setMetric($path, $current + $by); |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * Start a named timer: stores ISO timestamp under "timers.{key}.on_at". |
||
275 | * Timer is idempotent (won't overwrite if already running). |
||
276 | */ |
||
277 | public function startTimer(string $key, ?\DateTimeInterface $now = null): self |
||
278 | { |
||
279 | $now ??= new \DateTimeImmutable(); |
||
280 | |||
281 | if (!$this->getMetric("timers.$key.on_at")) { |
||
282 | $this->setMetric("timers.$key.on_at", $now->format(DATE_ATOM)); |
||
283 | } |
||
284 | |||
285 | return $this; |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Stop a named timer and add elapsed seconds to "totals.{key}_seconds". |
||
290 | * If the timer is not running, this is a no-op. |
||
291 | */ |
||
292 | public function stopTimer(string $key, ?\DateTimeInterface $now = null): self |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Internal helper to remove null/empty leaves so JSON column stays lean. |
||
310 | * This is called from setMetrics(). |
||
311 | */ |
||
312 | private function pruneEmpty(array $data): array |
||
313 | { |
||
314 | foreach ($data as $k => $v) { |
||
315 | if (is_array($v)) { |
||
316 | $v = $this->pruneEmpty($v); |
||
317 | if ($v === []) { |
||
318 | unset($data[$k]); |
||
319 | continue; |
||
320 | } |
||
321 | $data[$k] = $v; |
||
322 | } elseif ($v === null || $v === '') { |
||
323 | unset($data[$k]); |
||
324 | } |
||
325 | } |
||
326 | |||
330 |