German (Deutsch) translation by Alex Grigorovich (you can also view the original English article)
Beste von Wptuts 2011: Jede Woche bis Januar werden wir einige unserer Lieblingsposts aus dem Jahr 2011 erneut besuchen. Die Plugin-Entwicklung fühlt sich oft wie der wilde Westen an, wenn Sie etwas von Grund auf neu erstellen, ohne dass ein Boilerplate oder ein ähnliches Plugin funktioniert from - Toms zweiteilige Serie über wartbare WordPress-Widgets/Plugins bietet einige praktische Richtlinien, die Sie auf dem Laufenden halten sollten!
Wenn es um Softwareentwicklung geht, sind Frameworks und Bibliotheken beliebt, weil sie hilfreich sind, oder? Sie bieten eine konsistente Möglichkeit, Code zu schreiben und zu organisieren, um die Entwicklung und Wartung so einfach wie möglich zu gestalten.
Die gleichen Prinzipien, die für größere Systeme auf Unternehmensebene gelten, gelten ebenso für kleinere Projekte - wie z. B. WordPress-Plugins -, die von kleinen Teams entwickelt wurden. Und genau wie größere Systeme voller beweglicher Teile sind, ist dies bei WordPress-Plugins der Fall.
Beispiel: Sie haben den Kerncode, der für die Kommunikation mit WordPress (über Filter, Aktionen und Hooks), Verwaltungs-Dashboards, clientseitige Ansichten, JavaScript-Dateien, Stylesheets, Lokalisierungsdateien usw. verantwortlich ist werden mit mindestens vier verschiedenen Programmiersprachen erreicht.
Während meiner Zeit in der WordPress-Entwicklung habe ich einige Boilerplates erstellt, mit denen ich jedes meiner Projekte beginne. In diesem Tutorial erfahren Sie mehr über meinen WordPress Widget Boilerplate-Code, wie Sie ihn in neuen Projekten einsetzen können, und eine Beispielanwendung, mit der Sie Ihr nächstes WordPress-Projekt auf einen soliden Start bringen können.
Ein Widget Boilerplate
Organisation
Wenn es um die Entwicklung geht, versuche ich normalerweise, die Dinge so einfach wie möglich zu halten, indem ich nur die erforderlichen Funktionen plane. Dies ist jedoch ein Fall, in dem ich erschöpfend sein möchte. Es ist fast immer einfacher, mit der Planung einer Heizplatte zu beginnen, wenn Sie alle Komponenten kennen, die möglicherweise in das System eingehen.
Ein Plugin kann letztendlich aus Folgendem bestehen:
- Kern-Plugin-Code
- Stylesheets
- Java-Skripte
- Lokalisierungsdateien
- Markup
- Bilder
Unter Berücksichtigung all dieser Punkte ist das Widget-Boilerplate-Verzeichnis folgendermaßen aufgebaut:



