repository = $taskRepository; $this->builder = $builder; } // response is immutable. psr-7 impl makes copies of stuff. // TODO: There's a LOT of validation here. We need to build a validator, use a library, or refactor this method. public function addTask(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { $params = $request->getParsedBody(); $requiredKeys = ['name', 'description', 'status', 'start', 'end']; if (!array_key_exists('tasks', $params)) { $this->builder->setError(); $this->builder->setErrorMessage('Malformed request payload. Please try again.'); $this->builder->setErrorCode(400); $errorResponse = $response->withStatus(400, 'Invalid payload')->withHeader('Content-Type', 'application/json'); $errorResponse->getBody()->write($this->builder->build()); return $errorResponse; } $missingKeys = []; foreach ($requiredKeys as $key) { if (!array_key_exists($key, $params['tasks'])) { $missingKeys[] = $key; } } if (!empty($missingKeys)) { $this->builder->setError(); $this->builder->setErrorMessage("The following parameters are missing: " . implode(', ', $missingKeys)); $this->builder->setErrorCode(400); $errorResponse = $response->withStatus('400', 'Missing arguments')->withHeader('Content-Type', 'application/json'); $errorResponse->getBody()->write($this->builder->build()); return $errorResponse; } try { $start = Carbon::parse($params['tasks']['start']); $end = Carbon::parse($params['tasks']['end']); if ($start->gt($end) || $end->lt($start)) { throw new InvalidTaskDateException('Invalid task date. Start date must not be after end date.'); } } catch (InvalidFormatException | \DomainException $exception) { $this->builder->setError(); $this->builder->setErrorMessage($exception->getMessage()); $this->builder->setErrorCode(400); $errorResponse = $response->withStatus(400)->withHeader('Content-Type', 'application/json'); $errorResponse->getBody()->write($this->builder->build()); return $errorResponse; } if (is_null(TaskStatus::tryFrom($params['tasks']['status']))) { $this->builder->setError(); $this->builder->setErrorCode(400); $this->builder->setErrorMessage('Invalid task status code. Status must range from 1-4 (started, in progress, blocked, completed).'); $errorResponse = $response->withStatus(400)->withHeader('Content-Type', 'application/json'); $errorResponse->getBody()->write($this->builder->build()); return $errorResponse; } $task = new Task(); $task->setName($params['tasks']['name']) ->setStatusId(TaskStatus::tryFrom((int) $params['tasks']['status'])) ->setDescription($params['tasks']['description']) ->setStart($params['tasks']['start']) ->setEnd($params['tasks']['end']); $createdTask = $this->repository->create($task); if ($createdTask == 0) { $this->builder->setError() ->setErrorMessage('An unexpected error has occurred whilst trying to perform this operation.') ->setErrorCode(500); $errorResponse = $response->withStatus(500)->withHeader('Content-Type', 'application/json'); $errorResponse->getBody()->write($this->builder->build()); return $errorResponse; } $createdResponse = $response->withStatus(201)->withHeader('Content-Type', 'application/json'); $this->builder->setOptionalMessage('Task created.') ->setPayload($task->setId($createdTask)->persist()); $createdResponse->getBody()->write($this->builder->build()); return $createdResponse; } public function getTasks(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { $tasks = $this->repository->readAll(); $payload['tasks'] = $tasks; $this->builder->setPayload($payload)->setOptionalMessage('Listing all tasks: '); $response->getBody()->write($this->builder->build()); return $response; } public function getTask(ServerRequestInterface $request, ResponseInterface $response, string $id): ResponseInterface { try { $payload['task'] = $this->repository->readById((int)$id)->persist(); $this->builder->setPayload($payload)->setOptionalMessage("Task retrieved."); $response->getBody()->write($this->builder->build()); return $response; } catch (TaskNotFoundException $e) { $this->builder->setError()->setErrorMessage($e->getMessage())->setErrorCode(404); $errorResponse = $response->withStatus(404, $e->getMessage()); $response->getBody()->write($this->builder->build()); return $errorResponse; } } public function delete(ServerRequestInterface $request, ResponseInterface $response, $id): ResponseInterface { try { $task = $this->repository->readById((int) $id); $this->repository->delete($task); $this->builder->setOptionalMessage('Task deleted.'); $response->getBody()->write($this->builder->build()); return $response; } catch (TaskNotFoundException $exception) { $this->builder->setError()->setErrorMessage($exception->getMessage())->setErrorCode(404); $errorResponse = $response->withStatus(404); $errorResponse->getBody()->write($this->builder->build()); return $errorResponse; } } public function update(ServerRequestInterface $request, ResponseInterface $response, $id): ResponseInterface { $payloadWarnings = []; $toUpdate = $request->getParsedBody()['payload']['update']; $toUpdate['updated_at'] = Carbon::now()->toDateTimeString(); $acceptableFields = [ 'task_owner', 'name', 'description', 'start', 'end' ]; foreach ($toUpdate as $updateField => $value) { if($updateField !== 'updated_at' && !in_array($updateField, $acceptableFields)) { $payloadWarnings[] = "W: An invalid, unknown, or locked field has been truncated: " . $updateField; unset($toUpdate[$updateField]); } } try { $task = $this->repository->readById($id); // if we used the dirty implementation, we could skip all this code foreach($toUpdate as $property => $value) { // ignore updated_at if($property !== 'updated_at') { $task->{'set' . ucfirst(snakeToCamel($property))}($value); } } $finalFields = array_keys($toUpdate); $result = $this->repository->updateSelective($task, $finalFields); if ($result && empty($payloadWarnings)) { return $response->withStatus(204); } elseif($result) { $this->builder->setOptionalMessage('Task updated successfully with warnings.') ->setPayload([ 'updated' => $toUpdate, 'warnings' => $payloadWarnings, ]); $response->getBody()->write($this->builder->build()); return $response; } else { $this->builder->setError() ->setErrorMessage('An unexpected error occurred while updating the resource.') ->setErrorCode(500); $errorResponse = $response->withStatus(500)->withHeader('Content-Type', 'application/json'); $errorResponse->getBody()->write($this->builder->build()); return $errorResponse; } } catch (TaskNotFoundException $e) { $this->builder->setError() ->setErrorMessage($e->getMessage()) ->setErrorCode(404); $errorResponse = $response->withStatus(404)->withHeader('Content-Type', 'application/json'); $errorResponse->getBody()->write($this->builder->build()); return $errorResponse; } } }