{"id":1348,"date":"2015-07-09T14:22:17","date_gmt":"2015-07-09T14:22:17","guid":{"rendered":"https:\/\/www.zensations.at\/?p=1348"},"modified":"2023-08-09T00:35:13","modified_gmt":"2023-08-09T00:35:13","slug":"drupal-8-basics-eigene-service-klasse-definieren","status":"publish","type":"post","link":"https:\/\/www.zensations.at\/blog\/drupal-8-basics-eigene-service-klasse-definieren\/","title":{"rendered":"Drupal 8 Basics: eigene Service Klasse definieren"},"content":{"rendered":"

Heute werden wir uns in der Drupal 8 Modulentwicklung vertiefen und zeigen, wie man einen eigenen Service in Drupal 8 definieren kann. Das Ziel wird sein, eine (neuen) Controller zu definieren, um eine Route zu registrieren, auf welcher dynamisch die letzten 10 Tweets eines Twitter-Benutzers dargestellt werden. Wir haben bereits\u00a0in einem unserer vorherigen Beitr\u00e4ge gezeigt<\/a>, wie man in Drupal 8 eine Controller-Klasse erstelltt, die beim Aufruf einer URL einen einfachen Text ausgibt. Das Beispiel war immerhin nicht ganz trivial, denn der Text war von weiteren Parametern in der URL abh\u00e4ngig. Dieses Beispiel gilt es nun um das Konzepte von Services, ServiceContainer und Dependency Injection zu erweitern, um die Logik f\u00fcr den Abruf der Twitter-Daten in einer Service-Klasse getrennt zu halten und diese gezielt unserem Controller per Dependency Injection zur Verf\u00fcgung zu stellen.<\/p>\n

Voraussetzungen<\/h2>\n

Um Daten von Twitter holen zu k\u00f6nnen, muss man\u00a0eine App bei Twitter registrieren<\/a>. Das ist notwendig, damit man von der API von Twitter User-spezifische Daten abfragen kann. Wichtig ist es, nach der Erstellung der App einfach sich die Tokens f\u00fcr Consumer Key (API Key) und Consumer Secret (API Secret) zu merken, denn diese werden wir sp\u00e4ter in dem Code unserer Service-Klasse einsetzen.<\/p>\n

Eine neue Route definieren<\/h2>\n

Dieser Teil d\u00fcrfte uns schon aus unserem ersten Beitrag bekannt sein (Datei \/mymodule.routing.yml):<\/p>\n

\r\nmymodule.twitter:\r\n  path: '\/twitter\/{username}'\r\n  defaults:\r\n    _controller: '\\Drupal\\mymodule\\Controller\\TwitterController::feed'\r\n    _title_callback: '\\Drupal\\mymodule\\Controller\\TwitterController::setTitle'\r\n    username: 'drupal'\r\n  requirements:\r\n    _access: 'TRUE'\r\n<\/code><\/pre>\n

Wir definieren einen neuen Pfad („\/twitter“), der einen Variablen Teil ({username}) erhalten kann. Das neue hier ist, dass wir anstatt von _title den key _title_callback ben\u00fctzen, damit wir den Titel der Seite, entsprechend dem dynamischen Wert in der URL, durch eine Funktion \u00e4ndern k\u00f6nnen.<\/p>\n

Registrierung unserer Service-Klasse und deren Implementierung<\/h2>\n

Analog zur Vorgehensweise f\u00fcr die Registrierung der Controller, legen wir im root-Verzeichnis unseres Moduls eine YAML-Datei an, wo wir unsere Service-Klasse bekannt geben. Die Datei muss MODULENAME.services.yml heissen (in unserem Fall \/mymodule.services.yml):<\/p>\n

\r\nservices:\r\n  mymodule.twitter_service:\r\n    class: 'Drupal\\mymodule\\Twitter\\TwitterService'\r\n<\/code><\/pre>\n

Der Code oben ist ziemlich klar. Der „machine_name“ unserer Klasse ist mit dem Modulnamen prefixed, damit keine Namenskonflikte mit anderen Klassen entstehen. Wir k\u00f6nnen nun auch die Datei anlegen, die unsere Klasse beinhalten wird. Der Code f\u00fcr diese Datei (\/src\/Twitter\/TwitterService.php) lautet wie folgt:<\/p>\n

\/** * @file * Contains Drupal\\mymodule\\TwitterService. *\/<\/code><\/p>\n

namespace Drupal\\mymodule\\Twitter;<\/p>\n

class TwitterService {<\/p>\n

public function getData($username) { $api_key = urlencode(‚YOUR_TWITTER_API_KEY‘); $api_secret = urlencode(‚YOUR_TWITTER_API_SECRET‘); $auth_url = ‚https:\/\/api.twitter.com\/oauth2\/token‘;<\/p>\n

$data_username = $username;\r\n$data_count = 10;\r\n$data_url = 'https:\/\/api.twitter.com\/1.1\/statuses\/user_timeline.json';\r\n$api_credentials = base64_encode($api_key.':'.$api_secret);\r\n\r\n$auth_headers = 'Authorization: Basic '.$api_credentials.\"\\r\\n\".\r\n  'Content-Type: application\/x-www-form-urlencoded;charset=UTF-8'.\"\\r\\n\";\r\n\r\n$auth_context = stream_context_create(\r\n  array(\r\n    'http' => array(\r\n      'header' => $auth_headers,\r\n      'method' => 'POST',\r\n      'content'=> http_build_query(array('grant_type' => 'client_credentials', )),\r\n    )\r\n  )\r\n);\r\n\r\n$auth_response = json_decode(file_get_contents($auth_url, 0, $auth_context), true);\r\n$auth_token = $auth_response['access_token'];\r\n\r\n$data_context = stream_context_create( array( 'http' => array( 'header' => 'Authorization: Bearer '.$auth_token.\"\\r\\n\", ) ) );\r\n\r\n$data = json_decode(file_get_contents($data_url.'?count='.$data_count.'&screen_name='.urlencode($data_username), 0, $data_context), true);\r\n\r\nreturn $data;\r\n<\/code><\/pre>\n

}<\/p>\n

public function renderData($username) { $data = $this->getData($username);<\/p>\n

$tweets = [];\r\nforeach ($data as $value) {\r\n  $tweets[] = $value['text'];\r\n}\r\n\r\nreturn '<div>' . implode('<\/div><div>', $tweets) . '<\/div>';\r\n<\/code><\/pre>\n

} }<\/p>\n

Hier passiert eigentlich nicht viel. In der Klasse gibt es 2 Methoden – getData() und renderData(). getData() beinhaltet einfach den Code, den man braucht, um die Twitter-API abzufragen. Hier m\u00fcsste man auch die entsprechenden Consumer Key (API Key) und Consumer Secret (API Secret) einsetzen. Die Methode bekommt auch $username als Parameter. Die Zuweisung wird in unserem Controller passieren, den wir noch nicht geschrieben haben. Die $data-Variable, die zur\u00fcckgegeben wird ist einfach ein Array mit etlichen Infos \u00fcber die letzten 10 Tweets von einem Benutzer. Um das relativ gut anzeigen zu k\u00f6nnen existiert die 2. Methode in der Klasse- renderData(). Ja, diese Funktion ist zielmlich einfach, aber f\u00fcr unser Beispiel ausreichend.<\/p>\n

Durch die Erstellung der Twitter-Service-Klasse haben wir den Code sauber separiert und anderen Entwicklern die M\u00f6glichkeit gegeben, diesen bei Bedarf per Dependency Injection an anderen Stellen einzusetzen. Das ist von Vorteil, denn unsere Twitter-Funktionalit\u00e4t ist nun abgekapselt und k\u00f6nnte sogar in andere PHP-Projekte Verwendung finden. Es ist auch gut, solchen Code, der ganz spezifische Anforderungen erf\u00fcllt, getrennt zu haben und nicht in unseren Route-Controller zu finden, welcher eigentlich nur Aufgaben zu Delegierung haben sollte.<\/p>\n

Service per Dependency Injection aufrufen<\/h2>\n

Was uns noch fehlt ist die Implementierung unseres Controllers (siehe Schritt „Eine neue Route definieren“). Schauen wir uns einfach zuerst den Code an (Datei \/src\/Controller\/TwitterController.php):<\/p>\n

namespace Drupal\\mymodule\\Controller;<\/code><\/p>\n

use Drupal\\Core\\Controller\\ControllerBase; use Symfony\\Component\\DependencyInjection\\ContainerInterface;<\/p>\n

class TwitterController extends ControllerBase {<\/p>\n

protected $twitterService;<\/p>\n

public function __construct($twitterService) { $this->twitterService = $twitterService; }<\/p>\n

public static function create(ContainerInterface $container) { return new static( $container->get(‚mymodule.twitter_service‘) ); }<\/p>\n

public function feed($username) { $data = $this->twitterService->renderData($username);<\/p>\n

$content = array(\r\n  '#markup' => $data,\r\n);\r\n\r\nreturn $content;\r\n<\/code><\/pre>\n

}<\/p>\n

public function setTitle($username) { return ‚Latest 10 tweets of ‚ . $username; } }<\/p>\n

Die wesentliche \u00c4nderung zu unserer einfachen HelloWorldController-Klasse aus dem vorherigen Beitrag ist die Ben\u00fctzung von Symfony’s Komponente ContainerInterface. Das ist notwendig, damit wir in der create()-Methode in der Lage sind, Objekte aus anderen Klassen zu instanzieren. So k\u00f6nnen wir diese Objekte dann in der __construct()-Methode zuweisen und vollst\u00e4ndig ben\u00fctzen (siehe die erste Zeile in der feed()-Methode). Ziemlich klar und sauber, oder?<\/p>\n

Fazit<\/h2>\n

Schritt nach Schritt werden die neuen Konzepte in Drupal 8 klarer. Services machen es einem relativ leicht ganze Objekte getrennt zu warten und diese somit f\u00fcr andere Projekte (nicht unbedingt Drupal-Projekte) wieder zu verwenden. Durch deren Registrierung zum Symfony’s ServiceContainer ist es zudem sehr leicht die Eigenschaften und Methoden des geladenen Objekts an beliebigen Stellen einzusetzen.<\/p>\n","protected":false},"excerpt":{"rendered":"

Heute werden wir uns in der Drupal 8 Modulentwicklung vertiefen und zeigen, wie man einen eigenen Service in Drupal 8 definieren kann. Das Ziel wird sein, eine (neuen) Controller zu definieren, um eine Route zu registrieren, auf welcher dynamisch die letzten 10 Tweets eines Twitter-Benutzers dargestellt werden. Wir haben bereits\u00a0in einem unserer vorherigen Beitr\u00e4ge gezeigt, […]<\/p>\n","protected":false},"author":23,"featured_media":1349,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[78],"tags":[206],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/posts\/1348"}],"collection":[{"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/users\/23"}],"replies":[{"embeddable":true,"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/comments?post=1348"}],"version-history":[{"count":1,"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/posts\/1348\/revisions"}],"predecessor-version":[{"id":1350,"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/posts\/1348\/revisions\/1350"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/media\/1349"}],"wp:attachment":[{"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/media?parent=1348"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/categories?post=1348"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.zensations.at\/wp-json\/wp\/v2\/tags?post=1348"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}