Wir werden uns jedes Verzeichnis später in diesem Artikel genauer ansehen.
Das Skelett
Neben der Dateiorganisation möchte ich auch den Code stubben, der zum Steuern des Widgets verwendet wird. Der WordPress-Codex [1] enthält eine ausführliche Erläuterung der Widget-API [2]. Da es eine Möglichkeit gibt, sie zu erstellen, versuche ich, sie zu befolgen.
Außerdem bin ich ein Fan davon, meinen Code objektorientiert zusammen mit Codekommentaren zu schreiben, um zu erklären, was in den einzelnen Bereichen des Codes vor sich geht. Daher sieht der ursprüngliche Widget-Code folgendermaßen aus:
<?php /* Plugin Name: TODO Plugin URI: TODO Description: TODO Version: 1.0 Author: TODO Author URI: TODO Author Email: TODO License: Copyright 2011 TODO (email@domain.com) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ // TODO: change 'Plugin_Name' to the name of your actual plugin class Plugin_Name extends WP_Widget { /*--------------------------------------------------*/ /* Constructor /*--------------------------------------------------*/ /** * The widget constructor. Specifies the classname and description, instantiates * the widget, loads localization files, and includes necessary scripts and * styles. */ // TODO: This should match the title given in the class definition above. function Plugin_Name() { // Define constnats used throughout the plugin $this->init_plugin_constants(); // TODO: update classname and description $widget_opts = array ( 'classname' => PLUGIN_NAME, 'description' => __('Short description of the plugin goes here.', PLUGIN_LOCALE) ); $this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts); load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' ); // Load JavaScript and stylesheets $this->register_scripts_and_styles(); } // end constructor /*--------------------------------------------------*/ /* API Functions /*--------------------------------------------------*/ /** * Outputs the content of the widget. * * @args The array of form elements * @instance */ function widget($args, $instance) { extract($args, EXTR_SKIP); echo $before_widget; // TODO: This is where you retrieve the widget values // Display the widget include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php'); echo $after_widget; } // end widget /** * Processes the widget's options to be saved. * * @new_instance The previous instance of values before the update. * @old_instance The new instance of values to be generated via the update. */ function update($new_instance, $old_instance) { $instance = $old_instance; // TODO Update the widget with the new values return $instance; } // end widget /** * Generates the administration form for the widget. * * @instance The array of keys and values for the widget. */ function form($instance) { // TODO define default values for your variables $instance = wp_parse_args( (array)$instance, array( '' => '' ) ); // TODO store the values of widget in a variable // Display the admin form include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php'); } // end form /*--------------------------------------------------*/ /* Private Functions /*--------------------------------------------------*/ /** * Initializes constants used for convenience throughout * the plugin. */ private function init_plugin_constants() { /* TODO * * This provides the unique identifier for your plugin used in * localizing the strings used throughout. * * For example: wordpress-widget-boilerplate-locale. */ if(!defined('PLUGIN_LOCALE')) { define('PLUGIN_LOCALE', 'plugin-name-locale'); } // end if /* TODO * * Define this as the name of your plugin. This is what shows * in the Widgets area of WordPress. * * For example: WordPress Widget Boilerplate. */ if(!defined('PLUGIN_NAME')) { define('PLUGIN_NAME', 'Plugin Name'); } // end if /* TODO * * this is the slug of your plugin used in initializing it with * the WordPress API. * This should also be the * directory in which your plugin resides. Use hyphens. * * For example: wordpress-widget-boilerplate */ if(!defined('PLUGIN_SLUG')) { define('PLUGIN_SLUG', 'plugin-name-slug'); } // end if } // end init_plugin_constants /** * Registers and enqueues stylesheets for the administration panel and the * public facing site. */ private function register_scripts_and_styles() { if(is_admin()) { $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.js', true); $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/admin.css'); } else { $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.css', true); $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/widget.css'); } // end if/else } // end register_scripts_and_styles /** * Helper function for registering and enqueueing scripts and styles. * * @name The ID to register with WordPress * @file_path The path to the actual file * @is_script Optional argument for if the incoming file_path is a JavaScript source file. */ private function load_file($name, $file_path, $is_script = false) { $url = WP_PLUGIN_URL . $file_path; $file = WP_PLUGIN_DIR . $file_path; if(file_exists($file)) { if($is_script) { wp_register_script($name, $url); wp_enqueue_script($name); } else { wp_register_style($name, $url); wp_enqueue_style($name); } // end if } // end if } // end load_file } // end class add_action('widgets_init', create_function('', 'register_widget("Plugin_Name");')); // TODO remember to change this to match the class definition above ?>
Beachten Sie, dass der gesamte Code eine Reihe von TODOs enthält. Diese sind besonders nützlich, wenn Sie Ihren Code auf die Boilerplate schreiben.
Beachten Sie, dass es auch drei primäre Codeabschnitte gibt:
- Konstrukteur. Diese Funktion ist für die Initialisierung des Widgets, den Import von Lokalisierungsdateien und die Einbeziehung von JavaScript-Quellen und Stylesheets verantwortlich.
- API-Funktionen. Diese Funktionen sind die drei Funktionen, die zum Verwalten, Anzeigen und Aktualisieren des Widgets erforderlich sind.
- Hilfsfunktionen. Dies sind private Funktionen, mit denen ich bei sich oft wiederholenden oder erforderlichen Aufgaben helfe.
Die drei wichtigsten Funktionen oben, die API-Funktionen, werden für die Entwicklung Ihres Plugins benötigt.
- widget() extrahiert die gespeicherten Werte und rendert die öffentliche Ansicht
- update() ist dafür verantwortlich, die zuvor gespeicherten Werte mit den vom Benutzer angegebenen Werten zu aktualisieren
- form() rendert das Administrationsformular und bietet Funktionen, die zum Speichern neuer Werte erforderlich sind.
Da Plugins häufig zwischen der Verwaltungsfunktionalität und der Client-Funktionalität aufgeteilt werden, teile ich meine JavaScript-Quelle, Stylesheets und HTML entsprechend auf. Ich benenne diese Dateien entsprechend und lösche sie entsprechend aus:
JavaScript-Quellen:
admin.js:
jQuery(function($) { // Place your administration-specific code here });
widget.js:
jQuery(function($) { // Place your public facing JavaScript here });
Stylesheets:
admin.css:
/* This style sheet is used to style the admin option form of the widget. */
widget.css:
/* This style sheet is used to style the public view of the widget. */
Ansichten:
<!-- This file is used to markup the administration form of the widget. --> <!-- This file is used to markup the public facing widget. -->
Einfach richtig? Sie können diese gesamte Boilerplate einschließlich der Lokalisierungsdateien und der README auf GitHub anzeigen (und teilen!).
Es gibt jetzt einen Platz für alles und wenn es Zeit für den Versand ist, schließen Sie einfach bestimmte Dateien vom endgültigen Build aus.
Ein Arbeitsbeispiel mit Ihren sozialen Netzwerken
Wenn es um das Programmieren geht, hilft das Üben beim Erlernen einer neuen Sprache oder eines neuen Tipps. Hier finden Sie ein kurzes Beispiel für die Verwendung des oben genannten Boilerplates, um ein einfaches Widget zu erstellen, mit dem Sie Ihre Twitter-, Facebook- und Google+ Links einfach teilen können.
Zunächst listen wir die Anforderungen auf:
- Eine Administrationsansicht zur Eingabe von Werten. Dies umfasst Markups und Stile.
- Eine öffentlich zugängliche Ansicht zum Anzeigen von Links zu sozialen Netzwerken. Dies umfasst auch Markups und Stile.
- Optionen zum Speichern eines Twitter-Benutzernamens, eines Facebook-Benutzernamens und einer Google+ ID
Zweitens öffnen wir die Kesselplatte und beginnen, die erforderlichen Teile herauszuschneiden.
Zuerst definieren wir die Werte für Plugin-Namen, Slug und Gebietsschema. Diese werden im gesamten Code wiederholt verwendet, daher ist es hilfreich, sie als Konstanten zu speichern, um sie leicht abrufen zu können. Suchen Sie die Funktion init_plugin_constants() und stellen Sie sicher, dass Ihr Code folgendermaßen aussieht:
private function init_plugin_constants() { if(!defined('PLUGIN_LOCALE')) { define('PLUGIN_LOCALE', 'my-social-network-locale'); } // end if if(!defined('PLUGIN_NAME')) { define('PLUGIN_NAME', 'My Social Networks'); } // end if if(!defined('PLUGIN_SLUG')) { define('PLUGIN_SLUG', 'My-Social-Networks'); } // end if } // end init_plugin_constants
Danach müssen wir den Konstruktor vorbereiten:
function My_Social_Network() { // Define constants used throughout the plugin $this->init_plugin_constants(); $widget_opts = array ( 'classname' => PLUGIN_NAME, 'description' => __('A simple WordPress widget for sharing a few of your social networks.', PLUGIN_LOCALE) ); $this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts); load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' ); // Load JavaScript and stylesheets $this->register_scripts_and_styles(); } // end constructor
Und benutzen die API-Funktionen:
function widget($args, $instance) { extract($args, EXTR_SKIP); echo $before_widget; $twitter_username = empty($instance['twitter_username']) ? '' : apply_filters('twitter_username', $instance['twitter_username']); $facebook_username = empty($instance['facebook_username']) ? '' : apply_filters('facebook_username', $instance['facebook_username']); $google_plus_id = empty($instance['google_plus_id']) ? '' : apply_filters('google_plus_id', $instance['google_plus_id']); // Display the widget include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php'); echo $after_widget; } // end widget function update($new_instance, $old_instance) { $instance = $old_instance; $instance['twitter_username'] = strip_tags(stripslashes($new_instance['twitter_username'])); $instance['facebook_username'] = strip_tags(stripslashes($new_instance['facebook_username'])); $instance['google_plus_id'] = strip_tags(stripslashes($new_instance['google_plus_id'])); return $instance; } // end widget function form($instance) { $instance = wp_parse_args( (array)$instance, array( 'twitter_username' => '', 'facebook_username' => '', 'google_plus_id' => '' ) ); $twitter_username = strip_tags(stripslashes($new_instance['twitter_username'])); $facebook_username = strip_tags(stripslashes($new_instance['facebook_username'])); $google_plus_id = strip_tags(stripslashes($new_instance['google_plus_id'])); // Display the admin form include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php'); } // end form
Die endgültige Version des Plugins sollte folgendermaßen aussehen:
<?php /* Plugin Name: My Social Network Plugin URI: http://github.com/tommcfarlin/My-Social-Network Description: A simple WordPress widget for sharing a few of your social networks. Version: 1.0 Author: Tom McFarlin Author URI: http://tommcfarlin.com Author Email: tom@tommcfarlin.com License: Copyright 2011 My Social Network (tom@tommcfarlin.com) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ class My_Social_Network extends WP_Widget { /*--------------------------------------------------*/ /* Constructor /*--------------------------------------------------*/ /** * The widget constructor. Specifies the classname and description, instantiates * the widget, loads localization files, and includes necessary scripts and * styles. */ function My_Social_Network() { // Define constants used throughout the plugin $this->init_plugin_constants(); $widget_opts = array ( 'classname' => PLUGIN_NAME, 'description' => __('A simple WordPress widget for sharing a few of your social networks.', PLUGIN_LOCALE) ); $this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts); load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' ); // Load JavaScript and stylesheets $this->register_scripts_and_styles(); } // end constructor /*--------------------------------------------------*/ /* API Functions /*--------------------------------------------------*/ /** * Outputs the content of the widget. * * @args The array of form elements * @instance */ function widget($args, $instance) { extract($args, EXTR_SKIP); echo $before_widget; $twitter_username = empty($instance['twitter_username']) ? '' : apply_filters('twitter_username', $instance['twitter_username']); $facebook_username = empty($instance['facebook_username']) ? '' : apply_filters('facebook_username', $instance['facebook_username']); $google_plus_id = empty($instance['google_plus_id']) ? '' : apply_filters('google_plus_id', $instance['google_plus_id']); // Display the widget include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php'); echo $after_widget; } // end widget /** * Processes the widget's options to be saved. * * @new_instance The previous instance of values before the update. * @old_instance The new instance of values to be generated via the update. */ function update($new_instance, $old_instance) { $instance = $old_instance; $instance['twitter_username'] = strip_tags(stripslashes($new_instance['twitter_username'])); $instance['facebook_username'] = strip_tags(stripslashes($new_instance['facebook_username'])); $instance['google_plus_id'] = strip_tags(stripslashes($new_instance['google_plus_id'])); return $instance; } // end widget /** * Generates the administration form for the widget. * * @instance The array of keys and values for the widget. */ function form($instance) { $instance = wp_parse_args( (array)$instance, array( 'twitter_username' => '', 'facebook_username' => '', 'google_plus_id' => '' ) ); $twitter_username = strip_tags(stripslashes($new_instance['twitter_username'])); $facebook_username = strip_tags(stripslashes($new_instance['facebook_username'])); $google_plus_id = strip_tags(stripslashes($new_instance['google_plus_id'])); // Display the admin form include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php'); } // end form /*--------------------------------------------------*/ /* Private Functions /*--------------------------------------------------*/ /** * Initializes constants used for convenience throughout * the plugin. */ private function init_plugin_constants() { if(!defined('PLUGIN_LOCALE')) { define('PLUGIN_LOCALE', 'my-social-network-locale'); } // end if if(!defined('PLUGIN_NAME')) { define('PLUGIN_NAME', 'My Social Networks'); } // end if if(!defined('PLUGIN_SLUG')) { define('PLUGIN_SLUG', 'My-Social-Networks'); } // end if } // end init_plugin_constants /** * Registers and enqueues stylesheets for the administration panel and the * public facing site. */ private function register_scripts_and_styles() { if(is_admin()) { $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.js', true); $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/admin.css'); } else { $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.css', true); $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/widget.css'); } // end if/else } // end register_scripts_and_styles /** * Helper function for registering and enqueueing scripts and styles. * * @name The ID to register with WordPress * @file_path The path to the actual file * @is_script Optional argument for if the incoming file_path is a JavaScript source file. */ private function load_file($name, $file_path, $is_script = false) { $url = WP_PLUGIN_URL . $file_path; $file = WP_PLUGIN_DIR . $file_path; if(file_exists($file)) { if($is_script) { wp_register_script($name, $url); wp_enqueue_script($name); } else { wp_register_style($name, $url); wp_enqueue_style($name); } // end if } // end if } // end load_file } // end class add_action('widgets_init', create_function('', 'register_widget("My_Social_Network");')); ?>
Als Nächstes fügen wir dem Verwaltungsformular einige Stile hinzu. Schen Sie /css/admin.css und fügen Sie den folgenden Code hinzu:
.wrapper fieldset { border: 1px solid #ddd; width: 90%; padding: 5%; } .option { margin: 12px 0 12px 0; } .option input { width: 100%; }
Und schreiben wir das Markup, das die Ansicht des Administrationsformulars rendert:
<div class="wrapper"> <fieldset> <legend> <?php _e('My Social Networks', PLUGIN_LOCALE); ?> </legend> <div class="option"> <label for="twitter"> <?php _e('Twitter Username', PLUGIN_LOCALE); ?> </label> <input type="text" id="<?php echo $this->get_field_id('twitter_username'); ?>" name="<?php echo $this->get_field_name('twitter_username'); ?>" value="<?php echo $instance['twitter_username']; ?>" class="" /> </div> <div class="option"> <label for="facebook"> <?php _e('Facebook Username', PLUGIN_LOCALE); ?> </label> <input type="text" id="<?php echo $this->get_field_id('facebook_username'); ?>" name="<?php echo $this->get_field_name('facebook_username'); ?>" value="<?php echo $instance['facebook_username']; ?>" class="" /> </div> <div class="option"> <label for="google_plus"> <?php _e('Google+ ID', PLUGIN_LOCALE); ?> </label> <input type="text" id="<?php echo $this->get_field_id('google_plus_id'); ?>" name="<?php echo $this->get_field_name('google_plus_id'); ?>" value="<?php echo $instance['google_plus_id']; ?>" class="" /> </div> </fieldset> </div><!-- /wrapper -->
Schließlich müssen wir ein Markup schreiben, um die öffentlich zugängliche Ansicht des Widgets zu rendern, wenn es live im aktuellen Blog ist:
<h3> <?php _e('My Social Networks', PLUGIN_LOCALE); ?> </h3> <ul class="my-social-networks"> <?php if(strlen(trim($twitter_username)) > 0) { ?> <li> <a href="http://twitter.com/<?php echo $twitter_username; ?>"> <?php _e('Twitter', PLUGIN_LOCALE); ?> </a> </li> <?php } // end if ?> <?php if(strlen(trim($facebook_username)) > 0) { ?> <li> <a href="http://facebook.com/<?php echo $facebook_username; ?>"> <?php _e('Facebook', PLUGIN_LOCALE); ?> </a> </li> <?php } // end if ?> <?php if(strlen(trim($google_plus_id)) > 0) { ?> <li> <a href="http://plus.google.com/<?php echo $google_plus_id; ?>"> <?php _e('Google+', PLUGIN_LOCALE); ?> </a> </li> <?php } // end if ?> </ul><!-- /my-social-networks -->
Gemacht und gemacht. Nicht schlecht, oder? Eine Menge Arbeit und Funktionalität sind relativ schnell erledigt.

Sie können den funktionierenden Quellcode (einschließlich einer zugehörigen README-Datei) für dieses Widget auf GitHub oder direkt hier bei Wptuts herunterladen.
Letztendlich bedeutet die Pflege eines Softwareprojekts, dass versucht wird, die Komplexität zu organisieren. Obwohl das obige Boilerplate nicht *die* Art und Weise ist, wie Code organisiert oder verwaltet wird, ist es eine *effektive* Art, Code zu organisieren, und ich habe festgestellt, dass es in vielen meiner Projekte äußerst hilfreich ist und Ihnen hoffentlich bei Ihrer zukünftigen Arbeit hilft.
Denken Sie daran, dass Sie eine Kopie des Boilerplates und des Beispielprojekts aus den entsprechenden GitHub-Repositorys abrufen können. Ich empfehle außerdem dringend, den WordPress-Codex[1] mit einem Lesezeichen zu versehen. Es ist eine enorme Ressource für alle, die fortgeschrittene WordPress-Entwicklung betreiben möchten.
Weiter zum zweiten Teil...
Schauen Sie sich den zweiten Teil dieser Tutorial-Reihe an, in dem wir uns eingehender mit der Erstellung wartbarer Plugins befassen! Wir werden uns ansehen, wie man hooks
in WordPress verwendet - und dann werden wir unser Boilerplate tatsächlich verwenden, um ein weiteres nützliches Plugin zu erstellen. Bereit für den zweiten Teil?