2022
Drupal Vertiefung

Drupal Vertiefung

Datum: Februar 2022
Lesedauer: 14 Minuten


Nachdem ich im letzten Blogpost einen Überblick über Drupal gezeigt habe, gehe ich hier auf einzelne Themen nochmals genauer ein.
Aus diesem Grund ist dieser Post auch etwas fortgeschrittener.

Custom Block

Ein Block ist ein Teil eines Layouts und hat einen Inhalt. Blöcke können in beliebigen Regions platziert und danach auch verschoben werden. Sie können unter anderem Text, ein Formular oder auch Bilder beinhalten.
Sie sind wiederverwendbar und können so konfiguriert werden, dass sie nur auf bestimmten Seiten von ausgewählten Personen gesehen werden können.

Blöcke sind nicht zu verwechseln mit Content Types. Ein Content type ist eine Art von Inhalt. Dieser wird im Inhaltsbereich angezeigt.

Beispiel

Ein Block kann auf zwei verschiedene Varianten erstellt werden.

Browser

In Drupal kann man unter Structure > Block layout > Add custom block einen Block hinzufügen.

browser

Dieser kann in einer Region platziert werden.

block

So wird er dann dargestellt.

main

Code

Im Browser können schon sehr viele Dinge konfiguriert werden. Jedoch ist man im Code etwas flexibler.

hello_block/src/Plugin/Block/HelloBlock.php
<?php
namespace Drupal\hello_block\Plugin\Block;
 
use Drupal\Core\Block\BlockBase;
 
/**
*
* Provides a 'Hello' Block.
* @Block(
* id = "hello_block",
* admin_label = @Translation("Hello block"),
* category = @Translation("Hello World"),
* )
*/
class HelloBlock extends BlockBase {
	/**
	* {@inheritdoc}
	*/
	public function build() {
		return [
			'#markup' => $this->t('Hello, World!'),
		];
	}
}

Auf der Zeile zehn wird von der Block Annotation gebrauch gemacht. Damit gibt man an, dass es sich beim Code um einen Block handelt.
Die nachfolgenden drei Zeilen bestimmen die id, das admin_label und die category. Diese Dinge sind dann in Drupal sichtbar.

place-block

In der build Methode kann man das Markup des Blocks erstellen.
Dieser Block wird im Frontend folgendermassen dargestellt.

hello

Regions

Vorhin wurde der Block in einer Region platziert.
Regions sind Bereiche, welche gestyled und positioniert werden können. In diese kann man dann die Blöcke legen.

Bartik

Dieses Beispiel zeige ich anhand des Bark Themes (opens in a new tab).
Im Info File werden alle Regions definiert.

web/core/themes/bartik/bartik.info.yml
regions:
	header: Header
	primary_menu: 'Primary menu'
	secondary_menu: 'Secondary menu'
	page_top: 'Page top'
	page_bottom: 'Page bottom'
	highlighted: Highlighted
	featured_top: 'Featured top'
	breadcrumb: Breadcrumb
	content: Content
	sidebar_first: 'Sidebar first'
	sidebar_second: 'Sidebar second'
	featured_bottom_first: 'Featured bottom first'
	featured_bottom_second: 'Featured bottom second'
	featured_bottom_third: 'Featured bottom third'
	footer_first: 'Footer first'
	footer_second: 'Footer second'
	footer_third: 'Footer third'
	footer_fourth: 'Footer fourth'
	footer_fifth: 'Footer fifth'

Ist das Theme aktiviert, sieht man in Drupal die verschiedenen Bereiche.

regions

Nun kann man die Regions beliebig stylen, indem man mit CSS die korrekte Klasse anspricht.

web/core/themes/bartik/css/components/breadcrumb.css
/**
* @file
* Styles for Bartik's breadcrumbs.
*/
.breadcrumb {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 0.929em;
}

Hooks

https://drupalize.me/tutorial/what-are-hooks?p=2766 (opens in a new tab)

Hooks sind dazu da, das Verhalten zu verändern oder erweitern, ohne dass dabei der bestehende Code zu verändert werden muss. Mithilfe von Hooks können also Komponenten miteinander kommunizieren.

