9 Kommentare // Lesezeit: 17 min.
Hinweis: Dieser Artikel ist Teil einer Serie. Der erste Artikel befasste sich mit der Erstellung eines ersten Symfony 2-Projekts, der zweite mit Symfony 2 Formularen.
In diesem Artikel beschreibe ich eine schöne und einfache Möglichkeit, eine REST-API mit dem Symfony-Framework zu implementieren.
Was ist eine REST-API?
REST API ist ein Webdienst, der die REST-Architektur verwendet.
REST = "REpresentational State Transfer"
API = "Application Programming Interface"
Die REST-Architektur ist ressourcenbasiert, d. h. sie konzentriert sich auf Ressourcen und nicht auf Aktionen, d. h. sie ruft (in der URI) eine Ressource auf (in diesem Lernprogramm ist die Ressource "type") und verwendet ein http-Verb (method), um anzugeben, welche Operation sie mit der Ressource durchführen möchte.
REST läuft in der Regel über http und hat mehrere architektonische Beschränkungen:
- Entkopplung der Verbraucher von den Produzenten
- Stateless
- Nutzt einen Cache
- Nutzt ein mehrstufiges System
- Nutzt eine einheitliche Schnittstelle
1. Abhängigkeiten installieren
Eine gute Praxis in der Welt der Programmierung ist es, nicht bei jedem Projekt das Rad neu zu erfinden. - Verwenden Sie den Code wieder. Wenn es einen guten Code (Paket) gibt, der unsere Bedürfnisse erfüllt, sollten wir ihn verwenden. In diesem Projekt habe ich ein cooles Paket verwendet: FOSRest bundle (und 2 weitere), um mir bei der Erstellung einer REST-API zu helfen.
Binden wir also die Bundles über den Composer ein. Fügen Sie diese Zeilen zu "require" Abschnitt Ihrer composer.json Datei:
// File: composer.json
"friendsofsymfony/rest-bundle": "@dev",
"jms/serializer-bundle": "@dev",
"nelmio/api-doc-bundle": "@dev"
Oder Sie können diese Bundles über den Composer-Befehl require hinzufügen.
Wichtiger Hinweis:
Vielleicht haben Sie bemerkt, dass ich '@dev'-Versionen verwendet habe. Verwenden Sie Dev-Versionen nur, wenn Sie keine andere Wahl haben, oder wenn Sie einen guten Grund dafür haben. Sie sollten stattdessen die neueste stabile Version verwenden.
Vergessen Sie nicht, die Bundles zur AppKernel-Klasse hinzuzufügen:
// File: app/AppKernel.php
public function registerBundles() {
$bundles = [
// ...
new FOS\RestBundle\FOSRestBundle(),
new JMS\SerializerBundle\JMSSerializerBundle(),
new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
];
// ...
2. Datenbank erstellen
Sie müssen die Datenbankparameter in app/config/parameters.yaml festlegen, oder Sie können den Befehl composer install ausführen, um die Datei automatisch zu generieren. In jedem Fall müssen Sie die notwendigen Datenbankparameter angeben.
Führen Sie dann diesen Befehl aus:
php app/console doctrine:database:create
# File: app/config/parameters.yml
database_driver pdo_mysql
database_host: xxx
database_port: xxx
database_name: xxx
database_user: xxx
database_password: xxx
mailer_transport: smtp
mailer_host: xxx
mailer_user: xxx
mailer_password: xxx
locale: de
secret: xxx
Braucht Ihr Unternehmen einen Webspezialisten, mit dem es auf Augenhöhe sprechen kann?
3. Erstellen wir unsere Entität
In diesem Artikel werde ich die Entität Type vorstellen und eine REST-API für sie erstellen. Sie ist mit anderen Entitäten verknüpft, aber sie werden hier nicht angezeigt, da der gesamte Prozess der Erstellung einer API für die Entität Type auf jede andere Entität angewendet werden kann.
Typ-Entitätsklassen können mit diesem interaktiven Befehl erstellt werden:
php app/console doctrine:generate:entities TypoScriptBackendBundle:Type
Oder Sie können die php-Datei im Entity-Verzeichnis erstellen.
// File: SGalinski/TypoScriptBackendBundle/Entity/Type.php
<?php
namespace SGalinski\TypoScriptBackendBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Type
*
* @ORM\Table(uniqueConstraints={
* @ORM\UniqueConstraint(name="type_unique", columns={"name", "typo3_group"}),
* @ORM\UniqueConstraint(name="url_name_unique", columns={"url_name", "typo3_group"})
* })
* @ORM\Entity(repositoryClass="SGalinski\TypoScriptBackendBundle\Entity\TypeRepository")
*/
class Type {
/**
* Constant used in $typo3Group field.
*/
const NORMAL_GROUP = 1;
/**
* Constant used in $typo3Group field.
*/
const PAGE_GROUP = 2;
/**
* Constant used in $typo3Group field.
*/
const USER_GROUP = 3;
/**
* Default value of property $minVersion. Default value also needs to be specified in property annotations.
*/
const MIN_VERSION_DEFAULT = "4.5";
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* @var string
*
* @ORM\Column(name="url_name", type="string", length=255)
*/
private $urlName;
/**
* @var string
*
* @ORM\Column(name="description", type="text", nullable=true)
*/
private $description;
/**
* @var string The TYPO3 version in which the type was introduced
* Example: "4.5" "6.1" "6.2"), NULL is interpreted as most early version.
*
* @Assert\Range(
* min = 4.5,
* minMessage = "Lowest supported version is {{ limit }}.",
* )
* @ORM\Column(name="min_version", type="decimal", precision=4, scale=1, options={"default" = "4.5"}, nullable=false)
*/
private $minVersion = Type::MIN_VERSION_DEFAULT;
/**
* @var string Last TYPO3 version in which the type existed.
* A version after which the type was deprecated.
* Example: "4.5" "6.1" "6.2"), NULL is default and is interpreted as latest version.
*
* @Assert\Range(
* min = 4.5,
* minMessage = "Lowest supported version is {{ limit }}.",
* )
* @ORM\Column(name="max_version", type="decimal", precision=4, scale=1, nullable=true)
*/
private $maxVersion = NULL;
/**
* @var integer 3 = USER_GROUP, 2 = PAGE_GROUP, 1 = NORMAL_GROUP
*
* @ORM\Column(name="typo3_group", type="smallint", options={"default" = 1})
*/
private $typo3Group = Type::NORMAL_GROUP;
/**
* Reference to the Type this Type extends.
*
* @var Type
*
* @ORM\ManyToOne(targetEntity="Type")
* @ORM\JoinColumn(name="extends_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $extends;
/**
* @var Category
*
* @ORM\ManyToOne(targetEntity="Category")
* @ORM\JoinColumn(name="category", referencedColumnName="id")
*/
private $category;
/**
* Properties of the Type
*
* @var ArrayCollection
*
* @ORM\OneToMany(targetEntity="Property", mappedBy="parentType")
*/
private $children;
/**
* @var boolean
*
* @ORM\Column(name="deleted", type="boolean")
*/
private $deleted = FALSE;
/**
* Get id
*
* @return integer
*/
public function getId() {
return $this->id;
}
/**
* Set name
*
* @param string $name
* @return Type
*/
public function setName($name) {
$this->name = $name;
$this->urlName = preg_replace('/[^a-z0-9_-]/is', '_', $name);
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName() {
return $this->name;
}
/**
* Get urlName
*
* @return string
*/
public function getUrlName() {
return $this->urlName;
}
/**
* Set urlName
*
* @param string $name
* @return Type
*/
public function setUrlName($name) {
$this->urlName = $name;
return $this;
}
/**
* Set description
*
* @param string $description
* @return Type
*/
public function setDescription($description) {
$this->description = $description;
return $this;
}
/**
* Get description
*
* @return string
*/
public function getDescription() {
return $this->description;
}
/**
* Set minVersion
*
* @param string $minVersion
* @return Type
*/
public function setMinVersion($minVersion) {
$this->minVersion = $minVersion;
return $this;
}
/**
* Get minVersion
*
* @return string
*/
public function getMinVersion() {
return $this->minVersion;
}
/**
* @return string
*/
public function getMaxVersion() {
return $this->maxVersion;
}
/**
* @param string $maxVersion
* @return Type
*/
public function setMaxVersion($maxVersion) {
$this->maxVersion = $maxVersion;
return $this;
}
/**
* Set typo3Group
*
* @param integer $typo3Group
* @return Type
*/
public function setTypo3Group($typo3Group) {
$this->typo3Group = $typo3Group;
return $this;
}
/**
* Get typo3Group
*
* @return integer
*/
public function getTypo3Group() {
return $this->typo3Group;
}
/**
* Set children
*
* @param ArrayCollection $children
* @return Type
*/
public function setChildren($children) {
$this->children = $children;
return $this;
}
/**
* Get children
*
* @return ArrayCollection
*/
public function getChildren() {
return $this->children;
}
/**
* Adds one child to the list.
*
* @param Property $child
*/
public function addChild(Property $child) {
$this->children[] = $child;
}
/**
* Set deleted
*
* @param boolean $deleted
* @return Type
*/
public function setDeleted($deleted) {
$this->deleted = $deleted;
return $this;
}
/**
* Get deleted
*
* @return boolean
*/
public function getDeleted() {
return $this->deleted;
}
/**
* @return Category
*/
public function getCategory() {
return $this->category;
}
/**
* @param Category $category
* @return Type
*/
public function setCategory($category) {
$this->category = $category;
return $this;
}
/**
* @return Type
*/
public function getExtends() {
return $this->extends;
}
/**
* @param Type $extends
* @return Type
*/
public function setExtends($extends) {
$this->extends = $extends;
return $this;
}
}
Führen Sie diesen Befehl aus, um das generierte SQL zu sehen:
php app/console doctrine:schema:update --dump-sql
Wenn Sie mit dem erzeugten SQL zufrieden sind, führen Sie diesen Befehl aus, um die Tabelle in der Datenbank zu erzeugen:
php app/console doctrine:schema:update --force
Wir benötigen auch die Klasse TypeRepository. Sie wird automatisch erstellt, wenn Sie den Befehl ausführen:
php app/console doctrine:generate:entities TypoScriptBackendBundle:Type
Wenn es nicht erstellt wird, machen Sie es händisch.
4. Die Routes und die Konfiguration des FOSRest-Bundles
Erstellen Sie die Datei routes.yaml in Ihrem Bundle und fügen Sie sie in die Haupt-Routing-Datei ein:
# File: SGalinski/TypoScriptBackendBundle/Resources/config/routes.yml
typo_script_backend_typoscript_type:
resource: "SGalinski\TypoScriptBackendBundle\Controller\Typoscript\TypeController"
type: rest
name_prefix: api_typoscript_
prefix: /typoscript
# File: app/config/routing.yml
typo_script_backend:
resource: "@TypoScriptBackendBundle/Resources/config/routes.yml"
type: rest
prefix: /api
// File: app/config/config.yml
fos_rest:
param_fetcher_listener: true
view:
view_response_listener: 'force'
formats:
xml: true
json: true
templating_formats:
html: true
format_listener:
rules:
- { path: ^/, priorities: [ json, xml, html ], fallback_format: ~, prefer_extension: true }
exception:
codes:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT
'SGalinski\TypoScriptBackendBundle\Exception\BadRequestDataException': HTTP_BAD_REQUEST
messages:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': true
'SGalinski\TypoScriptBackendBundle\Exception\BadRequestDataException': true
allowed_methods_listener: true
access_denied_listener:
json: true
body_listener: true
disable_csrf_role: ROLE_API
5. Symfony-Formular
Wir brauchen eine Möglichkeit, die von der API übermittelten Daten zu validieren. Der einfachste Weg dazu ist die Verwendung von Symfony-Formularen. Lassen Sie uns also ein einfaches Symfony-Formular für die Entität Type erstellen:
// File: SGalinski/TypoScriptBackendBundle/Form/TypeType.php
<?php
namespace SGalinski\TypoScriptBackendBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TypeType extends AbstractType {
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('urlName')
->add('description')
->add('extends')
->add('minVersion')
->add('maxVersion')
->add('typo3Group')
->add('deleted')
->add('category');
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(
[
'data_class' => 'SGalinski\TypoScriptBackendBundle\Entity\Type',
'csrf_protection' => false,
]
);
}
/**
* @return string
*/
public function getName() {
return 'sgalinski_typoscriptbackendbundle_type';
}
}
6. REST-Controller
Wir kommen zum wichtigsten Teil - der Implementierung der REST-Methoden.
Um die Leistungsfähigkeit des FOSRest-Bundles zu nutzen, sollte unser TypeController die Klasse FOS\RestBundle\Controller\FOSRestController erweitern:
// File: SGalinski/TypoScriptBackendBundle/Controller/Typoscript/TypeController.php
namespace SGalinski\TypoScriptBackendBundle\Controller\Typoscript;
use FOS\RestBundle\Controller\FOSRestController;
class TypeController extends FOSRestController
Fügen wir dem Controller REST-Methoden hinzu.
6.1 GET-Verfahren
// File: SGalinski/TypoScriptBackendBundle/Controller/Typoscript/TypeController.php
/**
* REST action which returns type by id.
* Method: GET, url: /api/typoscript/types/{id}.{_format}
*
* @ApiDoc(
* resource = true,
* description = "Gets a Type for a given id",
* output = "SGalinski\TypoScriptBackendBundle\Entity\Type",
* statusCodes = {
* 200 = "Returned when successful",
* 404 = "Returned when the page is not found"
* }
* )
*
* @param $id
* @return mixed
*/
public function getTypeAction($id) {
/** @var TypeRepository $typeRepository */
$typeRepository = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:Type');
$type = NULL;
try {
$type = $typeRepository->find($id);
} catch (\Exception $exception) {
$type = NULL;
}
if (!$type) {
throw new NotFoundHttpException(sprintf('The resource \'%s\' was not found.', $id));
}
return $type;
}
getTypeAction erzeugt eine HTTP-GET-Methode für die type Ressource. Es kann von der Konsole (Linux) mit dem Befehl curl getestet werden:
curl -G 127.0.0.1/app.php/api/typoscript/types/1
Standardmäßig wird der Typ im JSON-Format zurückgegeben, aber auch XML wird unterstützt, und das Format kann explizit angegeben werden:
curl -G 127.0.0.1/app.php/api/typoscript/types/1.xml
Was macht diese Methode also? Sie holt die Entität nach ID aus dem Repository (Datenbank). Wenn die Entität nicht gefunden wird, wird die NotFoundHttpException ausgelöst. (Wir werden später in diesem Text über Ausnahmen sprechen.) Wenn die Entität gefunden wird, wird sie zurückgegeben, und das FOSRest-Bündel konvertiert sie automatisch in das gewünschte Format.
6.2 POST-Verfahren
// File: // File: SGalinski/TypoScriptBackendBundle/Controller/Typoscript/TypeController.php
/**
* Create a Type from the submitted data.
*
* @ApiDoc(
* resource = true,
* description = "Creates a new type from the submitted data.",
* input = "SGalinski\TypoScriptBackendBundle\Entity\Type",
* statusCodes = {
* 200 = "Returned when successful",
* 400 = "Returned when the form has errors",
* 401 = "Returned when not authenticated",
* 403 = "Returned when not having permissions"
* }
* )
*
* @param Request $request the request object
*
* @return FormTypeInterface|View
*/
public function postTypeAction(Request $request) {
try {
try {
/** @var UserRepository $userRepository */
$userRepository = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:User');
if (!$userRepository->canEditData($request)) {
throw new AccessDeniedException();
}
$persistedType = $this->createNewType($request);
$routeOptions = [
'id' => $persistedType->getId(),
'_format' => $request->get('_format')
];
return $this->routeRedirectView('api_typoscript_get_type', $routeOptions, Codes::HTTP_CREATED);
} catch (InvalidFormException $exception) {
return $exception->getForm();
}
} catch (\Exception $exception) {
$this->throwFosrestSupportedException($exception);
}
}
Wir verwenden die postTypeAction, um einen neuen Typ zu erstellen und ihn in der Datenbank zu speichern. Das geschieht in der Methode createNewType:
// File: SGalinski/TypoScriptBackendBundle/Controller/Typoscript/TypeController.php
/**
* Creates new type from request parameters and persists it.
*
* @param Request $request
* @return Type - persisted type
*/
protected function createNewType(Request $request) {
$type = new Type();
$parameters = $request->request->all();
$persistedType = $this->processForm($type, $parameters, 'POST');
return $persistedType;
}
/**
* Processes the form.
*
* @param Type $type
* @param array $parameters
* @param String $method
* @return Type
*
* @throws InvalidFormException
*/
private function processForm(Type $type, array $parameters, $method = 'PUT') {
$form = $this->createForm(new TypeType(), $type, ['method' => $method]);
$form->submit($parameters, 'PATCH' !== $method);
if ($form->isValid()) {
/** @var Type $type */
$type = $form->getData();
/** @var TypeRepository $typeRepository */
$typeRepository = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:Type');
$typeRepository->persistType($type);
return $type;
}
throw new InvalidFormException('Invalid submitted data', $form);
}
Wie Sie sehen können, verwenden wir in der Methode processForm ein zuvor erstelltes Symfony-Formular, um die Anfragedaten zu validieren. Wenn die Daten gültig sind, werden sie in der Datenbank gespeichert.
6.3 PUT-Verfahren
// File: SGalinski/TypoScriptBackendBundle/Controller/Typoscript/TypeController.php
/**
* Update existing type from the submitted data or create a new type.
* All required fields must be set within request data.
*
* @ApiDoc(
* resource = true,
* input = "SGalinski\TypoScriptBackendBundle\Entity\Type",
* statusCodes = {
* 201 = "Returned when the Type is created",
* 204 = "Returned when successful",
* 400 = "Returned when the form has errors",
* 401 = "Returned when not authenticated",
* 403 = "Returned when not having permissions"
* }
* )
*
* @param Request $request the request object
* @param int $id the type id
*
* @return FormTypeInterface|View
*
* @throws NotFoundHttpException when type not exist
*/
public function putTypeAction(Request $request, $id) {
try {
try {
/** @var UserRepository $userRepository */
$userRepository = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:User');
if (!$userRepository->canEditData($request)) {
throw new AuthenticationException();
}
/** @var TypeRepository $typeRepository */
$typeRepository = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:Type');
/** @var Type $type */
$type = $typeRepository->find($id);
if (!$type) {
$statusCode = Codes::HTTP_CREATED;
$type = $this->createNewType($request);
} else {
$statusCode = Codes::HTTP_NO_CONTENT;
$type = $this->processForm($type, $request->request->all(), 'PUT');
}
$routeOptions = [
'id' => $type->getId(),
'_format' => $request->get('_format')
];
return $this->routeRedirectView('api_typoscript_get_type', $routeOptions, $statusCode);
} catch (InvalidFormException $exception) {
return $exception->getForm();
}
} catch (\Exception $exception) {
$this->throwFosrestSupportedException($exception);
}
}
Wenn die HTTP PUT-Methode aufgerufen wird, sollten wir die Datenbank nach der Entität mit der angegebenen ID durchsuchen.
Wenn wir die Entität finden, wenden wir die Anfragedaten auf sie an, validieren sie und halten sie fest, wenn sie gültig sind. Wenn die Daten ungültig sind, wird eine Ausnahme ausgelöst. All dies, mit Ausnahme der Suche, wird in der processForm-Methode durchgeführt, die im obigen Text vorgestellt wird.
Wenn die Entität nicht in der Datenbank vorhanden ist, wird sie erstellt, validiert und persistiert, genau wie die POST-Methode aufgerufen wird.
6.4 DELETE-Verfahren
// File: SGalinski/TypoScriptBackendBundle/Controller/Typoscript/TypeController.php
/**
* REST action which deletes type by id.
* Method: DELETE, url: /api/typoscript/types/{id}.{_format}
*
* @ApiDoc(
* resource = true,
* description = "Deletes a Type for a given id",
* statusCodes = {
* 204 = "Returned when successful",
* 401 = "Returned when not authenticated",
* 403 = "Returned when not having permissions",
* 404 = "Returned when the type is not found"
* }
* )
*
* @param Request $request
* @param $id
* @return mixed
*/
public function deleteTypeAction(Request $request, $id) {
/** @var UserRepository $userRepository */
$userRepository = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:User');
if (!$userRepository->canEditData($request)) {
throw new AuthenticationException();
}
/** @var TypeRepository $typeRepository */
$typeRepository = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:Type');
/** @var Type $type */
$type = $typeRepository->find($id);
if ($type) {
try {
$typeRepository->deleteType($type);
} catch (\Exception $exception) {
$this->throwFosrestSupportedException($exception);
}
} else {
throw new NotFoundHttpException(sprintf('The resource \'%s\' was not found.', $id));
}
}
Diese Methode löscht einen Typ nach ID. Zunächst wird versucht, den Typ aus der Datenbank zu holen. Wenn wir ihn finden, löschen wir ihn, andernfalls wird eine Ausnahme geworfen.
6.5 PATCH-Verfahren
// File: SGalinski/TypoScriptBackendBundle/Controller/Typoscript/TypeController.php
/**
* Update existing type from the submitted data.
*
* @ApiDoc(
* resource = true,
* input = "SGalinski\TypoScriptBackendBundle\Entity\Type",
* statusCodes = {
* 204 = "Returned when successful",
* 400 = "Returned when the form has errors",
* 401 = "Returned when not authenticated",
* 403 = "Returned when not having permissions"
* }
* )
*
* @param Request $request the request object
* @param int $id the type id
*
* @return FormTypeInterface|View
*
* @throws NotFoundHttpException when type does not exist
*/
public function patchTypeAction(Request $request, $id) {
try {
try {
/** @var UserRepository $userRepository */
$userRepository = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:User');
if (!$userRepository->canEditData($request)) {
throw new AuthenticationException();
}
/** @var Type $type */
$type = $this->getDoctrine()->getRepository('TypoScriptBackendBundle:Type')->find($id);
if (!$type) {
throw new NotFoundHttpException(sprintf('The resource \'%s\' was not found.', $id));
}
$statusCode = Codes::HTTP_NO_CONTENT;
$type = $this->processForm($type, $request->request->all(), 'PATCH');
$routeOptions = [
'id' => $type->getId(),
'_format' => $request->get('_format')
];
return $this->routeRedirectView('api_typoscript_get_type', $routeOptions, $statusCode);
} catch (InvalidFormException $exception) {
return $exception->getForm();
}
} catch (\Exception $exception) {
$this->throwFosrestSupportedException($exception);
}
}
Wie Sie sehen können, erhalten wir zuerst die Entität und "patchen" sie dann - aktualisieren einige ihrer Felder. Hier liegt der Hauptunterschied zwischen den HTTP-Methoden PUT und PATCH - PATCH aktualisiert nur die Felder, die in den Anfragedaten übermittelt wurden, und andere Felder bleiben unverändert. Während PUT alle Felder der Entität aktualisiert. Wenn einige Felder in den Anfragedaten ausgelassen werden, werden diese Felder in der Entität auf NULL gesetzt. Außerdem kann die PUT-Methode eine neue Entität erstellen, während dies bei PATCH nicht möglich ist.
7. Ausnahmen
Ein guter Umgang mit Ausnahmen ist wichtig, denn sie helfen Ihnen bei der Entwicklung, indem sie Ihnen wertvolles Feedback geben, und sie zeigen dem Benutzer einen Fehler an, wenn er auftritt. Das FOSRest-Bundle fängt die von den Controller-Aktionen ausgelösten Ausnahmen ab und erstellt http-Fehlermeldungen, die an den Benutzer zurückgegeben werden.
Wichtig!
Das Verhalten von FOSRest-Bundles in Entwicklungs- und Produktionsumgebungen ist unterschiedlich. In der Entwicklungsumgebung werden die vollständigen Fehlermeldungen aller nicht abgefangenen Ausnahmen an den Benutzer (Entwickler) gesendet, während in der Produktionsumgebung nur die http-Fehlercodes mit der entsprechenden Meldung an den Benutzer gesendet werden. Wenn zum Beispiel ein Datenbankfehler durch ungültige Benutzerdaten verursacht wird, gibt das FOSRest-Bundle den http-Code 500 mit der Fehlermeldung zurück: 'Interner Serverfehler', was gut ist. Auf diese Weise werden sensible interne Informationen über das System (die z.B. in einer Datenbankfehlermeldung enthalten sein könnten) geschützt. Wenn der Entwickler den Benutzern jedoch eine aussagekräftigere Meldung geben möchte, muss er eine Ausnahmeklasse registrieren und sie mit einem http-Fehlercode verknüpfen. Dann kann er eine benutzerdefinierte Fehlermeldung mit mehr Details über den Fehler einstellen und hat dennoch die Kontrolle über die Systemdaten, die dem Benutzer präsentiert werden.
Hier ist ein Beispiel für eine benutzerdefinierte Ausnahmeklasse:
// File: SGalinski/TypoScriptBackendBundle/Exception/BadRequestDataException.php
<?php
namespace SGalinski\TypoScriptBackendBundle\Exception;
/**
* Class BadRequestDataException has purpose to pass the error message through FOSRest bundle to the client.
* It should be used for error caused by client's bad input.
*
* @package SGalinski\TypoScriptBackendBundle\Exception
*/
class BadRequestDataException extends \Exception {
}
Alle benutzerdefinierten Ausnahmen müssen in der Datei config.yaml registriert werden, was Sie im Abschnitt über Routes und Konfiguration in diesem Artikel sehen können.
Alle vom System ausgelösten Ausnahmen (z. B. doctrine) müssen abgefangen werden, und die nützlichen Informationen müssen extrahiert und dem Client in einer geeigneten Nachricht präsentiert werden. Das ist die Aufgabe der Funktion throwFosrestSupportedException, die von catch-Blöcken meines Codes aufgerufen wird. In diesem Beispiel tut die Funktion nichts Sinnvolles, sie überträgt lediglich die Fehlermeldung an die BadRequestDataException, die in der FOSRest-Bundle-Konfiguration registriert ist und einen http-Fehlercode zugewiesen hat. Dieser Funktion fehlt also der Code, der die Ausnahme analysieren und die Meldung für den Client anpassen sollte.
/**
* Makes response from given exception.
*
* @param \Exception $exception
* @throws BadRequestDataException
*/
protected function throwFosrestSupportedException(\Exception $exception) {
throw new BadRequestDataException($exception->getMessage());
}
In unserem Git-Repository können Sie den kompletten Code sehen, der in diesem Tutorial verwendet wird. Prost! :)
Kontaktieren Sie uns!
Wir sind eine Digitalagentur, die sich auf die Entwicklung digitaler Produkte spezialisiert hat. Unsere Kernthemen sind Webseiten und Portale mit TYPO3, eCommerce mit Shopware und Android und iOS-Apps. Daneben beschäftigen wir uns mit vielen weiteren Themen im Bereich Webentwicklung. Kontaktieren Sie uns gerne mit Ihren Anliegen!
Kommentare
Stefan Galinski
am 26.10.2015Thanks Damjan for this extensive tutorial! Thanks Damjan for this extensive tutorial!
Maximilian Mayer
am 26.10.2015Nice Symfony tutorials, Damjan! :-) Nice Symfony tutorials, Damjan! :-)
olivedev
am 05.10.2016This is quite a long and tiring process. It would take a really long time for you to create a simple rest api. You can make it much quicker like shown in this guide on how to create rest api in [...] This is quite a long and tiring process. It would take a really long time for you to create a simple rest api. You can make it much quicker like shown in this guide on how to create rest api in Symfony 3.1: https://www.cloudways.com/blog/rest-api-in-symfony-3-1/
pHzao
am 04.11.2017The best tutorials i have read until now! Good Work! The best tutorials i have read until now! Good Work!
PARDEEP
am 09.03.2018Nice Article ! i am also working restful api with symfony and publish article on that
Iink: https://www.cloudways.com/blog/rest-api-in-symfony-3-1/ Nice Article ! i am also working restful api with symfony and publish article on that
Iink: https://www.cloudways.com/blog/rest-api-in-symfony-3-1/
Lukasz
am 24.11.2018Something less simple - hope documentation is ok :) https://github.com/tulik/symfony-4-rest-api Something less simple - hope documentation is ok :) https://github.com/tulik/symfony-4-rest-api
Stas
am 03.09.2019Just a short note. API - application programming interface Just a short note. API - application programming interface
johan
am 27.06.2020Just to mention that API means "Application Programming Interface" and NOT "APplication Interface" Just to mention that API means "Application Programming Interface" and NOT "APplication Interface"
Stefan Galinski
am 27.06.2020Thanks. I changed that. Thanks. I changed that.