validator = $validator; return $this; } public function setSerializer(SerializerInterface $serializer): self { $this->serializer = $serializer; return $this; } public function setApiServer(ApiServer $server): self { $this->apiServer = $server; return $this; } /** * This will return a response with code 400. Usage example: * return $this->createBadRequestResponse('Unable to access this page!'); * * @param string $message A message * * @return Response */ public function createBadRequestResponse(string $message = 'Bad Request.'): Response { return new Response($message, 400); } /** * This will return an error response. Usage example: * return $this->createErrorResponse(new UnauthorizedHttpException()); * * @param HttpException $exception An HTTP exception * * @return Response */ public function createErrorResponse(HttpException $exception): Response { $statusCode = $exception->getStatusCode(); $headers = array_merge($exception->getHeaders(), ['Content-Type' => 'application/json']); $json = $this->exceptionToArray($exception); $json['statusCode'] = $statusCode; return new Response(json_encode($json, 15), $statusCode, $headers); } /** * Serializes data to a given type format. * * @param mixed $data The data to serialize. * @param string $format The target serialization format. * * @return string A serialized data string. */ protected function serialize($data, string $format): string { return $this->serializer->serialize($data, $format); } /** * Deserializes data from a given type format. * * @param mixed $data The data to deserialize. * @param string $class The target data class. * @param string $format The source serialization format. * * @return mixed A deserialized data. */ protected function deserialize($data, string $class, string $format) { return $this->serializer->deserialize($data, $class, $format); } /** * @param mixed $data * @param mixed $asserts * * @return Response|null */ protected function validate($data, $asserts = null): ?Response { $errors = $this->validator->validate($data, $asserts); if (count($errors) > 0) { $errorsString = ''; /** @var ConstraintViolation $violation */ foreach ($errors as $violation) { $errorsString .= $violation->getMessage()."\n"; } return $this->createBadRequestResponse($errorsString); } return null; } /** * Converts an exception to a serializable array. * * @param \Throwable|null $exception * * @return array|null */ private function exceptionToArray(?\Throwable $exception = null): ?array { if (null === $exception) { return null; } if (!$this->container->get('kernel')->isDebug()) { return [ 'message' => $exception->getMessage(), ]; } return [ 'message' => $exception->getMessage(), 'type' => get_class($exception), 'previous' => $this->exceptionToArray($exception->getPrevious()), ]; } /** * Converts an exception to a serializable array. * * @param string $accept * @param array $produced * * @return ?string */ protected function getOutputFormat(string $accept, array $produced): ?string { // First get the list of formats accepted by the client by weight. // eg: text/html,*/*; q=0.7, text/*;q=0.8,text/plain;format=fixed;q=0.6 is turned to // [ // text/html => 1, // because when no weight is present then the default value is 1. See https://httpwg.org/specs/rfc9110.html#quality.values . // */* => 0.7, // text/* => 0.8, // text/plain => 0.6, // ] // So we can subsequently order that list by descending weight (because 1 is the most prefered value, 0.001 is the least preferred) $weightedFormats = array(); foreach (explode(",", str_replace(' ', '', $accept)) as $accept) { $exploded = explode(';', $accept); // If no weight is present then the default value is 1 (see https://httpwg.org/specs/rfc9110.html#quality.values ) if (count($exploded) === 1) { $weight = 1.0; } else { $lastItem = end($exploded); if (str_starts_with($lastItem, "q=")) { $weight = (float) str_replace("q=", "", $lastItem); } else { $weight = 1.0; } } $weightedFormats[$exploded[0]] = $weight; } arsort($weightedFormats); // Now return the first produced format that matches foreach (array_keys($weightedFormats) as $acceptedFormat) { $acceptedFormatParts = explode('/', $acceptedFormat); if (count($acceptedFormatParts) != 2) { // badly formatted header sent by the client. Let's continue (instead of crashing) continue; } $acceptedFormatType = $acceptedFormatParts[0]; $acceptedFormatSubtype = $acceptedFormatParts[1]; foreach ($produced as $producedFormat) { if ($acceptedFormat === $producedFormat) { return $producedFormat; } if ($acceptedFormatSubtype === '*' && $acceptedFormatType === explode("/", $producedFormat)[0]) { return $producedFormat; } if ($acceptedFormat === "*/*") { return $producedFormat; } } } // If we reach this point, we don't have a common ground between server and client return null; } /** * Checks whether Content-Type request header presented in supported formats. * * @param Request $request Request instance. * @param array $consumes Array of supported content types. * * @return bool Returns true if Content-Type supported otherwise false. */ public static function isContentTypeAllowed(Request $request, array $consumes = []): bool { if (!empty($consumes) && $consumes[0] !== '*/*') { $currentFormat = $request->getContentTypeFormat(); foreach ($consumes as $mimeType) { // canonize mime type if (is_string($mimeType) && false !== $pos = strpos($mimeType, ';')) { $mimeType = trim(substr($mimeType, 0, $pos)); } if (!$format = $request->getFormat($mimeType)) { // add custom format to request $format = $mimeType; $request->setFormat($format, $format); $currentFormat = $request->getContentTypeFormat(); } if ($format === $currentFormat) { return true; } } return false; } return true; } }