Man kann sich das wie folgt vorstellen:
Der User löscht seinen Account.
Dabei fragt Drupal: "Der User mit der ID 19 löscht seinen Account. Kann hier irgendjemand etwas damit anfangen?"
Daraufhin können andere Module diese Information nutzen. So könnte ein Modul das Profilbild löschen, da diese nun nicht mehr benötigt wird. Ein anderes wäre dann für den Versand eine Umfrage Mail zuständig.

Eine Hook ist also ein Punkt an welchem andere Komponenten einhängen können.

Dabei unterscheidet man zwischen drei Typen von Hooks.

Answer questions

Die sogenannten info hooks sind für das Sammeln von Informationen zuständig. Solche Hooks geben hauptsächlich Arrays zurück.
Beispiel: Die user_toolbar Hook liefert den Link zum persönlichen Account des jeweiligen Benutzers, welcher dann in der Toolbar dargestellt wird.

Alter existing data

Diese Hook verändert bereits vorhandene Daten.
Eine info hook sammelt eine Liste von Informationen. Danach wird die alter hook aufgerufen. Diese kann dann die Liste noch verändern, bevor sie verwendet wird.
Wahrscheinlich wird diese Form der Hook am häufigsten implementiert.

Beispiel: Die taxonomy_views_data_alter Hook fügt den Artikel Taxonomy Terms hinzu, welche Informationen über das Node anzeigen.

React to events

Eine solche Hook wird aufgerufen, wenn im System eine Aktion ausgeführt wird.

Beispiel: Um das vorhin beschriebene Beispiel mit dem Löschen eines Users umzusetzen, würde man eine hook_user_cancel Hook verwenden. Die würde dann alles Bereinigen, was vom User übrig blieb.

Naming

Eine Hook benennt man wie folgt: HOOK_entity_view()

Hierbei wird Hook mit dem maschine name des Moduls ersetzt.
Beispiel: mymodule_entity_view
Diese Hook würde dann für jeden Entity Type aufgerufen werden.

Man kann und sollte Hooks aber spezifischer machen. So spart man sich die If-Abfragen im Code.
mymodule_node_view wird nur für Nodes gecalled.
mymodule_form_2_alter verändert nur das Formular mit der angegebenen ID.

Auf der Drupal API Seite (opens in a new tab) findet man Hilfe zur Erstellung und Benennung von Hooks.

Beispiel

Ich will, dass bei jedem Artikel ein Text vorbeiläuft.
Dazu suche ich auf der Drupal Seite nach der passenden Hook:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_view/9.3.x (opens in a new tab)

Im nächsten Schritt erstelle ich ein Modul. Im .module File füge ich dann den gefunden Code ein und passe ihn meinen Wünschen entsprechend an.
In diesem Fall füge ich bei jedem Node ein ein marquee Tag ein.

web/modules/custom/sneaker/sneaker.module
<?php
/**
* Implements hook_ENTITY_TYPE_view().
*/
function sneaker_node_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity,
\Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode) {
	$build['awesome'] = [
		'#markup' => '<marquee>This is awesome.</marquee>',
		'#allowed_tags' => ['marquee'],
	];
}

So sehen danach die Artikel aus.

article

Preprocess Functions

Preprocess Funktionen ermöglichen die Veränderung und Erstellung von Variablen, bevor diese dann in den Template-Dateien verwendet werden.

Naming

THEME_preprocess_HOOK()

THEME: Name des Themes oder Moduls
HOOK: Template, in welcher die Variablen verwendet werden

Will man beispielsweise die Seite des Bartik Themes anpassen, wäre der Funktionsname so aufgebaut.
bartik_preprocess_page()

Für einen Artikel würde die Funktion so aussehen.
mymodule_preprocess_node()

Diese Namen findet man am leichtesten, wenn man die Seite im TWIG Debug Mode anschaut.

Titel Variable definieren

In einer Preprocess Function wird ein Parameter mitgegeben. Durch diesen Array kann man dann die Variablen, welche man im Template File verwendet, anpassen.

web/themes/custom/mytheme/mytheme.theme
<?php
function mytheme_preprocess_page(&$variables) {
	$variables['title'] = 'My Custom Title';
}
web/themes/custom/mytheme/templates/page.html.twig
{{ title }}

