uuid = $this->generateUuid(); $this->status = TaskStatus::PENDING; $this->type = TaskType::HUMAN_TASK; $this->createdAt = new \DateTimeImmutable(); $this->updatedAt = new \DateTimeImmutable(); } public static function create( string $title, string $createdBy, ?string $description = null, ?TaskType $type = null, ?int $parentTaskId = null, ?\DateTimeImmutable $dueDate = null ): self { $task = new self(); $task->title = $title; $task->createdBy = $createdBy; $task->description = $description; if ($type !== null) { $task->type = $type; } if ($parentTaskId !== null) { $task->parentTaskId = $parentTaskId; } if ($dueDate !== null) { $task->dueDate = $dueDate; } return $task; } private function generateUuid(): string { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) ); } // Getters public function getId(): ?int { return $this->id; } public function getUuid(): string { return $this->uuid; } public function getTitle(): string { return $this->title; } public function getDescription(): ?string { return $this->description; } public function getType(): TaskType { return $this->type; } public function getStatus(): TaskStatus { return $this->status; } public function getCreatedBy(): string { return $this->createdBy; } public function getCreatedByType(): string { return $this->createdByType; } public function getParentTaskId(): ?int { return $this->parentTaskId; } public function getDueDate(): ?\DateTimeImmutable { return $this->dueDate; } public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } public function getUpdatedAt(): \DateTimeImmutable { return $this->updatedAt; } public function getCompletedAt(): ?\DateTimeImmutable { return $this->completedAt; } public function getMetadata(): array { return $this->metadata; } // Business Methods public function updateDetails(string $title, ?string $description = null): self { $this->title = $title; $this->description = $description; $this->touch(); return $this; } public function changeType(TaskType $type): self { $this->type = $type; $this->touch(); return $this; } public function assignParent(int $parentTaskId): self { $this->parentTaskId = $parentTaskId; $this->touch(); return $this; } public function setDueDateTo(\DateTimeImmutable $dueDate): self { $this->dueDate = $dueDate; $this->touch(); return $this; } public function clearDueDate(): self { $this->dueDate = null; $this->touch(); return $this; } public function updateMetadata(array $metadata): self { $this->metadata = $metadata; $this->touch(); return $this; } public function isOverdue(): bool { if ($this->dueDate === null || $this->status->isTerminal()) { return false; } return $this->dueDate < new \DateTimeImmutable(); } public function isSubtask(): bool { return $this->parentTaskId !== null; } // Status Transitions public function start(): self { $this->transitionTo(TaskStatus::IN_PROGRESS); return $this; } public function complete(): self { $this->transitionTo(TaskStatus::COMPLETED); return $this; } public function fail(): self { $this->transitionTo(TaskStatus::FAILED); return $this; } public function cancel(): self { $this->transitionTo(TaskStatus::CANCELLED); return $this; } public function retry(): self { if ($this->status !== TaskStatus::FAILED) { throw new \DomainException('Can only retry failed tasks'); } $this->transitionTo(TaskStatus::PENDING); $this->completedAt = null; return $this; } private function transitionTo(TaskStatus $newStatus): void { if (!$this->status->canTransitionTo($newStatus)) { throw new \DomainException( sprintf('Cannot transition from %s to %s', $this->status->value, $newStatus->value) ); } $this->status = $newStatus; $this->touch(); if ($newStatus->isTerminal()) { $this->completedAt = new \DateTimeImmutable(); } } private function touch(): void { $this->updatedAt = new \DateTimeImmutable(); } // Internal Setters for Hydration public function setId(int $id): self { $this->id = $id; return $this; } public function hydrate(array $data): self { if (isset($data['uuid'])) { $this->uuid = $data['uuid']; } if (isset($data['title'])) { $this->title = $data['title']; } if (isset($data['description'])) { $this->description = $data['description']; } if (isset($data['type'])) { $this->type = is_string($data['type']) ? TaskType::from($data['type']) : $data['type']; } if (isset($data['status'])) { $this->status = is_string($data['status']) ? TaskStatus::from($data['status']) : $data['status']; } if (isset($data['created_by'])) { $this->createdBy = $data['created_by']; } if (isset($data['created_by_type'])) { $this->createdByType = $data['created_by_type']; } if (isset($data['parent_task_id'])) { $this->parentTaskId = (int) $data['parent_task_id']; } if (isset($data['due_date'])) { $this->dueDate = $data['due_date'] instanceof \DateTimeImmutable ? $data['due_date'] : new \DateTimeImmutable($data['due_date']); } if (isset($data['created_at'])) { $this->createdAt = $data['created_at'] instanceof \DateTimeImmutable ? $data['created_at'] : new \DateTimeImmutable($data['created_at']); } if (isset($data['updated_at'])) { $this->updatedAt = $data['updated_at'] instanceof \DateTimeImmutable ? $data['updated_at'] : new \DateTimeImmutable($data['updated_at']); } if (isset($data['completed_at'])) { $this->completedAt = $data['completed_at'] instanceof \DateTimeImmutable ? $data['completed_at'] : new \DateTimeImmutable($data['completed_at']); } if (isset($data['metadata'])) { $this->metadata = is_string($data['metadata']) ? json_decode($data['metadata'], true) : $data['metadata']; } return $this; } public function toArray(): array { return [ 'id' => $this->id, 'uuid' => $this->uuid, 'title' => $this->title, 'description' => $this->description, 'type' => $this->type, 'status' => $this->status->value, 'created_by' => $this->createdBy, 'created_by_type' => $this->createdByType, 'parent_task_id' => $this->parentTaskId, 'due_date' => $this->dueDate?->format('Y-m-d H:i:s'), 'created_at' => $this->createdAt->format('Y-m-d H:i:s.u'), 'updated_at' => $this->updatedAt->format('Y-m-d H:i:s.u'), 'completed_at' => $this->completedAt?->format('Y-m-d H:i:s.u'), 'metadata' => $this->metadata, ]; } public static function fromArray(array $data): self { $task = new self(); if (isset($data['id'])) { $task->setId((int) $data['id']); } if (isset($data['uuid'])) { $task->setUuid($data['uuid']); } if (isset($data['title'])) { $task->setTitle($data['title']); } if (isset($data['description'])) { $task->setDescription($data['description']); } if (isset($data['type'])) { $task->setType($data['type']); } if (isset($data['status'])) { $task->status = TaskStatus::from($data['status']); } if (isset($data['created_by'])) { $task->setCreatedBy($data['created_by']); } if (isset($data['created_by_type'])) { $task->setCreatedByType($data['created_by_type']); } if (isset($data['parent_task_id'])) { $task->setParentTaskId((int) $data['parent_task_id']); } if (isset($data['due_date'])) { $task->setDueDate(new \DateTimeImmutable($data['due_date'])); } if (isset($data['created_at'])) { $task->setCreatedAt(new \DateTimeImmutable($data['created_at'])); } if (isset($data['updated_at'])) { $task->setUpdatedAt(new \DateTimeImmutable($data['updated_at'])); } if (isset($data['completed_at'])) { $task->setCompletedAt(new \DateTimeImmutable($data['completed_at'])); } if (isset($data['metadata'])) { $metadata = is_string($data['metadata']) ? json_decode($data['metadata'], true) : $data['metadata']; $task->setMetadata($metadata ?? []); } return $task; } }