| Total Complexity | 90 |
| Total Lines | 605 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like LineItem 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 LineItem, and based on these observations, apply Extract Interface, too.
| 1 | <?php declare(strict_types=1); |
||
| 19 | #[Package('checkout')] |
||
| 20 | class LineItem extends Struct |
||
| 21 | { |
||
| 22 | final public const CREDIT_LINE_ITEM_TYPE = 'credit'; |
||
| 23 | final public const PRODUCT_LINE_ITEM_TYPE = 'product'; |
||
| 24 | final public const CUSTOM_LINE_ITEM_TYPE = 'custom'; |
||
| 25 | final public const PROMOTION_LINE_ITEM_TYPE = 'promotion'; |
||
| 26 | final public const DISCOUNT_LINE_ITEM = 'discount'; |
||
| 27 | final public const CONTAINER_LINE_ITEM = 'container'; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * @var array<mixed> |
||
| 31 | */ |
||
| 32 | protected array $payload = []; |
||
| 33 | |||
| 34 | protected ?string $label = null; |
||
| 35 | |||
| 36 | protected int $quantity; |
||
| 37 | |||
| 38 | protected ?PriceDefinitionInterface $priceDefinition = null; |
||
| 39 | |||
| 40 | protected ?CalculatedPrice $price = null; |
||
| 41 | |||
| 42 | protected bool $good = true; |
||
| 43 | |||
| 44 | protected ?string $description = null; |
||
| 45 | |||
| 46 | protected ?MediaEntity $cover = null; |
||
| 47 | |||
| 48 | protected ?DeliveryInformation $deliveryInformation = null; |
||
| 49 | |||
| 50 | protected LineItemCollection $children; |
||
| 51 | |||
| 52 | protected ?Rule $requirement = null; |
||
| 53 | |||
| 54 | protected bool $removable = false; |
||
| 55 | |||
| 56 | protected bool $stackable = false; |
||
| 57 | |||
| 58 | protected ?QuantityInformation $quantityInformation = null; |
||
| 59 | |||
| 60 | protected bool $modified = false; |
||
| 61 | |||
| 62 | /** |
||
| 63 | * The data timestamp can be used to record when the line item was last updated with data from the database. |
||
| 64 | * Updating the data timestamp must be done by the corresponding cart data collector. |
||
| 65 | */ |
||
| 66 | protected ?\DateTimeInterface $dataTimestamp = null; |
||
| 67 | |||
| 68 | /** |
||
| 69 | * Data data context hash can be used, like the data timestamp, to check if the line item was calculated with the same |
||
| 70 | * context hash or not |
||
| 71 | */ |
||
| 72 | protected ?string $dataContextHash = null; |
||
| 73 | |||
| 74 | /** |
||
| 75 | * Used as a unique id to identify a line item over multiple nested levels |
||
| 76 | */ |
||
| 77 | protected string $uniqueIdentifier; |
||
| 78 | |||
| 79 | /** |
||
| 80 | * @var array<int, string> |
||
| 81 | */ |
||
| 82 | protected array $states = []; |
||
| 83 | |||
| 84 | protected bool $modifiedByApp = false; |
||
| 85 | |||
| 86 | /** |
||
| 87 | * @var array<string, bool> |
||
| 88 | */ |
||
| 89 | private array $payloadProtection = []; |
||
| 90 | |||
| 91 | /** |
||
| 92 | * @throws CartException |
||
| 93 | */ |
||
| 94 | public function __construct( |
||
| 95 | protected string $id, |
||
| 96 | protected string $type, |
||
| 97 | protected ?string $referencedId = null, |
||
| 98 | int $quantity = 1 |
||
| 99 | ) { |
||
| 100 | $this->uniqueIdentifier = Uuid::randomHex(); |
||
| 101 | $this->children = new LineItemCollection(); |
||
| 102 | |||
| 103 | if ($quantity < 1) { |
||
| 104 | throw CartException::invalidQuantity($quantity); |
||
| 105 | } |
||
| 106 | $this->quantity = $quantity; |
||
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * @throws CartException |
||
| 111 | */ |
||
| 112 | public static function createFromLineItem(LineItem $lineItem): self |
||
| 113 | { |
||
| 114 | $self = new self($lineItem->id, $lineItem->type, $lineItem->getReferencedId(), $lineItem->quantity); |
||
| 115 | |||
| 116 | foreach (get_object_vars($lineItem) as $property => $value) { |
||
| 117 | $self->$property = $value; /* @phpstan-ignore-line */ |
||
| 118 | } |
||
| 119 | |||
| 120 | return $self; |
||
| 121 | } |
||
| 122 | |||
| 123 | public function getId(): string |
||
| 124 | { |
||
| 125 | return $this->id; |
||
| 126 | } |
||
| 127 | |||
| 128 | public function setId(string $id): self |
||
| 129 | { |
||
| 130 | $this->id = $id; |
||
| 131 | |||
| 132 | return $this; |
||
| 133 | } |
||
| 134 | |||
| 135 | public function getReferencedId(): ?string |
||
| 136 | { |
||
| 137 | return $this->referencedId; |
||
| 138 | } |
||
| 139 | |||
| 140 | public function setReferencedId(?string $referencedId): self |
||
| 145 | } |
||
| 146 | |||
| 147 | public function getLabel(): ?string |
||
| 148 | { |
||
| 149 | return $this->label; |
||
| 150 | } |
||
| 151 | |||
| 152 | public function setLabel(?string $label): self |
||
| 153 | { |
||
| 154 | $this->label = $label; |
||
| 155 | |||
| 156 | return $this; |
||
| 157 | } |
||
| 158 | |||
| 159 | public function getQuantity(): int |
||
| 160 | { |
||
| 161 | return $this->quantity; |
||
| 162 | } |
||
| 163 | |||
| 164 | /** |
||
| 165 | * @throws CartException |
||
| 166 | */ |
||
| 167 | public function setQuantity(int $quantity): self |
||
| 168 | { |
||
| 169 | if ($quantity < 1) { |
||
| 170 | throw CartException::invalidQuantity($quantity); |
||
| 171 | } |
||
| 172 | |||
| 173 | if (!$this->isStackable()) { |
||
| 174 | throw CartException::lineItemNotStackable($this->id); |
||
| 175 | } |
||
| 176 | |||
| 177 | if ($this->hasChildren()) { |
||
| 178 | $this->refreshChildQuantity($this->children, $this->quantity, $quantity); |
||
| 179 | } |
||
| 180 | |||
| 181 | if ($this->priceDefinition instanceof QuantityPriceDefinition) { |
||
| 182 | $this->price = null; |
||
| 183 | } |
||
| 184 | |||
| 185 | $this->quantity = $quantity; |
||
| 186 | |||
| 187 | return $this; |
||
| 188 | } |
||
| 189 | |||
| 190 | public function getType(): string |
||
| 191 | { |
||
| 192 | return $this->type; |
||
| 193 | } |
||
| 194 | |||
| 195 | public function setType(string $type): self |
||
| 196 | { |
||
| 197 | $this->type = $type; |
||
| 198 | |||
| 199 | return $this; |
||
| 200 | } |
||
| 201 | |||
| 202 | /** |
||
| 203 | * @return array<string, mixed> |
||
| 204 | */ |
||
| 205 | public function getPayload(): array |
||
| 206 | { |
||
| 207 | return $this->payload; |
||
| 208 | } |
||
| 209 | |||
| 210 | /** |
||
| 211 | * @return mixed|null |
||
| 212 | */ |
||
| 213 | public function getPayloadValue(string $key) |
||
| 214 | { |
||
| 215 | if (!$this->hasPayloadValue($key)) { |
||
| 216 | return null; |
||
| 217 | } |
||
| 218 | |||
| 219 | return $this->payload[$key]; |
||
| 220 | } |
||
| 221 | |||
| 222 | public function hasPayloadValue(string $key): bool |
||
| 223 | { |
||
| 224 | return isset($this->payload[$key]); |
||
| 225 | } |
||
| 226 | |||
| 227 | /** |
||
| 228 | * @throws CartException |
||
| 229 | */ |
||
| 230 | public function removePayloadValue(string $key): void |
||
| 231 | { |
||
| 232 | if (!$this->hasPayloadValue($key)) { |
||
| 233 | throw CartException::payloadKeyNotFound($key, $this->getId()); |
||
| 234 | } |
||
| 235 | unset($this->payload[$key]); |
||
| 236 | unset($this->payloadProtection[$key]); |
||
| 237 | } |
||
| 238 | |||
| 239 | /** |
||
| 240 | * @deprecated tag:v6.6.0 - reason:new-optional-parameter - Parameter $protected will be added in v6.6.0 |
||
| 241 | * |
||
| 242 | * @param mixed|null $value |
||
| 243 | * |
||
| 244 | * @throws CartException |
||
| 245 | */ |
||
| 246 | public function setPayloadValue(string $key, $value/* , ?bool $protected = null */): self |
||
| 247 | { |
||
| 248 | $protected = false; |
||
| 249 | if (\func_num_args() === 3) { |
||
| 250 | $protected = func_get_arg(2); |
||
| 251 | } |
||
| 252 | |||
| 253 | if ($value !== null && !\is_scalar($value) && !\is_array($value)) { |
||
| 254 | throw CartException::invalidPayload($key, $this->getId()); |
||
| 255 | } |
||
| 256 | |||
| 257 | $this->payload[$key] = $value; |
||
| 258 | |||
| 259 | if ($protected !== null) { |
||
| 260 | $this->addPayloadProtection([$key => $protected]); |
||
| 261 | } |
||
| 262 | |||
| 263 | return $this; |
||
| 264 | } |
||
| 265 | |||
| 266 | /** |
||
| 267 | * @deprecated tag:v6.6.0 - reason:new-optional-parameter - Parameter $protection will be added in v6.6.0 |
||
| 268 | * |
||
| 269 | * @param array<string, mixed> $payload |
||
| 270 | * |
||
| 271 | * @throws CartException |
||
| 272 | */ |
||
| 273 | public function setPayload(array $payload/* , array $protection = [] */): self |
||
| 274 | { |
||
| 275 | $protection = []; |
||
| 276 | if (\func_num_args() === 2) { |
||
| 277 | $protection = func_get_arg(1); |
||
| 278 | } |
||
| 279 | |||
| 280 | foreach ($payload as $key => $value) { |
||
| 281 | if (\is_string($key)) { |
||
| 282 | $this->setPayloadValue($key, $value); |
||
| 283 | |||
| 284 | continue; |
||
| 285 | } |
||
| 286 | |||
| 287 | throw CartException::invalidPayload((string) $key, $this->getId()); |
||
| 288 | } |
||
| 289 | |||
| 290 | return $this->addPayloadProtection($protection); |
||
| 291 | } |
||
| 292 | |||
| 293 | /** |
||
| 294 | * @param array<string, bool> $protection |
||
| 295 | */ |
||
| 296 | public function addPayloadProtection(array $protection): self |
||
| 297 | { |
||
| 298 | $this->payloadProtection = \array_replace($this->payloadProtection, $protection); |
||
| 299 | |||
| 300 | return $this; |
||
| 301 | } |
||
| 302 | |||
| 303 | /** |
||
| 304 | * @deprecated tag:v6.6.0 - reason:new-optional-parameter - Parameter $protection will be added in v6.6.0 |
||
| 305 | * |
||
| 306 | * @param array<string, mixed> $payload |
||
| 307 | */ |
||
| 308 | public function replacePayload(array $payload/* , array $protection = [] */): self |
||
| 309 | { |
||
| 310 | $protection = []; |
||
| 311 | if (\func_num_args() === 2) { |
||
| 312 | $protection = func_get_arg(1); |
||
| 313 | } |
||
| 314 | |||
| 315 | $this->payload = \array_replace_recursive($this->payload, $payload); |
||
| 316 | |||
| 317 | return $this->addPayloadProtection($protection); |
||
| 318 | } |
||
| 319 | |||
| 320 | public function getPriceDefinition(): ?PriceDefinitionInterface |
||
| 321 | { |
||
| 322 | return $this->priceDefinition; |
||
| 323 | } |
||
| 324 | |||
| 325 | public function setPriceDefinition(?PriceDefinitionInterface $priceDefinition): self |
||
| 326 | { |
||
| 327 | $this->priceDefinition = $priceDefinition; |
||
| 328 | |||
| 329 | return $this; |
||
| 330 | } |
||
| 331 | |||
| 332 | public function getPrice(): ?CalculatedPrice |
||
| 333 | { |
||
| 334 | return $this->price; |
||
| 335 | } |
||
| 336 | |||
| 337 | public function setPrice(?CalculatedPrice $price): self |
||
| 338 | { |
||
| 339 | $this->price = $price; |
||
| 340 | |||
| 341 | return $this; |
||
| 342 | } |
||
| 343 | |||
| 344 | public function isGood(): bool |
||
| 345 | { |
||
| 346 | return $this->good; |
||
| 347 | } |
||
| 348 | |||
| 349 | public function setGood(bool $good): self |
||
| 350 | { |
||
| 351 | $this->good = $good; |
||
| 352 | |||
| 353 | return $this; |
||
| 354 | } |
||
| 355 | |||
| 356 | public function getDescription(): ?string |
||
| 357 | { |
||
| 358 | return $this->description; |
||
| 359 | } |
||
| 360 | |||
| 361 | public function setDescription(?string $description): self |
||
| 362 | { |
||
| 363 | $this->description = $description; |
||
| 364 | |||
| 365 | return $this; |
||
| 366 | } |
||
| 367 | |||
| 368 | public function getCover(): ?MediaEntity |
||
| 369 | { |
||
| 370 | return $this->cover; |
||
| 371 | } |
||
| 372 | |||
| 373 | public function setCover(?MediaEntity $cover): self |
||
| 378 | } |
||
| 379 | |||
| 380 | public function getDeliveryInformation(): ?DeliveryInformation |
||
| 381 | { |
||
| 382 | return $this->deliveryInformation; |
||
| 383 | } |
||
| 384 | |||
| 385 | public function setDeliveryInformation(?DeliveryInformation $deliveryInformation): self |
||
| 386 | { |
||
| 387 | $this->deliveryInformation = $deliveryInformation; |
||
| 388 | |||
| 389 | return $this; |
||
| 390 | } |
||
| 391 | |||
| 392 | public function getChildren(): LineItemCollection |
||
| 393 | { |
||
| 394 | return $this->children; |
||
| 395 | } |
||
| 396 | |||
| 397 | public function setChildren(LineItemCollection $children): self |
||
| 398 | { |
||
| 399 | foreach ($children as $child) { |
||
| 400 | $this->validateChildQuantity($child); |
||
| 401 | } |
||
| 402 | $this->children = $children; |
||
| 403 | |||
| 404 | return $this; |
||
| 405 | } |
||
| 406 | |||
| 407 | public function hasChildren(): bool |
||
| 408 | { |
||
| 409 | return $this->children->count() > 0; |
||
| 410 | } |
||
| 411 | |||
| 412 | /** |
||
| 413 | * @throws CartException |
||
| 414 | */ |
||
| 415 | public function addChild(LineItem $child): self |
||
| 416 | { |
||
| 417 | $this->validateChildQuantity($child); |
||
| 418 | $this->children->add($child); |
||
| 419 | |||
| 420 | return $this; |
||
| 421 | } |
||
| 422 | |||
| 423 | public function setRequirement(?Rule $requirement): LineItem |
||
| 424 | { |
||
| 425 | $this->requirement = $requirement; |
||
| 426 | |||
| 427 | return $this; |
||
| 428 | } |
||
| 429 | |||
| 430 | public function getRequirement(): ?Rule |
||
| 431 | { |
||
| 432 | return $this->requirement; |
||
| 433 | } |
||
| 434 | |||
| 435 | public function isRemovable(): bool |
||
| 436 | { |
||
| 437 | return $this->removable; |
||
| 438 | } |
||
| 439 | |||
| 440 | public function setRemovable(bool $removable): LineItem |
||
| 441 | { |
||
| 442 | $this->removable = $removable; |
||
| 443 | |||
| 444 | return $this; |
||
| 445 | } |
||
| 446 | |||
| 447 | public function isStackable(): bool |
||
| 448 | { |
||
| 449 | return $this->stackable; |
||
| 450 | } |
||
| 451 | |||
| 452 | public function setStackable(bool $stackable): LineItem |
||
| 453 | { |
||
| 454 | $this->stackable = $stackable; |
||
| 455 | |||
| 456 | return $this; |
||
| 457 | } |
||
| 458 | |||
| 459 | public function getQuantityInformation(): ?QuantityInformation |
||
| 460 | { |
||
| 461 | return $this->quantityInformation; |
||
| 462 | } |
||
| 463 | |||
| 464 | public function setQuantityInformation(?QuantityInformation $quantityInformation): LineItem |
||
| 465 | { |
||
| 466 | $this->quantityInformation = $quantityInformation; |
||
| 467 | |||
| 468 | return $this; |
||
| 469 | } |
||
| 470 | |||
| 471 | public function isModified(): bool |
||
| 472 | { |
||
| 473 | return $this->modified; |
||
| 474 | } |
||
| 475 | |||
| 476 | public function markModified(): void |
||
| 477 | { |
||
| 478 | $this->modified = true; |
||
| 479 | } |
||
| 480 | |||
| 481 | public function markUnmodified(): void |
||
| 482 | { |
||
| 483 | $this->modified = false; |
||
| 484 | } |
||
| 485 | |||
| 486 | public function getApiAlias(): string |
||
| 487 | { |
||
| 488 | return 'cart_line_item'; |
||
| 489 | } |
||
| 490 | |||
| 491 | /** |
||
| 492 | * @see LineItem::$dataTimestamp |
||
| 493 | */ |
||
| 494 | public function getDataTimestamp(): ?\DateTimeInterface |
||
| 495 | { |
||
| 496 | return $this->dataTimestamp; |
||
| 497 | } |
||
| 498 | |||
| 499 | /** |
||
| 500 | * @see LineItem::$dataTimestamp |
||
| 501 | */ |
||
| 502 | public function setDataTimestamp(?\DateTimeInterface $dataTimestamp): void |
||
| 503 | { |
||
| 504 | $this->dataTimestamp = $dataTimestamp; |
||
| 505 | } |
||
| 506 | |||
| 507 | /** |
||
| 508 | * @see LineItem::$dataContextHash |
||
| 509 | */ |
||
| 510 | public function getDataContextHash(): ?string |
||
| 511 | { |
||
| 512 | return $this->dataContextHash; |
||
| 513 | } |
||
| 514 | |||
| 515 | /** |
||
| 516 | * @see LineItem::$dataContextHash |
||
| 517 | */ |
||
| 518 | public function setDataContextHash(?string $dataContextHash): void |
||
| 519 | { |
||
| 520 | $this->dataContextHash = $dataContextHash; |
||
| 521 | } |
||
| 522 | |||
| 523 | public function getUniqueIdentifier(): string |
||
| 526 | } |
||
| 527 | |||
| 528 | /** |
||
| 529 | * @return array<int, string> |
||
| 530 | */ |
||
| 531 | public function getStates(): array |
||
| 532 | { |
||
| 533 | return $this->states; |
||
| 534 | } |
||
| 535 | |||
| 536 | /** |
||
| 537 | * @param array<int, string> $states |
||
| 538 | */ |
||
| 539 | public function setStates(array $states): LineItem |
||
| 540 | { |
||
| 541 | $this->states = $states; |
||
| 542 | |||
| 543 | return $this; |
||
| 544 | } |
||
| 545 | |||
| 546 | public function hasState(string $state): bool |
||
| 547 | { |
||
| 548 | return \in_array($state, $this->states, true); |
||
| 549 | } |
||
| 550 | |||
| 551 | public function markUnModifiedByApp(): void |
||
| 552 | { |
||
| 553 | $this->modifiedByApp = false; |
||
| 554 | } |
||
| 555 | |||
| 556 | public function markModifiedByApp(): void |
||
| 557 | { |
||
| 558 | $this->modifiedByApp = true; |
||
| 559 | } |
||
| 560 | |||
| 561 | public function isModifiedByApp(): bool |
||
| 562 | { |
||
| 563 | return $this->modifiedByApp; |
||
| 564 | } |
||
| 565 | |||
| 566 | public function jsonSerialize(): array |
||
| 581 | } |
||
| 582 | |||
| 583 | /** |
||
| 584 | * @throws CartException |
||
| 585 | */ |
||
| 586 | private function refreshChildQuantity( |
||
| 587 | LineItemCollection $lineItems, |
||
| 588 | int $oldParentQuantity, |
||
| 589 | int $newParentQuantity |
||
| 590 | ): void { |
||
| 591 | foreach ($lineItems as $lineItem) { |
||
| 592 | $newQuantity = intdiv($lineItem->getQuantity(), $oldParentQuantity) * $newParentQuantity; |
||
| 593 | |||
| 594 | if ($lineItem->hasChildren()) { |
||
| 595 | $this->refreshChildQuantity($lineItem->getChildren(), $lineItem->getQuantity(), $newQuantity); |
||
| 596 | } |
||
| 597 | |||
| 598 | $lineItem->quantity = $newQuantity; |
||
| 599 | $priceDefinition = $lineItem->getPriceDefinition(); |
||
| 602 | } |
||
| 603 | } |
||
| 604 | } |
||
| 605 | |||
| 606 | /** |
||
| 607 | * @throws CartException |
||
| 608 | */ |
||
| 609 | private function validateChildQuantity(LineItem $child): void |
||
| 624 | } |
||
| 625 | } |
||
| 626 | } |
||
| 627 |