Überprüfung Autor

Auch das Überprüfen von anderen Variablen ist möglich. In diesem Fall schaue ich, ob der angemeldete User, den angezeigten Artikel erstellt hat. Trifft dies zu, so füge ich der Überschrift "- [you are the author]" hinzu.

web/themes/custom/mytheme/mytheme.theme
<?php
function mytheme_preprocess_node(&$variables) {
	$variables['current_user_is_owner'] = FALSE;
 
	if ($variables['logged_in'] == TRUE && $variables['node']->getOwnerId() == $variables['user']->id()) {
		$variables['label']['#suffix'] = '- [you are the author]';
		$variables['current_user_is_owner'] = TRUE;
	}
}

Im Node Template File zeige ich dieses Label auf Zeile sechs an.

web/themes/custom/mytheme/templates/node.html.twig
<article{{ attributes.addClass(classes) }}>
	<header>
		{{ title_prefix }}
		{% if label and not page %}
			<h2{{ title_attributes.addClass('node__title') }}>
				<a href="{{ url }}" rel="bookmark">{{ label }}</a>
			</h2>
		{% endif %}
 
		{{ title_suffix }}
 
		{% if display_submitted %}
			<div class="node__meta">
				{{ author_picture }}
				<span{{ author_attributes }}>
				{% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %}
				</span>
				{{ metadata }}
			</div>
		{% endif %}
	</header>
 
	<div{{ content_attributes.addClass('node__content', 'clearfix') }}>
		{{ content }}
	</div>
</article>

Webform

Das Webform Modul findet man hier: https://www.drupal.org/project/webform (opens in a new tab)

Ich empfehle auch, das Webform UI Modul zu aktivieren. Dieses UI erleichtert einem das Bauen und Pflegen der Formulare enorm.

webform

Unter Structure > Webforms kann man sich dann mit nur wenigen Klicks ein gutes Formular zusammenstellen. Die Konfigurationsmöglichkeiten sind fast unbegrenzt.

structure-webform

Z. B. kann man in nur wenigen Sekunden Seiten anlegen um das Formular in mehrere Schritte zu teilen.

Services

Was ist ein Service?

Ein Service ist ein nützliches Objekt, welches etwas für einen macht. Dieses kann aus einer eigenen, oder fremden Klasse stammen. Eine solches Objekt kann überall wiederverwendet werden.
Klassen, die etwas loggen oder Mails verschicken, sind beispielsweise Services.
Eine Klasse, welche Produkte (Sneaker, Cap,...) abbilden, wäre dann kein Service. Diese Objekte halten lediglich Daten.

Service erstellen

Ausgangslage

Man hat einen Controller. Dieser schreibt eine Hello World auf den Bildschirm.
Leider können solche Controller schnell unübersichtlich werden. Zudem kann man den Code nur im Controller verwenden.

modules/shout_hello/src/Controller/HelloController.php
<?php
namespace Drupal\shout_hello\Controller;
use Symfony\Component\HttpFoundation\Response;
 
class HelloController {
	public function hello($count){
		$hello = 'H'.str_ repeat('e', $count).'llo!';
		return new Response($hello);
	}
}

Vorgehen

  1. Als erstes erstellt man in src einen Ordner. (Beispiel Greeting)

  2. Darin muss man dann die Klasse anlegen. (Beispiel HelloGenerator.php)

  3. In der Klasse muss man zwingend den korrekten namespace definieren.
    In diesem Fall: namespace Drupal\shout_hello\Greeting;

  4. Nun kann man die Funktion in diese neue Klasse auslagern.

    modules/shout_hello/src/Greeting/HelloGenerator.php
    <?php
    namespace Drupal\shout_hello\Greeting;
    class HelloGenerator {
    	public function getHello($length) {
    		return = 'H'.str_ repeat('e', $length).'llo!';
    	}
    }
  5. Controller

    modules/shout_hello/src/Controller/HelloController.php
    	<?php
    	namespace Drupal\shout_hello\Controller:
     
    	use Drupal\shout_hello\Greeting\HelloGenerator;
    	use Symfony\Component\HttpFoundation\Response;
     
    	class HelloController {
    		public function hello($count){
    			$helloGenerator = new HelloGenerator();
    			$hello = $helloGenerator->getRoar($count);
    			return new Response($hello);
    		}
    	}

