Instantiate all classes within a namespace in Symfony and Drupal

03 Jun 2021

Occasionally I find myself needing plugin-like functionality, where users/downstream can throw a class into a folder and expect it to work. My script is supposed to find and instantiate these plugins during runtime without keeping track of their existence.

In a regular Drupal module, one would usually use the plugin architecture, but that comes with its overhead of boilerplate code and may not be the solution for the simplest of use cases.

Many class finder libraries rely on get_declared_classes() which may not be helpful, as the classes in question may not have been declared yet.

If you are on a Drupal 8/9 installation and want to use components already available to you, the Symfony (file) Finder can be an alternative for finding classes in a given namespace.

Installing dependencies

Ouside of Drupal 8/9, you may need to require this library in your application:

  1. composer require symfony/finder

A simple example

  1. use Symfony\Component\Finder\Finder;
  2.  
  3. class PluginLoader {
  4.  
  5. /**
  6.   * Loads all plugins.
  7.   *
  8.   * @param string $namespace
  9.   * Namespace required for a class to be considered a plugin.
  10.   * @param string $search_root_path
  11.   * Search classes recursively starting from this folder.
  12.   * The default is the folder this here class resides in.
  13.   *
  14.   * @return object[]
  15.   * Array of instantiated plugins
  16.   */
  17. public static function loadPlugins(string $namespace, string $search_root_path = __DIR__): array {
  18. $finder = new Finder();
  19. $finder->files()->in($search_root_path)->name('*.php');
  20. foreach ($finder as $file) {
  21. $class_name = rtrim($namespace, '\\') . '\\' . $file->getFilenameWithoutExtension();
  22. if (class_exists($class_name)) {
  23. try {
  24. $plugins[] = new $class_name();
  25. }
  26. catch (\Throwable $e) {
  27. continue;
  28. }
  29. }
  30. }
  31.  
  32. return $plugins ?? [];
  33. }
  34.  
  35. }

Usage

  1. $plugin_instances = PluginLoader::loadPlugins('\Some\Namespace');

This is just an abstract catch-all example with a couple of obvious problems which can be circumvented when using more specific code.

In the above example, the finder looks for all files with the .php extension within all folders in a given path. If it finds a class, it tries to instantiate it. The try-catch block is for it to not fail when trying to instantiate non-instantiatable classes, interfaces and similar.

The above can be improved upon by making assumptions about the class name (one could be looking for class files named *Plugin.php) and examining the file content (which the Finder component is capable of as well).

Let me know of other simple ways of tackling this problem!

Add new comment

The content of this field is kept private and will not be shown publicly.

Restricted HTML

  • Allowed HTML tags: <a href hreflang target> <em> <strong> <cite> <blockquote cite> <pre> <ul type> <ol start type> <li> <dl> <dt> <dd> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.

Get a quote in 24 hours

Wether a huge commerce system, or a small business website, we will quote the project within 24h of you pressing the following button: Get quote