{"id":70347,"date":"2024-06-19T10:53:06","date_gmt":"2024-06-19T09:53:06","guid":{"rendered":"https:\/\/kinsta.com\/de\/?p=70347&#038;preview=true&#038;preview_id=70347"},"modified":"2024-06-28T08:47:46","modified_gmt":"2024-06-28T07:47:46","slug":"wordpress-plugin-erweitern","status":"publish","type":"post","link":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/","title":{"rendered":"WordPress-Plugins mit PHP-Klassen erweitern"},"content":{"rendered":"<p>WordPress-Plugins k\u00f6nnen mit zus\u00e4tzlichen Funktionen erweitert werden, wie beliebte Plugins wie <a href=\"https:\/\/kinsta.com\/de\/blog\/woocommerce-tutorial\/\">WooCommerce<\/a> und Gravity Forms zeigen. In dem Artikel <a href=\"https:\/\/kinsta.com\/de\/blog\/pro-kostenlose-versionen-wordpress-plugin\/\">Ein WordPress-Plugin so gestalten, dass es Erweiterungen unterst\u00fctzt<\/a> erfahren wir, dass es zwei M\u00f6glichkeiten gibt, ein WordPress-Plugin erweiterbar zu machen:<\/p>\n<ol>\n<li>Durch das Einrichten von Hooks (Aktionen und Filter), mit denen Erweiterungs-Plugins ihre eigenen Funktionen einf\u00fcgen k\u00f6nnen<\/li>\n<li>Durch die Bereitstellung von PHP-Klassen, die Erweiterungs-Plugins erben k\u00f6nnen<\/li>\n<\/ol>\n<p>Die erste Methode st\u00fctzt sich eher auf die Dokumentation, in der die verf\u00fcgbaren Hooks und ihre Verwendung detailliert beschrieben werden. Die zweite Methode hingegen bietet gebrauchsfertigen Code f\u00fcr Erweiterungen und macht eine umfangreiche Dokumentation \u00fcberfl\u00fcssig. Das ist von Vorteil, da die Erstellung von Dokumentation neben dem Code die Verwaltung und Ver\u00f6ffentlichung des Plugins erschweren kann.<\/p>\n<p>Die direkte Bereitstellung von <a href=\"https:\/\/kinsta.com\/php\/\">PHP-Klassen<\/a> ersetzt die Dokumentation effektiv durch Code. Anstatt zu lehren, wie eine Funktion zu implementieren ist, liefert das Plugin den notwendigen PHP-Code und vereinfacht damit die Aufgabe f\u00fcr Drittentwickler.<\/p>\n<p>Wir wollen uns einige Techniken ansehen, um dies zu erreichen, mit dem Ziel, ein \u00d6kosystem von Integrationen rund um unser WordPress-Plugin zu schaffen.<\/p>\n<div><\/div><kinsta-auto-toc heading=\"Table of Contents\" exclude=\"last\" list-style=\"arrow\" selector=\"h2\" count-number=\"-1\"><\/kinsta-auto-toc>\n<h2>Definition von PHP-Basisklassen im WordPress-Plugin<\/h2>\n<p>Das <a href=\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin\/\">WordPress-Plugin<\/a> enth\u00e4lt PHP-Klassen, die von Erweiterungs-Plugins verwendet werden k\u00f6nnen. Diese PHP-Klassen werden m\u00f6glicherweise nicht vom Haupt-Plugin selbst verwendet, sondern sind speziell f\u00fcr die Nutzung durch andere Plugins vorgesehen.<\/p>\n<p>Schauen wir uns an, wie dies im Open-Source-Plugin <a href=\"https:\/\/wordpress.org\/plugins\/gatographql\/\" target=\"_blank\" rel=\"noopener noreferrer\">Gato GraphQL<\/a> umgesetzt wird.<\/p>\n<h3>AbstractPlugin-Klasse:<\/h3>\n<p><a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/f918258371ec4c49c68b64fa184212cf0a956c16\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/PluginSkeleton\/AbstractPlugin.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>AbstractPlugin<\/code><\/a> stellt ein Plugin dar, sowohl f\u00fcr das Haupt-Plugin von Gato GraphQL als auch f\u00fcr seine Erweiterungen:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractPlugin implements PluginInterface\n{\n  protected string $pluginBaseName;\n  protected string $pluginSlug;\n  protected string $pluginName;\n\n  public function __construct(\n    protected string $pluginFile,\n    protected string $pluginVersion,\n    ?string $pluginName,\n  ) {\n    $this-&gt;pluginBaseName = plugin_basename($pluginFile);\n    $this-&gt;pluginSlug = dirname($this-&gt;pluginBaseName);\n    $this-&gt;pluginName = $pluginName ?? $this-&gt;pluginBaseName;\n  }\n\n  public function getPluginName(): string\n  {\n    return $this-&gt;pluginName;\n  }\n\n  public function getPluginBaseName(): string\n  {\n    return $this-&gt;pluginBaseName;\n  }\n\n  public function getPluginSlug(): string\n  {\n    return $this-&gt;pluginSlug;\n  }\n\n  public function getPluginFile(): string\n  {\n    return $this-&gt;pluginFile;\n  }\n\n  public function getPluginVersion(): string\n  {\n    return $this-&gt;pluginVersion;\n  }\n\n  public function getPluginDir(): string\n  {\n    return dirname($this-&gt;pluginFile);\n  }\n\n  public function getPluginURL(): string\n  {\n    return plugin_dir_url($this-&gt;pluginFile);\n  }\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<h3>AbstractMainPlugin-Klasse:<\/h3>\n<p><a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/6545e639ceb85180bf869f50dcaae56de37421ea\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/PluginSkeleton\/AbstractMainPlugin.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>AbstractMainPlugin<\/code><\/a> erweitert <code>AbstractPlugin<\/code>, um das Hauptplugin zu repr\u00e4sentieren:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  public function __construct(\n    string $pluginFile,\n    string $pluginVersion,\n    ?string $pluginName,\n    protected MainPluginInitializationConfigurationInterface $pluginInitializationConfiguration,\n  ) {\n    parent::__construct(\n      $pluginFile,\n      $pluginVersion,\n      $pluginName,\n    );\n  }\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<h3>AbstractExtension Klasse:<\/h3>\n<p>\u00c4hnlich, <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/2447b3478ecfd012d509ad3075149cc0191fb3a1\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/PluginSkeleton\/AbstractExtension.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>AbstractExtension<\/code><\/a> erweitert <code>AbstractPlugin<\/code>, um ein Erweiterungs-Plugin darzustellen:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface\n{\n  public function __construct(\n    string $pluginFile,\n    string $pluginVersion,\n    ?string $pluginName,\n    protected ?ExtensionInitializationConfigurationInterface $extensionInitializationConfiguration,\n  ) {\n    parent::__construct(\n      $pluginFile,\n      $pluginVersion,\n      $pluginName,\n    );\n  }\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<p>Beachte, dass <code>AbstractExtension<\/code> im Hauptplugin enthalten ist und Funktionen zur Registrierung und Initialisierung einer Erweiterung bereitstellt. Es wird jedoch nur von Erweiterungen verwendet, nicht vom Hauptplugin selbst.<\/p>\n<p>Die Klasse <code>AbstractPlugin<\/code> enth\u00e4lt einen gemeinsamen Initialisierungscode, der zu verschiedenen Zeitpunkten aufgerufen wird. Diese Methoden sind auf der Ebene der Vorfahren definiert, werden aber von den vererbenden Klassen entsprechend ihrer Lebenszyklen aufgerufen.<\/p>\n<p>Das Hauptplugin und die Erweiterungen werden initialisiert, indem die Methode <code>setup<\/code> der entsprechenden Klasse ausgef\u00fchrt wird, die in der Hauptdatei des WordPress-Plugins aufgerufen wird.<\/p>\n<p>In Gato GraphQL geschieht dies zum Beispiel in <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/135a11e2637c76410a1d23d090fc4ec9b87c01fb\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/gatographql.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>gatographql.php<\/code><\/a>:<\/p>\n<pre><code class=\"language-php\">$pluginFile = __FILE__;\n$pluginVersion = '2.4.0';\n$pluginName = __('Gato GraphQL', 'gatographql');\nPluginApp::getMainPluginManager()-&gt;register(new Plugin(\n  $pluginFile,\n  $pluginVersion,\n  $pluginName\n))-&gt;setup();\n<\/code><\/pre>\n<h3>setup-Methode:<\/h3>\n<p>Auf der Vorg\u00e4ngerebene enth\u00e4lt <code>setup<\/code> die gemeinsame Logik zwischen dem Plugin und seinen Erweiterungen, z. B. die Aufhebung der Registrierung, wenn das Plugin deaktiviert wird. Diese Methode ist nicht endg\u00fcltig; sie kann von den vererbenden Klassen \u00fcberschrieben werden, um ihre Funktionalit\u00e4t zu erweitern:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractPlugin implements PluginInterface\n{\n  \/\/ ...\n\n  public function setup(): void\n  {\n    register_deactivation_hook(\n      $this-&gt;getPluginFile(),\n      $this-&gt;deactivate(...)\n    );\n  }\n\n  public function deactivate(): void\n  {\n    $this-&gt;removePluginVersion();\n  }\n\n  private function removePluginVersion(): void\n  {\n    $pluginVersions = get_option('gatographql-plugin-versions', []);\n    unset($pluginVersions[$this-&gt;pluginBaseName]);\n    update_option('gatographql-plugin-versions', $pluginVersions);\n  }\n}\n<\/code><\/pre>\n<h3>Die Setup-Methode des Haupt-Plugins:<\/h3>\n<p>Die <code>setup<\/code> Methode des Hauptplugins initialisiert den Lebenszyklus der Anwendung. Sie f\u00fchrt die Funktionen des Hauptplugins \u00fcber Methoden wie <code>initialize<\/code>, <code>configureComponents<\/code>, <code>configure<\/code> und <code>boot<\/code> aus und l\u00f6st die entsprechenden Aktionshaken f\u00fcr Erweiterungen aus:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  public function setup(): void\n  {\n    parent::setup();\n\n    add_action('plugins_loaded', function (): void\n    {\n      \/\/ 1. Initialize main plugin\n      $this-&gt;initialize();\n\n      \/\/ 2. Initialize extensions\n      do_action('gatographql:initializeExtension');\n\n      \/\/ 3. Configure main plugin components\n      $this-&gt;configureComponents();\n\n      \/\/ 4. Configure extension components\n      do_action('gatographql:configureExtensionComponents');\n\n      \/\/ 5. Configure main plugin\n      $this-&gt;configure();\n\n      \/\/ 6. Configure extension\n      do_action('gatographql:configureExtension');\n\n      \/\/ 7. Boot main plugin\n      $this-&gt;boot();\n\n      \/\/ 8. Boot extension\n      do_action('gatographql:bootExtension');\n    }\n\n    \/\/ ...\n  }\n  \n  \/\/ ...\n}\n<\/code><\/pre>\n<h3>Methode &#8222;Extension setup&#8220;:<\/h3>\n<p>Die Klasse <code>AbstractExtension<\/code> f\u00fchrt ihre Logik \u00fcber die entsprechenden Hooks aus:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface\n{\n  \/\/ ...\n\n  final public function setup(): void\n  {\n    parent::setup();\n\n    add_action('plugins_loaded', function (): void\n    {\n      \/\/ 2. Initialize extensions\n      add_action(\n        'gatographql:initializeExtension',\n        $this-&gt;initialize(...)\n      );\n\n      \/\/ 4. Configure extension components\n      add_action(\n        'gatographql:configureExtensionComponents',\n        $this-&gt;configureComponents(...)\n      );\n\n      \/\/ 6. Configure extension\n      add_action(\n        'gatographql:configureExtension',\n        $this-&gt;configure(...)\n      );\n\n      \/\/ 8. Boot extension\n      add_action(\n        'gatographql:bootExtension',\n        $this-&gt;boot(...)\n      );\n    }, 20);\n  }\n}\n<\/code><\/pre>\n<p>Die Methoden <code>initialize<\/code>, <code>configureComponents<\/code>, <code>configure<\/code> und <code>boot<\/code> sind sowohl f\u00fcr das Hauptplugin als auch f\u00fcr die Erweiterungen gleich und k\u00f6nnen eine gemeinsame Logik haben. Diese gemeinsame Logik wird in der Klasse <code>AbstractPlugin<\/code> gespeichert.<\/p>\n<p>Die Methode <code>configure<\/code> beispielsweise konfiguriert das Plugin oder die Erweiterungen, indem sie <code>callPluginInitializationConfiguration<\/code> aufruft, die f\u00fcr das Hauptplugin und die Erweiterungen unterschiedliche Implementierungen hat und als abstrakt definiert ist, und <code>getModuleClassConfiguration<\/code>, das ein Standardverhalten bietet, aber bei Bedarf \u00fcberschrieben werden kann:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractPlugin implements PluginInterface\n{\n  \/\/ ...\n\n  public function configure(): void\n  {\n    $this-&gt;callPluginInitializationConfiguration();\n\n    $appLoader = App::getAppLoader();\n    $appLoader-&gt;addModuleClassConfiguration($this-&gt;getModuleClassConfiguration());\n  }\n\n  abstract protected function callPluginInitializationConfiguration(): void;\n\n  \/**\n   * @return array&lt;class-string&lt;ModuleInterface&gt;,mixed&gt; [key]: Module class, [value]: Configuration\n   *\/\n  public function getModuleClassConfiguration(): array\n  {\n    return [];\n  }\n}\n<\/code><\/pre>\n<p>Das Haupt-Plugin stellt seine Implementierung f\u00fcr <code>callPluginInitializationConfiguration<\/code> zur Verf\u00fcgung:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  \/\/ ...\n\n  protected function callPluginInitializationConfiguration(): void\n  {\n    $this-&gt;pluginInitializationConfiguration-&gt;initialize();\n  }\n}\n<\/code><\/pre>\n<p>Ebenso stellt die Erweiterungsklasse ihre Implementierung zur Verf\u00fcgung:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractExtension extends AbstractPlugin implements ExtensionInterface\n{\n  \/\/ ...\n\n  protected function callPluginInitializationConfiguration(): void\n  {\n    $this-&gt;extensionInitializationConfiguration?-&gt;initialize();\n  }\n}\n<\/code><\/pre>\n<p>Die Methoden <code>initialize<\/code>, <code>configureComponents<\/code> und <code>boot<\/code> sind auf der Vorfahrenebene definiert und k\u00f6nnen von den erbenden Klassen \u00fcberschrieben werden:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractPlugin implements PluginInterface\n{\n  \/\/ ...\n\n  public function initialize(): void\n  {\n    $moduleClasses = $this-&gt;getModuleClassesToInitialize();\n    App::getAppLoader()-&gt;addModuleClassesToInitialize($moduleClasses);\n  }\n\n  \/**\n   * @return array&lt;class-string&lt;ModuleInterface&gt;&gt; List of `Module` class to initialize\n   *\/\n  abstract protected function getModuleClassesToInitialize(): array;\n\n  public function configureComponents(): void\n  {\n    $classNamespace = ClassHelpers::getClassPSR4Namespace(get_called_class());\n    $moduleClass = $classNamespace . '\\\\Module';\n    App::getModule($moduleClass)-&gt;setPluginFolder(dirname($this-&gt;pluginFile));\n  }\n\n  public function boot(): void\n  {\n    \/\/ By default, do nothing\n  }\n}\n<\/code><\/pre>\n<p>Alle Methoden k\u00f6nnen von <code>AbstractMainPlugin<\/code> oder <code>AbstractExtension<\/code> \u00fcberschrieben werden, um sie mit ihren eigenen Funktionen zu erweitern.<\/p>\n<p>F\u00fcr das Hauptplugin entfernt die Methode <code>setup<\/code> auch jegliches Caching aus der WordPress-Instanz, wenn das Plugin oder eine seiner Erweiterungen aktiviert oder deaktiviert wird:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  public function setup(): void\n  {\n    parent::setup();\n\n    \/\/ ...\n\n    \/\/ Main-plugin specific methods\n    add_action(\n      'activate_plugin',\n      function (string $pluginFile): void {\n        $this-&gt;maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);\n      }\n    );\n    add_action(\n      'deactivate_plugin',\n      function (string $pluginFile): void {\n        $this-&gt;maybeRegenerateContainerWhenPluginActivatedOrDeactivated($pluginFile);\n      }\n    );\n  }\n\n  public function maybeRegenerateContainerWhenPluginActivatedOrDeactivated(string $pluginFile): void\n  {\n    \/\/ Removed code for simplicity\n  }\n\n  \/\/ ...\n}\n<\/code><\/pre>\n<p>In \u00e4hnlicher Weise entfernt die Methode <code>deactivate<\/code> das Caching und <code>boot<\/code> f\u00fchrt zus\u00e4tzliche Aktionshaken nur f\u00fcr das Hauptplugin aus:<\/p>\n<pre><code class=\"language-php\">abstract class AbstractMainPlugin extends AbstractPlugin implements MainPluginInterface\n{\n  public function deactivate(): void\n  {\n    parent::deactivate();\n\n    $this-&gt;removeTimestamps();\n  }\n\n  protected function removeTimestamps(): void\n  {\n    $userSettingsManager = UserSettingsManagerFacade::getInstance();\n    $userSettingsManager-&gt;removeTimestamps();\n  }\n\n  public function boot(): void\n  {\n    parent::boot();\n\n    add_filter(\n      'admin_body_class',\n      function (string $classes): string {\n        $extensions = PluginApp::getExtensionManager()-&gt;getExtensions();\n        $commercialExtensionActivatedLicenseObjectProperties = SettingsHelpers::getCommercialExtensionActivatedLicenseObjectProperties();\n        foreach ($extensions as $extension) {\n          $extensionCommercialExtensionActivatedLicenseObjectProperties = $commercialExtensionActivatedLicenseObjectProperties[$extension-&gt;getPluginSlug()] ?? null;\n          if ($extensionCommercialExtensionActivatedLicenseObjectProperties === null) {\n            continue;\n          }\n          return $classes . ' is-gatographql-customer';\n        }\n        return $classes;\n      }\n    );\n  }\n}\n<\/code><\/pre>\n<p>Aus dem oben dargestellten Code wird deutlich, dass wir bei der Entwicklung und Programmierung eines WordPress-Plugins die Bed\u00fcrfnisse der Erweiterungen ber\u00fccksichtigen und den Code so oft wie m\u00f6glich wiederverwenden m\u00fcssen. Die Umsetzung solider <a href=\"https:\/\/kinsta.com\/de\/blog\/oop-php\/\">objektorientierter Programmiermuster<\/a> (wie die <a href=\"https:\/\/dev.to\/evrtrabajo\/solid-in-php-d8e\" target=\"_blank\" rel=\"noopener noreferrer\">SOLID-Prinzipien<\/a>) hilft dabei und macht die Codebasis langfristig wartbar.<\/p>\n<h2>Deklaration und Validierung der Versionsabh\u00e4ngigkeit<\/h2>\n<p>Da die Erweiterung von einer PHP-Klasse erbt, die vom Plugin bereitgestellt wird, ist es wichtig zu \u00fcberpr\u00fcfen, ob die erforderliche Version des Plugins vorhanden ist. Wenn du das nicht tust, kann es zu Konflikten kommen, die die Website zum Absturz bringen.<\/p>\n<p>Wenn z. B. die Klasse <code>AbstractExtension<\/code> aktualisiert wird und eine Hauptversion <code>4.0.0<\/code> von der vorherigen Version <code>3.4.0<\/code> ver\u00f6ffentlicht wird, kann das Laden der Erweiterung ohne \u00dcberpr\u00fcfung der Version zu einem PHP-Fehler f\u00fchren und WordPress am Laden hindern.<\/p>\n<p>Um dies zu vermeiden, muss die Erweiterung \u00fcberpr\u00fcfen, ob das installierte Plugin die Version <code>3.x.x<\/code> hat. Wenn die Version <code>4.0.0<\/code> installiert ist, wird die Erweiterung deaktiviert und verhindert so Fehler.<\/p>\n<p>Die Erweiterung kann diese \u00dcberpr\u00fcfung mit der folgenden Logik durchf\u00fchren, die am <code>plugins_loaded<\/code> -Hook (da das Haupt-Plugin zu diesem Zeitpunkt bereits geladen ist) in der <a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/4ce883e3ec5325f15797aaff3042370f25e7e671\/templates\/basic\/layers\/GatoGraphQLForWP\/plugins\/extension-template\/gatographql-extension-template.php\" target=\"_blank\" rel=\"noopener noreferrer\">Haupt-Plugin-Datei der Erweiterung<\/a> ausgef\u00fchrt wird. Diese Logik greift auf die <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/4d33af1ad5c40318b0c2e612d1114c5ba342b06e\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/src\/PluginManagement\/ExtensionManager.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>ExtensionManager<\/code><\/a> Klasse zu, die im Hauptplugin enthalten ist, um Erweiterungen zu verwalten:<\/p>\n<pre><code class=\"language-php\">\/**\n * Create and set-up the extension\n *\/\nadd_action(\n  'plugins_loaded',\n  function (): void {\n    \/**\n     * Extension's name and version.\n     *\n     * Use a stability suffix as supported by Composer.\n     *\/\n    $extensionVersion = '1.1.0';\n    $extensionName = __('Gato GraphQL - Extension Template');\n\n    \/**\n     * The minimum version required from the Gato GraphQL plugin\n     * to activate the extension.\n     *\/\n    $gatoGraphQLPluginVersionConstraint = '^1.0';\n    \n    \/**\n     * Validate Gato GraphQL is active\n     *\/\n    if (!class_exists(\\GatoGraphQL\\GatoGraphQL\\Plugin::class)) {\n      add_action('admin_notices', function () use ($extensionName) {\n        printf(\n          '&lt;div class=\"notice notice-error\"&gt;&lt;p&gt;%s&lt;\/p&gt;&lt;\/div&gt;',\n          sprintf(\n            __('Plugin &lt;strong&gt;%s&lt;\/strong&gt; is not installed or activated. Without it, plugin &lt;strong&gt;%s&lt;\/strong&gt; will not be loaded.'),\n            __('Gato GraphQL'),\n            $extensionName\n          )\n        );\n      });\n      return;\n    }\n\n    $extensionManager = \\GatoGraphQL\\GatoGraphQL\\PluginApp::getExtensionManager();\n    if (!$extensionManager-&gt;assertIsValid(\n      GatoGraphQLExtension::class,\n      $extensionVersion,\n      $extensionName,\n      $gatoGraphQLPluginVersionConstraint\n    )) {\n      return;\n    }\n    \n    \/\/ Load Composer\u2019s autoloader\n    require_once(__DIR__ . '\/vendor\/autoload.php');\n\n    \/\/ Create and set-up the extension instance\n    $extensionManager-&gt;register(new GatoGraphQLExtension(\n      __FILE__,\n      $extensionVersion,\n      $extensionName,\n    ))-&gt;setup();\n  }\n);\n<\/code><\/pre>\n<p>Beachte, dass die Erweiterung eine Abh\u00e4ngigkeit von der Versionsbeschr\u00e4nkung <code>^1.0<\/code> des Hauptplugins deklariert (unter Verwendung <a href=\"https:\/\/getcomposer.org\/doc\/articles\/versions.md#writing-version-constraints\" target=\"_blank\" rel=\"noopener noreferrer\">der Versionsbeschr\u00e4nkungen des Composers<\/a>). Wenn also die Version <code>2.0.0<\/code> von Gato GraphQL installiert ist, wird die Erweiterung nicht aktiviert.<\/p>\n<p>Die Versionsbeschr\u00e4nkung wird \u00fcber die Methode <code>ExtensionManager::assertIsValid<\/code> \u00fcberpr\u00fcft, die <code>Semver::satisfies<\/code> aufruft (bereitgestellt durch das <a href=\"https:\/\/packagist.org\/packages\/composer\/semver\" target=\"_blank\" rel=\"noopener noreferrer\">Paket <code>composer\/semver<\/code><\/a>):<\/p>\n<pre><code class=\"language-php\">use Composer\\Semver\\Semver;\n\nclass ExtensionManager extends AbstractPluginManager\n{\n  \/**\n   * Validate that the required version of the Gato GraphQL for WP plugin is installed.\n   *\n   * If the assertion fails, it prints an error on the WP admin and returns false\n   *\n   * @param string|null $mainPluginVersionConstraint the semver version constraint required for the plugin (eg: \"^1.0\" means &gt;=1.0.0 and &lt;2.0.0)\n   *\/\n  public function assertIsValid(\n    string $extensionClass,\n    string $extensionVersion,\n    ?string $extensionName = null,\n    ?string $mainPluginVersionConstraint = null,\n  ): bool {\n    $mainPlugin = \\GatoGraphQL\\GatoGraphQL\\PluginApp::getMainPluginManager()-&gt;getPlugin();\n    $mainPluginVersion = $mainPlugin-&gt;getPluginVersion();\n    if (\n      $mainPluginVersionConstraint !== null && !Semver::satisfies(\n        $mainPluginVersion,\n        $mainPluginVersionConstraint\n      )\n    ) {\n      $this-&gt;printAdminNoticeErrorMessage(\n        sprintf(\n          __('Extension or bundle &lt;strong&gt;%s&lt;\/strong&gt; requires plugin &lt;strong&gt;%s&lt;\/strong&gt; to satisfy version constraint &lt;code&gt;%s&lt;\/code&gt;, but the current version &lt;code&gt;%s&lt;\/code&gt; does not. The extension or bundle has not been loaded.', 'gatographql'),\n          $extensionName ?? $extensionClass,\n          $mainPlugin-&gt;getPluginName(),\n          $mainPluginVersionConstraint,\n          $mainPlugin-&gt;getPluginVersion(),\n        )\n      );\n      return false;\n    }\n\n    return true;\n  }\n\n  protected function printAdminNoticeErrorMessage(string $errorMessage): void\n  {\n    \\add_action('admin_notices', function () use ($errorMessage): void {\n      $adminNotice_safe = sprintf(\n        '&lt;div class=\"notice notice-error\"&gt;&lt;p&gt;%s&lt;\/p&gt;&lt;\/div&gt;',\n        $errorMessage\n      );\n      echo $adminNotice_safe;\n    });\n  }\n}\n<\/code><\/pre>\n<h2>Integrationstests gegen einen WordPress-Server durchf\u00fchren<\/h2>\n<p>Um es Entwicklern von Drittanbietern zu erleichtern, Erweiterungen f\u00fcr deine Plugins zu erstellen, solltest du ihnen Werkzeuge f\u00fcr die Entwicklung und das Testen zur Verf\u00fcgung stellen, einschlie\u00dflich Workflows f\u00fcr ihre <a href=\"https:\/\/kinsta.com\/de\/blog\/automatisierte-tests\/\">kontinuierlichen Integrations- und Lieferprozesse<\/a> (CI\/CD).<\/p>\n<p>W\u00e4hrend der Entwicklung kann jeder mit <a href=\"https:\/\/kinsta.com\/de\/devkinsta\/\">DevKinsta<\/a> ganz einfach einen Webserver aufsetzen, das Plugin installieren, f\u00fcr das er die Erweiterung programmiert, und sofort \u00fcberpr\u00fcfen, ob die Erweiterung mit dem Plugin kompatibel ist.<\/p>\n<p>Um die Tests w\u00e4hrend des CI\/CD zu automatisieren, muss der Webserver \u00fcber ein Netzwerk f\u00fcr den CI\/CD-Dienst zug\u00e4nglich sein. Dienste wie InstaWP k\u00f6nnen zu diesem Zweck eine Sandbox-Website mit installiertem WordPress erstellen.<\/p>\n<p>Wenn die Codebasis der Erweiterung auf <a href=\"https:\/\/kinsta.com\/de\/blog\/was-ist-github\/\">GitHub<\/a> gehostet wird, k\u00f6nnen Entwickler <a href=\"https:\/\/kinsta.com\/de\/blog\/ci-cd-pipeline-github-aktionen-erstellen\/#what-is-github-actions\">GitHub Actions<\/a> nutzen, um Integrationstests gegen den InstaWP-Dienst durchzuf\u00fchren. Der folgende Arbeitsablauf installiert die <a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/aa574c785a139d8ffc094b3b3703eb516156f77e\/.github\/workflows\/generate_plugins.yml\" target=\"_blank\" rel=\"noopener noreferrer\">Erweiterung<\/a> auf einer InstaWP-Sandbox-Website (zusammen mit der neuesten stabilen Version des Haupt-Plugins) und <a href=\"https:\/\/github.com\/GatoGraphQL\/ExtensionStarter\/blob\/a2d5d37ed205264f5ee03ce3ad075a08e89b67a7\/.github\/workflows\/integration_tests.yml\">f\u00fchrt dann die Integrationstests aus<\/a>:<\/p>\n<pre><code class=\"language-yaml\">name: Integration tests (InstaWP)\non:\n  workflow_run:\n    workflows: [Generate plugins]\n    types:\n      - completed\n\njobs:\n  provide_data:\n    if: ${{ github.event.workflow_run.conclusion == 'success' }}\n    name: Retrieve the GitHub Action artifact URLs to install in InstaWP\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n\n      - uses: shivammathur\/setup-php@v2\n        with:\n          php-version: 8.1\n          coverage: none\n        env:\n          COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - uses: \"ramsey\/composer-install@v2\"\n\n      - name: Retrieve artifact URLs from GitHub workflow\n        uses: actions\/github-script@v6\n        id: artifact-url\n        with:\n          script: |\n            const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              run_id: context.payload.workflow_run.id,\n            });\n            const artifactURLs = allArtifacts.data.artifacts.map((artifact) =&gt; {\n              return artifact.url.replace('https:\/\/api.github.com\/repos', 'https:\/\/nightly.link') + '.zip'\n            }).concat([\n              \"https:\/\/downloads.wordpress.org\/plugin\/gatographql.latest-stable.zip\"\n            ]);\n            return artifactURLs.join(',');\n          result-encoding: string\n\n      - name: Artifact URL for InstaWP\n        run: echo \"Artifact URL for InstaWP - ${{ steps.artifact-url.outputs.result }}\"\n        shell: bash\n\n    outputs:\n      artifact_url: ${{ steps.artifact-url.outputs.result }}\n\n  process:\n    needs: provide_data\n    name: Launch InstaWP site from template 'integration-tests' and execute integration tests against it\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n\n      - uses: shivammathur\/setup-php@v2\n        with:\n          php-version: 8.1\n          coverage: none\n        env:\n          COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - uses: \"ramsey\/composer-install@v2\"\n\n      - name: Create InstaWP instance\n        uses: instawp\/wordpress-testing-automation@main\n        id: create-instawp\n        with:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}\n          INSTAWP_TEMPLATE_SLUG: \"integration-tests\"\n          REPO_ID: 25\n          INSTAWP_ACTION: create-site-template\n          ARTIFACT_URL: ${{ needs.provide_data.outputs.artifact_url }}\n\n      - name: InstaWP instance URL\n        run: echo \"InstaWP instance URL - ${{ steps.create-instawp.outputs.instawp_url }}\"\n        shell: bash\n\n      - name: Extract InstaWP domain\n        id: extract-instawp-domain        \n        run: |\n          instawp_domain=\"$(echo \"${{ steps.create-instawp.outputs.instawp_url }}\" | sed -e s#https:\/\/##)\"\n          echo \"instawp-domain=$(echo $instawp_domain)\" &gt;&gt; $GITHUB_OUTPUT\n\n      - name: Run tests\n        run: |\n          INTEGRATION_TESTS_WEBSERVER_DOMAIN=${{ steps.extract-instawp-domain.outputs.instawp-domain }} \\\n          INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_USERNAME=${{ steps.create-instawp.outputs.iwp_wp_username }} \\\n          INTEGRATION_TESTS_AUTHENTICATED_ADMIN_USER_PASSWORD=${{ steps.create-instawp.outputs.iwp_wp_password }} \\\n          vendor\/bin\/phpunit --filter=Integration\n\n      - name: Destroy InstaWP instance\n        uses: instawp\/wordpress-testing-automation@main\n        id: destroy-instawp\n        if: ${{ always() }}\n        with:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          INSTAWP_TOKEN: ${{ secrets.INSTAWP_TOKEN }}\n          INSTAWP_TEMPLATE_SLUG: \"integration-tests\"\n          REPO_ID: 25\n          INSTAWP_ACTION: destroy-site\n<\/code><\/pre>\n<p>Dieser Workflow greift auf die <strong>.zip-Datei<\/strong> \u00fcber <a href=\"https:\/\/nightly.link\" target=\"_blank\" rel=\"noopener noreferrer\">Nightly Link<\/a> zu, einen Dienst, der es erm\u00f6glicht, auf ein Artefakt von GitHub zuzugreifen, ohne sich anzumelden, was die Konfiguration von InstaWP vereinfacht.<\/p>\n<h2>Freigabe des Erweiterungs-Plugins<\/h2>\n<p>Wir k\u00f6nnen Werkzeuge bereitstellen, die bei der Ver\u00f6ffentlichung der Erweiterungen helfen und die Abl\u00e4ufe so weit wie m\u00f6glich automatisieren.<\/p>\n<p>Der <a href=\"https:\/\/github.com\/symplify\/monorepo-builder\" target=\"_blank\" rel=\"noopener noreferrer\">Monorepo Builder<\/a> ist eine Bibliothek zur Verwaltung beliebiger PHP-Projekte, einschlie\u00dflich eines WordPress-Plugins. Er bietet den Befehl <code>monorepo-builder release<\/code>, um eine Version des Projekts freizugeben, wobei entweder die Major-, Minor- oder Patch-Komponente der Version gem\u00e4\u00df der <a href=\"https:\/\/semver.org\" target=\"_blank\" rel=\"noopener noreferrer\">semantischen Versionierung<\/a> erh\u00f6ht wird.<\/p>\n<p>Dieser Befehl f\u00fchrt eine Reihe von <a href=\"https:\/\/github.com\/symplify\/monorepo-builder?tab=readme-ov-file#7-release-flow\" target=\"_blank\" rel=\"noopener noreferrer\">Release Workern<\/a> aus, d. h. PHP-Klassen, die eine bestimmte Logik ausf\u00fchren. Zu den Standardworkern geh\u00f6rt einer, der eine <code>git tag<\/code> mit der neuen Version erstellt, und ein anderer, der das Tag in das entfernte Repository \u00fcbertr\u00e4gt. Benutzerdefinierte Worker k\u00f6nnen vor, nach oder zwischen diesen Schritten eingef\u00fcgt werden.<\/p>\n<p>Die Release Worker werden \u00fcber eine Konfigurationsdatei konfiguriert:<\/p>\n<pre><code class=\"language-php\">use Symplify\\MonorepoBuilder\\Config\\MBConfig;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\AddTagToChangelogReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\PushNextDevReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\PushTagReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\SetCurrentMutualDependenciesReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\SetNextMutualDependenciesReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\TagVersionReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\UpdateBranchAliasReleaseWorker;\nuse Symplify\\MonorepoBuilder\\Release\\ReleaseWorker\\UpdateReplaceReleaseWorker;\n\nreturn static function (MBConfig $mbConfig): void {\n  \/\/ release workers - in order to execute\n  $mbConfig-&gt;workers([\n    UpdateReplaceReleaseWorker::class,\n    SetCurrentMutualDependenciesReleaseWorker::class,\n    AddTagToChangelogReleaseWorker::class,\n    TagVersionReleaseWorker::class,\n    PushTagReleaseWorker::class,\n    SetNextMutualDependenciesReleaseWorker::class,\n    UpdateBranchAliasReleaseWorker::class,\n    PushNextDevReleaseWorker::class,\n  ]);\n};\n<\/code><\/pre>\n<p>Wir k\u00f6nnen benutzerdefinierte Release Worker bereitstellen, um den Release-Prozess auf die Bed\u00fcrfnisse eines WordPress-Plugins zuzuschneiden. Zum Beispiel kann der <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/af21729cd7e88439156ca1b9cf0231a06354b18f\/src\/OnDemand\/Symplify\/MonorepoBuilder\/Release\/ReleaseWorker\/ConvertStableTagVersionForProdInPluginReadmeFileReleaseWorker.php\" target=\"_blank\" rel=\"noopener noreferrer\"><code>InjectStableTagVersionInPluginReadmeFileReleaseWorker<\/code><\/a> setzt die neue Version als &#8222;Stable tag&#8220;-Eintrag in der <a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/how-your-readme-txt-works\/\" target=\"_blank\" rel=\"noopener noreferrer\"> <strong>readme.txt-Datei<\/strong> der Erweiterung<\/a>:<\/p>\n<pre><code class=\"language-php\">use Nette\\Utils\\Strings;\nuse PharIo\\Version\\Version;\nuse Symplify\\SmartFileSystem\\SmartFileInfo;\nuse Symplify\\SmartFileSystem\\SmartFileSystem;\n\nclass InjectStableTagVersionInPluginReadmeFileReleaseWorker implements ReleaseWorkerInterface\n{\n  public function __construct(\n    \/\/ This class is provided by the Monorepo Builder\n    private SmartFileSystem $smartFileSystem,\n  ) {\n  }\n\n  public function getDescription(Version $version): string\n  {\n    return 'Have the \"Stable tag\" point to the new version in the plugin\\'s readme.txt file';\n  }\n\n  public function work(Version $version): void\n  {\n    $replacements = [\n      '\/Stable tag:\\s+[a-z0-9.-]+\/' =&gt; 'Stable tag: ' . $version-&gt;getVersionString(),\n    ];\n    $this-&gt;replaceContentInFiles(['\/readme.txt'], $replacements);\n  }\n\n  \/**\n   * @param string[] $files\n   * @param array&lt;string,string&gt; $regexPatternReplacements regex pattern to search, and its replacement\n   *\/\n  protected function replaceContentInFiles(array $files, array $regexPatternReplacements): void\n  {\n    foreach ($files as $file) {\n      $fileContent = $this-&gt;smartFileSystem-&gt;readFile($file);\n      foreach ($regexPatternReplacements as $regexPattern =&gt; $replacement) {\n        $fileContent = Strings::replace($fileContent, $regexPattern, $replacement);\n      }\n      $this-&gt;smartFileSystem-&gt;dumpFile($file, $fileContent);\n    }\n  }\n}\n<\/code><\/pre>\n<p>Wenn du <code>InjectStableTagVersionInPluginReadmeFileReleaseWorker<\/code> zur Konfigurationsliste hinzuf\u00fcgst, wird bei der Ausf\u00fchrung des Befehls <code>monorepo-builder release<\/code> zur Ver\u00f6ffentlichung einer neuen Version des Plugins der &#8222;Stable-Tag&#8220; in der <strong>readme.txt-Datei<\/strong> der Erweiterung automatisch aktualisiert.<\/p>\n<h2>Ver\u00f6ffentlichung des Erweiterungs-Plugins im WP.org-Verzeichnis<\/h2>\n<p>Wir k\u00f6nnen auch einen Arbeitsablauf bereitstellen, der die Ver\u00f6ffentlichung der Erweiterung im <a href=\"https:\/\/wordpress.org\/plugins\/\" target=\"_blank\" rel=\"noopener noreferrer\">WordPress Plugin-Verzeichnis<\/a> unterst\u00fctzt. Wenn du das Projekt auf dem remoten Repository taggst, ver\u00f6ffentlicht der <a href=\"https:\/\/github.com\/GatoGraphQL\/GatoGraphQL\/blob\/f8579139f1af05aae171e776c6f53b0c458e339a\/layers\/GatoGraphQLForWP\/plugins\/gatographql\/.github\/workflows\/deploy_wordpress_plugin_to_svn.yml\" target=\"_blank\" rel=\"noopener noreferrer\">folgende Workflow<\/a> das WordPress-Erweiterungs-Plugin im Verzeichnis:<\/p>\n<pre><code class=\"language-yml\"># See: https:\/\/github.com\/10up\/action-wordpress-plugin-deploy#deploy-on-pushing-a-new-tag\nname: Deploy to WordPress.org Plugin Directory (SVN)\non:\n  push:\n  tags:\n  - \"*\"\n\njobs:\n  tag:\n  name: New tag\n  runs-on: ubuntu-latest\n  steps:\n  - uses: actions\/checkout@master\n  - name: WordPress Plugin Deploy\n    uses: 10up\/action-wordpress-plugin-deploy@stable\n    env:\n    SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}\n    SVN_USERNAME: ${{ secrets.SVN_USERNAME }}\n    SLUG: ${{ secrets.SLUG }}\n<\/code><\/pre>\n<p>Dieser Arbeitsablauf verwendet die <a href=\"https:\/\/github.com\/10up\/action-wordpress-plugin-deploy\" target=\"_blank\" rel=\"noopener noreferrer\"><code>10up\/action-wordpress-plugin-deploy<\/code><\/a> Aktion, die den Code aus einem Git-Repository abruft und ihn in das <a href=\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-verzeichnis-veroeffentlicht\/#plugin-structure\">SVN-Repository von WordPress.org<\/a> pusht, was den Vorgang vereinfacht.<\/p>\n<h2>Zusammenfassung<\/h2>\n<p>Wenn wir ein erweiterbares Plugin f\u00fcr WordPress entwickeln, wollen wir es Entwicklern von Drittanbietern so einfach wie m\u00f6glich machen, das Plugin zu erweitern, um so die Chancen auf ein lebendiges \u00d6kosystem rund um unsere Plugins zu erh\u00f6hen.<\/p>\n<p>Eine ausf\u00fchrliche Dokumentation kann Entwicklern zeigen, wie sie das Plugin erweitern k\u00f6nnen. Noch effektiver ist es jedoch, den notwendigen PHP-Code und die Werkzeuge f\u00fcr die Entwicklung, das Testen und die Ver\u00f6ffentlichung ihrer Erweiterungen bereitzustellen.<\/p>\n<p>Indem wir den zus\u00e4tzlichen Code, den die Erweiterungen ben\u00f6tigen, direkt in unser Plugin integrieren, vereinfachen wir den Prozess f\u00fcr Entwickler.<\/p>\n<p><em>Hast du vor, dein WordPress-Plugin erweiterbar zu machen? Lass es uns in den Kommentaren wissen.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>WordPress-Plugins k\u00f6nnen mit zus\u00e4tzlichen Funktionen erweitert werden, wie beliebte Plugins wie WooCommerce und Gravity Forms zeigen. In dem Artikel Ein WordPress-Plugin so gestalten, dass es Erweiterungen &#8230;<\/p>\n","protected":false},"author":196,"featured_media":70348,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kinsta_gated_content":false,"_kinsta_gated_content_redirect":"","footnotes":""},"tags":[],"topic":[999,1006],"class_list":["post-70347","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","topic-wordpress-entwicklung","topic-wordpress-plugins"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v24.6 (Yoast SEO v24.6) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>WordPress-Plugins mit PHP-Klassen erweitern - Kinsta\u00ae<\/title>\n<meta name=\"description\" content=\"Hier erf\u00e4hrst du, wie du ein erweiterbares WordPress-Plugin erstellst, das den Entwicklungsprozess f\u00fcr Drittentwickler vereinfacht.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"WordPress-Plugins mit PHP-Klassen erweitern\" \/>\n<meta property=\"og:description\" content=\"Hier erf\u00e4hrst du, wie du ein erweiterbares WordPress-Plugin erstellst, das den Entwicklungsprozess f\u00fcr Drittentwickler vereinfacht.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/\" \/>\n<meta property=\"og:site_name\" content=\"Kinsta\u00ae\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Kinsta-Deutschland-207459890108303\/\" \/>\n<meta property=\"article:published_time\" content=\"2024-06-19T09:53:06+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-06-28T07:47:46+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1460\" \/>\n\t<meta property=\"og:image:height\" content=\"730\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Leonardo Losoviz\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:description\" content=\"Hier erf\u00e4hrst du, wie du ein erweiterbares WordPress-Plugin erstellst, das den Entwicklungsprozess f\u00fcr Drittentwickler vereinfacht.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions-1024x512.jpg\" \/>\n<meta name=\"twitter:creator\" content=\"@losoviz\" \/>\n<meta name=\"twitter:site\" content=\"@Kinsta_DE\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Leonardo Losoviz\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"16\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/\"},\"author\":{\"name\":\"Leonardo Losoviz\",\"@id\":\"https:\/\/kinsta.com\/de\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\"},\"headline\":\"WordPress-Plugins mit PHP-Klassen erweitern\",\"datePublished\":\"2024-06-19T09:53:06+00:00\",\"dateModified\":\"2024-06-28T07:47:46+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/\"},\"wordCount\":1473,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/de\/#organization\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/\",\"url\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/\",\"name\":\"WordPress-Plugins mit PHP-Klassen erweitern - Kinsta\u00ae\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/de\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"datePublished\":\"2024-06-19T09:53:06+00:00\",\"dateModified\":\"2024-06-28T07:47:46+00:00\",\"description\":\"Hier erf\u00e4hrst du, wie du ein erweiterbares WordPress-Plugin erstellst, das den Entwicklungsprozess f\u00fcr Drittentwickler vereinfacht.\",\"breadcrumb\":{\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#primaryimage\",\"url\":\"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"contentUrl\":\"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg\",\"width\":1460,\"height\":730},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/kinsta.com\/de\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"WordPress-Plugins\",\"item\":\"https:\/\/kinsta.com\/de\/thema\/wordpress-plugins\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"WordPress-Plugins mit PHP-Klassen erweitern\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/kinsta.com\/de\/#website\",\"url\":\"https:\/\/kinsta.com\/de\/\",\"name\":\"Kinsta\u00ae\",\"description\":\"Schnelle, sichere und hochwertige Hosting-L\u00f6sungen\",\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/de\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/kinsta.com\/de\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/kinsta.com\/de\/#organization\",\"name\":\"Kinsta\",\"url\":\"https:\/\/kinsta.com\/de\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/kinsta.com\/de\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/12\/kinsta-logo.jpeg\",\"contentUrl\":\"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/12\/kinsta-logo.jpeg\",\"width\":500,\"height\":500,\"caption\":\"Kinsta\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/de\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/Kinsta-Deutschland-207459890108303\/\",\"https:\/\/x.com\/Kinsta_DE\",\"https:\/\/www.instagram.com\/kinstahosting\/\",\"https:\/\/www.linkedin.com\/company\/kinsta\/\",\"https:\/\/www.pinterest.com\/kinstahosting\/\",\"https:\/\/www.youtube.com\/c\/Kinsta\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/kinsta.com\/de\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238\",\"name\":\"Leonardo Losoviz\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\/\/kinsta.com\/de\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/b28085726ee66e49f08be16ad668efd5?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/b28085726ee66e49f08be16ad668efd5?s=96&d=mm&r=g\",\"caption\":\"Leonardo Losoviz\"},\"description\":\"Leo writes about innovative web development trends, mostly concerning PHP, WordPress and GraphQL. You can find him at leoloso.com and twitter.com\/losoviz.\",\"sameAs\":[\"https:\/\/leoloso.com\",\"https:\/\/x.com\/losoviz\",\"https:\/\/www.youtube.com\/@GatoGraphQL\"],\"url\":\"https:\/\/kinsta.com\/de\/blog\/author\/leonardolosoviz\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"WordPress-Plugins mit PHP-Klassen erweitern - Kinsta\u00ae","description":"Hier erf\u00e4hrst du, wie du ein erweiterbares WordPress-Plugin erstellst, das den Entwicklungsprozess f\u00fcr Drittentwickler vereinfacht.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/","og_locale":"de_DE","og_type":"article","og_title":"WordPress-Plugins mit PHP-Klassen erweitern","og_description":"Hier erf\u00e4hrst du, wie du ein erweiterbares WordPress-Plugin erstellst, das den Entwicklungsprozess f\u00fcr Drittentwickler vereinfacht.","og_url":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/","og_site_name":"Kinsta\u00ae","article_publisher":"https:\/\/www.facebook.com\/Kinsta-Deutschland-207459890108303\/","article_published_time":"2024-06-19T09:53:06+00:00","article_modified_time":"2024-06-28T07:47:46+00:00","og_image":[{"width":1460,"height":730,"url":"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","type":"image\/jpeg"}],"author":"Leonardo Losoviz","twitter_card":"summary_large_image","twitter_description":"Hier erf\u00e4hrst du, wie du ein erweiterbares WordPress-Plugin erstellst, das den Entwicklungsprozess f\u00fcr Drittentwickler vereinfacht.","twitter_image":"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions-1024x512.jpg","twitter_creator":"@losoviz","twitter_site":"@Kinsta_DE","twitter_misc":{"Verfasst von":"Leonardo Losoviz","Gesch\u00e4tzte Lesezeit":"16\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#article","isPartOf":{"@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/"},"author":{"name":"Leonardo Losoviz","@id":"https:\/\/kinsta.com\/de\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238"},"headline":"WordPress-Plugins mit PHP-Klassen erweitern","datePublished":"2024-06-19T09:53:06+00:00","dateModified":"2024-06-28T07:47:46+00:00","mainEntityOfPage":{"@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/"},"wordCount":1473,"commentCount":0,"publisher":{"@id":"https:\/\/kinsta.com\/de\/#organization"},"image":{"@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/","url":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/","name":"WordPress-Plugins mit PHP-Klassen erweitern - Kinsta\u00ae","isPartOf":{"@id":"https:\/\/kinsta.com\/de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#primaryimage"},"image":{"@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","datePublished":"2024-06-19T09:53:06+00:00","dateModified":"2024-06-28T07:47:46+00:00","description":"Hier erf\u00e4hrst du, wie du ein erweiterbares WordPress-Plugin erstellst, das den Entwicklungsprozess f\u00fcr Drittentwickler vereinfacht.","breadcrumb":{"@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#primaryimage","url":"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","contentUrl":"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2024\/06\/wp-creating-a-starter-project-on-github-for-wordpress-plugin-extensions.jpg","width":1460,"height":730},{"@type":"BreadcrumbList","@id":"https:\/\/kinsta.com\/de\/blog\/wordpress-plugin-erweitern\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/kinsta.com\/de\/"},{"@type":"ListItem","position":2,"name":"WordPress-Plugins","item":"https:\/\/kinsta.com\/de\/thema\/wordpress-plugins\/"},{"@type":"ListItem","position":3,"name":"WordPress-Plugins mit PHP-Klassen erweitern"}]},{"@type":"WebSite","@id":"https:\/\/kinsta.com\/de\/#website","url":"https:\/\/kinsta.com\/de\/","name":"Kinsta\u00ae","description":"Schnelle, sichere und hochwertige Hosting-L\u00f6sungen","publisher":{"@id":"https:\/\/kinsta.com\/de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/kinsta.com\/de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/kinsta.com\/de\/#organization","name":"Kinsta","url":"https:\/\/kinsta.com\/de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/kinsta.com\/de\/#\/schema\/logo\/image\/","url":"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/12\/kinsta-logo.jpeg","contentUrl":"https:\/\/kinsta.com\/de\/wp-content\/uploads\/sites\/5\/2023\/12\/kinsta-logo.jpeg","width":500,"height":500,"caption":"Kinsta"},"image":{"@id":"https:\/\/kinsta.com\/de\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/Kinsta-Deutschland-207459890108303\/","https:\/\/x.com\/Kinsta_DE","https:\/\/www.instagram.com\/kinstahosting\/","https:\/\/www.linkedin.com\/company\/kinsta\/","https:\/\/www.pinterest.com\/kinstahosting\/","https:\/\/www.youtube.com\/c\/Kinsta"]},{"@type":"Person","@id":"https:\/\/kinsta.com\/de\/#\/schema\/person\/c382de1885cc21b079ec1e71d7faf238","name":"Leonardo Losoviz","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/kinsta.com\/de\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/b28085726ee66e49f08be16ad668efd5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b28085726ee66e49f08be16ad668efd5?s=96&d=mm&r=g","caption":"Leonardo Losoviz"},"description":"Leo writes about innovative web development trends, mostly concerning PHP, WordPress and GraphQL. You can find him at leoloso.com and twitter.com\/losoviz.","sameAs":["https:\/\/leoloso.com","https:\/\/x.com\/losoviz","https:\/\/www.youtube.com\/@GatoGraphQL"],"url":"https:\/\/kinsta.com\/de\/blog\/author\/leonardolosoviz\/"}]}},"acf":[],"_links":{"self":[{"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/posts\/70347","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/users\/196"}],"replies":[{"embeddable":true,"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/comments?post=70347"}],"version-history":[{"count":5,"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/posts\/70347\/revisions"}],"predecessor-version":[{"id":70477,"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/posts\/70347\/revisions\/70477"}],"alternate":[{"embeddable":true,"hreflang":"en","title":"English","href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/translations\/en"},{"embeddable":true,"hreflang":"it","title":"Italian","href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/translations\/it"},{"embeddable":true,"hreflang":"pt","title":"Portuguese","href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/translations\/pt"},{"embeddable":true,"hreflang":"fr","title":"French","href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/translations\/fr"},{"embeddable":true,"hreflang":"de","title":"German","href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/translations\/de"},{"embeddable":true,"hreflang":"ja","title":"Japanese","href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/translations\/jp"},{"embeddable":true,"hreflang":"nl","title":"Dutch","href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/translations\/nl"},{"embeddable":true,"hreflang":"es","title":"Spanish","href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/translations\/es"},{"href":"https:\/\/kinsta.com\/de\/wp-json\/kinsta\/v1\/posts\/70347\/tree"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/media\/70348"}],"wp:attachment":[{"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/media?parent=70347"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/tags?post=70347"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/kinsta.com\/de\/wp-json\/wp\/v2\/topic?post=70347"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}