Nun ist die Logik in einer anderen Klasse. Diese nimmt einem Arbeit ab. Sie ist ein Service.
Mit dieser Variante muss man die Klasse mit new HelloGenerator() erstellen. Dies ist noch nicht die schönste Variante. Um dies zu umgehen, kommt der Service Container ins Spiel.

Service Container

In Drupal gibt es ein Objekt namens Container. Dieses wird auch "Dependency Injection Container" genannt.
Darin befinden sich alle Services. Dazu gehören folgende Klassen:

  • Logger factory
  • Translater
  • DB connection
  • File system

Um sich einen Überblick über alle Services zu verschaffen, kann man diese mit drupal container:debug in der Konsole ausgeben.

Ausgangslage

Vorhin wurde das Objekt direkt erzeugt. Der Container kann uns dies aber abnehmen.

Vorgehen

Service Konfigurieren

Zuerst erstellt man im root des Moduls eine Datei. Diese trägt den Namen:
[modulname].services.yml

Darin konfiguriert man dann den Service.

modules/shout_hello/shout_hello.services.yml
services:
	shout_hello.hello_generator:
		class: Drupal\shout_hello\HelloGenerator

Auf der Zeile zwei gibt man den "Machine Name" an. Dieser darf frei erfunden, muss aber lowercase und unique sein.
Die nachfolgende Linie zeigt, wo sich der Service befindet.
Optional kann man auch noch Arguments mitgeben, die der Service benötigt.

Nachdem man den Cache gelehrt hat, findet man den Service im Container.

drupal container:debug | grep shout
Service aus dem Container holen
  1. Nun kann man von ControllerBase erben und die create Method implementieren.

    modules/shout_hello/src/Controller/HelloController.php
    <?php
    namespace Drupal\shout_hello\Controller:
     
    use Drupal\Core\Controller\ControllerBase;
    use Drupal\shout_hello\Greeting\HelloGenerator;
    use Symfony\Component\HttpFoundation\Response;
     
    class HelloController extends ControllerBase {
    	public function hello($count){
    		$helloGenerator = new HelloGenerator();
    		$hello = $helloGenerator->getRoar($count);
    		return new Response($hello);
    	}
     
    	public static function create(ContainerInterface $container) {
    	}
    }
  2. Objekt instanziieren

    modules/shout_hello/src/Controller/HelloController.php
    <?php
    namespace Drupal\shout_hello\Controller:
     
    use Drupal\Core\Controller\ControllerBase;
    use Drupal\shout_hello\Greeting\HelloGenerator;
    use Symfony\Component\HttpFoundation\Response;
     
    class HelloController extends ControllerBase {
    	public function hello($count){
    		$helloGenerator = new HelloGenerator();
    		$hello = $helloGenerator->getRoar($count);
    		return new Response($hello);
    	}
     
    	public static function create(ContainerInterface $container) {
    		$helloGenerator = $container->get('shout_hello.hello_generator');
    		return new static($helloGenerator);
    	}
    }
  3. Konstruktor und Property erstellen

    modules/shout_hello/src/Controller/HelloController.php
    	<?php
    	namespace Drupal\shout_hello\Controller:
     
    	use Drupal\Core\Controller\ControllerBase;
    	use Drupal\shout_hello\Greeting\HelloGenerator;
    	use Symfony\Component\HttpFoundation\Response;
     
    	class HelloController extends ControllerBase {
    		private $helloGenerator;
     
    		public function __construct(HelloGenerator $helloGenerator) {
    			$this->helloGenerator = $helloGenerator;
    		}
     
    		public function hello($count){
    			$helloGenerator = new HelloGenerator();
    			$hello = $helloGenerator->getRoar($count);
    			return new Response($hello);
    		}
     
    		public static function create(ContainerInterface $container) {
    			$helloGenerator = $container->get('shout_hello.hello_generator');
    			return new static($helloGenerator);
    		}
    	}
  4. In der hello Methode die getHello Funktion über den Service aufrufen

    modules/shout_hello/src/Controller/HelloController.php
    <?php
    namespace Drupal\shout_hello\Controller:
     
    use Drupal\Core\Controller\ControllerBase;
    use Drupal\shout_hello\Greeting\HelloGenerator;
    use Symfony\Component\HttpFoundation\Response;
     
    class HelloController extends ControllerBase {
    	private $helloGenerator;
     
    	public function __construct(HelloGenerator $helloGenerator) {
    		$this->helloGenerator = $helloGenerator;
    	}
     
    	public function hello($count){
    		$hello = $this->helloGenerator->getHello($count);
    		return new Response($hello);
    	}
     
    	public static function create(ContainerInterface $container) {
    		$helloGenerator = $container->get('shout_hello.hello_generator');
    		return new static($helloGenerator);
    	}
    }
Grund

Es gibt drei wesentliche Gründe, warum man einen Service in den Container tun sollte.

  • Durch die Verwendung eines Containers wird das Objekt erst erstellt, wenn es benötigt wird. So kann man Unmengen von Services haben, ohne dabei auf Performance Probleme zu stossen.
  • Benötigt man einen Service mehrfach, so wird überall dasselbe Objekt verwendet. Ohne Container wäre, zu Lasten der Performance, jedes mal ein neues Objekt erstellt worden.
  • Dank des Containers kann man nun alles im Konstruktor übergeben. Jetzt kann man andere Services im eigenen verwenden.

API

Um Abfragen durchzuführen, habe ich Postman (opens in a new tab) installiert. Dies muss man nicht machen, finde ich aber empfehlenswert.

JSON

Das JSON:API Modul ist normalerweise schon installiert. Es muss also bei Bedarf nur noch aktiviert werden.

Eine solche API kann sehr schnell alle Node Daten zur Verfügung stellen. Um dies zu bewerkstelligen braucht man auch nicht viele Kenntnisse.
Will man aber beispielsweise auf Bilder zugreifen, so sind mehrere Calls nötig, da man vorerst nur die ID erhält. Zudem wird man bei einem Request mit vielen Daten überflutet.

Modul

modul

Settings

In den Drupal Einstellungen gibt es zwei verschiedene Konfigurationsmöglichkeiten.

operations

Zugriff

Wie die URL genau aufgebaut ist, erfährt man hier: https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/api-overview (opens in a new tab)
So könnte dann ein Response aussehen.

zugriff

REST

Die JSON:API fokussiert sich auf Drupals Stärken. Das REST Modul bietet jedoch viel mehr Möglichkeiten, da es extrem konfigurierbar ist. Zudem kann das Format, die Logik und die HTTP Method frei gewählt werden.

FeatureJSON:APIRESTRemark
Entities exposed as resources✔️✔️REST: needs configuration per entity type. JSON:API exposes everything by default. Both respect entity access.
Custom data exposed as resources✔️Write custom @RestResource plugins. JSON:API only supports entities.
Getting individual resources✔️✔️
Getting lists of resources✔️kindaREST: needs a view with a "REST export" display.
Paginating list of resources✔️REST: not supported! REST export views return all resources. Additional modules like Pager Serializer needed.
Filtering list of resources✔️kindaREST: requires an exposed filter for each field and every possible operator.
Sorting of resources✔️
Includes/embedding✔️Only in HAL+JSON
No unnecessary wrapping of field values✔️REST/HAL normalization exposes raw Drupal PHP structures, causing poor DX. JSON:API simplifies normalization.
Ability to omit fields consumer does not need✔️
Consistent URLs✔️
Consumer can discover available resource types✔️
Drupal-agnostic response structure✔️REST: HAL normalization is theoretically Drupal-free, but practically isn't.
Client libraries✔️
Extensible specWIP
Zero configuration✔️REST: Each @RestResource plugin must be configured (formats, auth, HTTP methods). JSON:API handles this automatically.

Source (opens in a new tab)

Modul

Um die RESTful API zu verwenden, können folgende zwei Module aktiviert werden.

rest-modul

Das erste Modul wird benötigt, um die Schnittstelle zu öffnen. REST UI bietet ein Interface, mit welchem man die verschiedensten Konfigurationen durchführen kann.

Settings

Mit dem RESTful Modul kann man für jede Resource die Berechtigungen festlegen.

permission

Dank dem UI Modul findet man unter Configuration > Web services > REST eine Seite, auf welcher man alle Ressourcen öffnen oder schliessen kann.

resource

In meinem Fall habe ich die Content Seiten freigegeben.

content

Zugriff

Durch diese Freigabe der Nodes, kann ich dann darauf zugreifen.

postman

Custom

Das RESTful Modul ist schon sehr gut. Jedoch ist man im Code jeweils flexibler.
Aus diesem Grund bietet Drupal, dass man Custom Module erstellen kann.

Ein solches Modul muss man selbst im Code erstellen. Dazu sind folgende Schritte notwendig.

  1. Neues Modul im Ordner /modules/custom/ erstellen

  2. Info File anlegen

    demo_rest_api.info.yml
    name: Demo REST API
    description: Define's a custom REST Resource
    package: Custom
    type: module
    core: 8.x
    core_version_requirement: ^8 || ^9
  3. Eine Resource Klasse im Ordner /src/Plugin/rest/resource/ erstellen

    /src/Plugin/rest/resource/DemoResource.php
    <?php
    namespace Drupal\demo_rest_api\Plugin\rest\resource;
     
    use Drupal\rest\Plugin\ResourceBase;
    use Drupal\rest\ResourceResponse;
     
    class DemoResource extends ResourceBase {
    }
  4. Nun kann man mit der "Rest Resource Annotation" die ID, den Namen und die Pfade angeben.

    /src/Plugin/rest/resource/DemoResource.php
    /**
    *
    * Provides a Demo Resource
    * @RestResource(
    * id = "demo_resource",
    * label = @Translation("Demo Resource"),
    * uri_paths = {
    * "canonical" = "/demo_rest_api/demo_resource"
    * }
    * )
    */
    class DemoResource extends ResourceBase {
    }
  5. Nun existiert die Resource. Jedoch macht diese noch nichts.
    Um einen Get Request abzufangen, muss man die entsprechende Funktion implementieren.

    /src/Plugin/rest/resource/DemoResource.php
    /**
     *
     * Provides a Demo Resource
     * @RestResource(
     * id = "demo_resource",
     * label = @Translation("Demo Resource"),
     * uri_paths = {
     * "canonical" = "/demo_rest_api/demo_resource"
     * }
     * )
     */
    class DemoResource extends ResourceBase {
       /**
       * Responds to entity GET requests.
       * @return \Drupal\rest\ResourceResponse
       */
    	public function get() {
    		$response = ['message' => 'Hello, this is a rest service'];
    			return new ResourceResponse($response);
    		}
    	}
  6. Wichtig ist noch, dass das Modul in Drupal aktiviert werden muss.

  7. Nun kann man manuell in den Einstellungen die Links Zugänglich machen. links
    Alternativ kann man dem Modul auch ein YAML File hinzufügen. Dieses wird dann bei der Installation beachtet.

    demo_rest_api/config/install/rest.resource.demo_resource.yml
    id: demo_resource
    plugin_id: demo_resource
    granularity: method
    configuration:
    	GET:
    		supported_formats:
    		- json
    		supported_auth:
    		- cookie
    	POST:
    		supported_formats:
    		- json
    		supported_auth:
    		- cookie

Hat man all diese Schritte durchgeführt, kann man eine Anfrage durchführen.

Sneaker Loader

src/SneakerLoader.php
namespace Drupal\demo_rest_api;
 
use Drupal\Core\Entity\EntityTypeManagerInterface;
 
class SneakerLoader {
	/**
	* The entity type manager service.
	*
	* @var \Drupal\Core\Entity\EntityTypeManagerInterface
	*/
	protected $entityTypeManager;
 
	/**
	*
	* Constructs a new ContentUninstallValidator.
	* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
	* The entity type manager service.
	*/
	public function __construct(EntityTypeManagerInterface $entity_type_manager) {
		$this->entityTypeManager = $entity_type_manager;
	}
 
	public function getText() {
		/** @var \Drupal\node\NodeInterface $sneaker */
		$sneaker = $this->entityTypeManager->getStorage("node")->load(29);
		return $sneaker->getTitle();
	}
}

In diesem Service wird in der getText Methode der Sneaker mit der ID 29 geholt. Davon wird er Titel zurückgegeben.
In der DemoResource.php Datei, wo der GET definiert ist, wird die getText Methode dann aufgerufen. Daher sieht dann der entsprechende Request so aus.

demo

SneakerSaver

In meinem Webform, welches weiter oben beschrieben ist, habe ich einen sogenannten post handler eingebaut. Wird das Formular abgeschickt, so wird ein Post auf die angegebene URL gesendet.

remote

Im Code habe ich dann eine SneakerSaver Klasse erstellt. Die enthaltene Methode erstellt einen Sneaker anhand der mitgegebenen Daten.

demo_rest_api/src/SneakerSaver.php
<?php
namespace Drupal\demo_rest_api;
 
use Drupal\Core\Entity\EntityTypeManagerInterface;
 
class SneakerSaver {
	/**
	*
	* The entity type manager service.
	* @var \Drupal\Core\Entity\EntityTypeManagerInterface
	*/
	protected $entityTypeManager;
 
	/**
	*
	* Constructs a new ContentUninstallValidator.
	* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
	* The entity type manager service.
	*/
	public function __construct(EntityTypeManagerInterface $entity_type_manager) {
		$this->entityTypeManager = $entity_type_manager;
	}
 
	public function saveSneaker($data) {
		/** @var \Drupal\node\NodeInterface $sneaker */
		$sneaker = $this->entityTypeManager->getStorage('node')->create([
		'type' => 'sneaker',
		'title' => $data['title'],
		'uid' => 1,
		'status' => 1
		]);
		$sneaker->save();
		return true;
	}
}

In DemoResource wird diese Methode im POST aufgerufen.

demo_rest_api/src/Plugin/rest/resource/DemoResource.php
<?php
namespace Drupal\demo_rest_api\Plugin\rest\resource;
 
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\demo_rest_api\SneakerLoader;
use Drupal\demo_rest_api\SneakerSaver;
 
/**
*
* Provides a Demo Resource
* @RestResource(
* id = "demo_resource",
* label = @Translation("Demo Resource"),
* uri_paths = {
* "canonical" = "/demo_rest_api/demo_resource",
* "create" = "/demo_rest_api/create_entity"
* }
* )
*/
class DemoResource extends ResourceBase {
	private SneakerLoader $sneakerLoader;
	private SneakerSaver $sneakerSaver;
 
	public function __construct(array $configuration, $plugin_id, $plugin_definition, array
	$serializer_formats, LoggerInterface $logger, SneakerLoader $sneakerLoader, SneakerSaver $sneakerSaver) {
		parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
		$this->sneakerLoader = $sneakerLoader;
		$this->sneakerSaver = $sneakerSaver;
	}
 
	public static function create(ContainerInterface $container, array $configuration, $plugin_id,
	$plugin_definition) {
		return new static(
		$configuration,
		$plugin_id,
		$plugin_definition,
		$container->getParameter('serializer.formats'),
		$container->get('logger.factory')->get('rest'),
		$container->get("demo_rest_api.sneaker_loader"),
		$container->get("demo_rest_api.sneaker_saver")
		);
	}
 
	/**
	* Responds to entity GET requests.
	* @return \Drupal\rest\ResourceResponse
	*/
	public function get() {
		$response = ['message' => $this->sneakerLoader->getText()];
		return new ResourceResponse($response);
	}
 
	/**
	*
	* Responds to POST requests.
	* @param mixed $data
	*
	* @return \Drupal\rest\ModifiedResourceResponse
	* The HTTP response object.
	*
	* @throws \Symfony\Component\HttpKernel\Exception\HttpException
	* Throws exception expected.
	*/
	public function post($data) {
		$response = ['message' => $this->sneakerSaver->saveSneaker($data)];
		return new ResourceResponse($response);
	}
}

Wichtig ist hierbei noch, dass unter uri_paths ein create existiert und diese URL mit dem POST des Webforms übereinstimmt.

Der gesamte Ablauf ist aber hier (opens in a new tab) noch detaillierter beschreiben.