It is recommended you have at least some basic understanding of OOP concepts prior to attempting the certification exam. There are many tutorials available to learn those. This documentation will only cover some of the essential Drupal-related topics that fall under the scope of the D8 certification exam. See Getting Started - Background & Prerequisites (Drupal 8) for a list of resources for learning OOP.
All classes, interfaces and traits should live in their own files, where the name of the file matches the name of the class. Additionally the files should be located at src/{objecttype}/{classname}.php
For example:
<?php
class HelloController extends ControllerBase {
// ...
}
This class should be located at src/Controller/HelloController.php
inside your module.
The Factory Pattern is a common OOP design pattern that creates an object of the class you want to use. By using factories instead of directly instantiating objects, you can reduce complexity around the actual creation of objects, especially when multiple steps are required.
Consider this example from PHP The Right Way - Design Patterns:
<?php
class Automobile
{
private $vehicleMake;
private $vehicleModel;
public function __construct($make, $model)
{
$this->vehicleMake = $make;
$this->vehicleModel = $model;
}
public function getMakeAndModel()
{
return $this->vehicleMake . ' ' . $this->vehicleModel;
}
}
<?php
class AutomobileFactory
{
public static function create($make, $model)
{
return new Automobile($make, $model);
}
}
<?php
// have the factory create the Automobile object
$veyron = AutomobileFactory::create('Bugatti', 'Veyron');
print_r($veyron->getMakeAndModel()); // outputs "Bugatti Veyron"
The AutomobileFactory
class will create a new automobile object for you as needed.
Drupal makes use of factories in several places. One such example is \Drupal::configFactory()
:
<?php
public static function configFactory() {
return static::getContainer()->get('config.factory');
}
Then you can just use \Drupal::configFactory()
to retrieve a new config object.
For example, have a look at system_update_8200()
:
<?php
function system_update_8200(&$sandbox) {
$config_factory = \Drupal::configFactory();
if (!array_key_exists('config_names', $sandbox)) {
$sandbox['config_names'] = $config_factory->listAll();
$sandbox['max'] = count($sandbox['config_names']);
}
// Get a list of 50 to work on at a time.
$config_names_to_process = array_slice($sandbox['config_names'], 0, 50);
// Preload in a single query.
$config_factory->loadMultiple($config_names_to_process);
foreach ($config_names_to_process as $config_name) {
$config_factory->getEditable($config_name)->save();
}
// Update the list of names to process.
$sandbox['config_names'] = array_diff($sandbox['config_names'], $config_names_to_process);
$sandbox['#finished'] = empty($sandbox['config_names']) ? 1 : ($sandbox['max'] - count($sandbox['config_names'])) / $sandbox['max'];
}
If you'd like to do more research into specific instances, as of Drupal 8.3.1, core provides the following factories:
core/lib/Drupal/Component/FileCache/FileCacheFactory.php
core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
core/lib/Drupal/Component/Plugin/Factory/ReflectionFactory.php
core/lib/Drupal/Core/Access/AccessArgumentsResolverFactory.php
core/lib/Drupal/Core/AppRootFactory.php
core/lib/Drupal/Core/Cache/ApcuBackendFactory.php
core/lib/Drupal/Core/Cache/CacheFactory.php
core/lib/Drupal/Core/Cache/ChainedFastBackendFactory.php
core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php
core/lib/Drupal/Core/Cache/MemoryBackendFactory.php
core/lib/Drupal/Core/Cache/NullBackendFactory.php
core/lib/Drupal/Core/Cache/PhpBackendFactory.php
core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php
core/lib/Drupal/Core/Config/ConfigFactory.php
core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
core/lib/Drupal/Core/Config/FileStorageFactory.php
core/lib/Drupal/Core/Entity/KeyValueStore/Query/QueryFactory.php
core/lib/Drupal/Core/Entity/Query/Null/QueryFactory.php
core/lib/Drupal/Core/Entity/Query/QueryFactory.php
core/lib/Drupal/Core/Entity/Query/Sql/pgsql/QueryFactory.php
core/lib/Drupal/Core/Entity/Query/Sql/QueryFactory.php
core/lib/Drupal/Core/Http/ClientFactory.php
core/lib/Drupal/Core/Http/TrustedHostsRequestFactory.php
core/lib/Drupal/Core/Image/ImageFactory.php
core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseExpirableFactory.php
core/lib/Drupal/Core/KeyValueStore/KeyValueDatabaseFactory.php
core/lib/Drupal/Core/KeyValueStore/KeyValueExpirableFactory.php
core/lib/Drupal/Core/KeyValueStore/KeyValueFactory.php
core/lib/Drupal/Core/KeyValueStore/KeyValueMemoryFactory.php
core/lib/Drupal/Core/KeyValueStore/KeyValueNullExpirableFactory.php
core/lib/Drupal/Core/Logger/LoggerChannelFactory.php
core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
core/lib/Drupal/Core/Plugin/Factory/ContainerFactory.php
core/lib/Drupal/Core/Plugin/PluginFormFactory.php
core/lib/Drupal/Core/Queue/QueueDatabaseFactory.php
core/lib/Drupal/Core/Queue/QueueFactory.php
core/lib/Drupal/Core/SitePathFactory.php
core/lib/Drupal/Core/TypedData/Validation/ExecutionContextFactory.php
core/lib/Drupal/Core/Update/UpdateRegistryFactory.php
core/lib/Drupal/Core/Validation/ConstraintValidatorFactory.php
core/modules/update/src/UpdateRootFactory.php
core/modules/user/src/PrivateTempStoreFactory.php
core/modules/user/src/SharedTempStoreFactory.php
core/modules/views/src/ViewExecutableFactory.php
Drupal projects should be properly namespaced to prevent potential overlap with other modules. This is done through PHP namespacing. The recommended namespace for any given module is namespace Drupal\{modulename}
.
For example, the namespace for the block
module is:
namespace Drupal\block;
For example, if two modules had a poorly named ModuleController.php
that contained a ModuleController
class with a description()
method, by using namespaces we can distinguish between those two methods:
\Drupal\module1\Controller\ModuleController::description()
\Drupal\module2\Controller\ModuleController::description()
As Drupal coding standards require you to include use statements, you can use class aliasing to distinguish between these two classes:
use \Drupal\module1\Controller\ModuleController as Module1ModuleController;
use \Drupal\module2\Controller\ModuleController as Module2ModuleController;
Module1ModuleController::description();
Module2ModuleController::description();
From PSR-4 namespaces and autoloading in Drupal 8:
Component | Base namespace | Base directory | Contains |
---|---|---|---|
Drupal core | Drupal\Component\ |
core/lib/Drupal/Component/ |
Components that are reusable outside of Drupal. |
-- | Drupal\Core\ |
core/lib/Drupal/Core/ |
Components that are specific to Drupal. |
-- | Drupal\Tests\ |
core/tests/Drupal/Tests/ |
PHPUnit tests of core components. |
Modules | Drupal\$modulename\ |
modules/$modulename/src/ |
Main integration files. |
-- | Drupal\$modulename\Tests\ |
modules/$modulename/src/Tests/ |
Simpletest tests of the module. |
-- | Drupal\Tests\$modulename\ |
modules/$modulename/tests/src/ |
PHPUnit tests of the module. |
Symfony2, the underlying PHP framework Drupal 8 is built upon, is not officially a MVC (Model/View/Controller) framework, but does make heavy use of views and controllers. Drupal 8 is an extension of that. Views are handled almost exclusively through Twig and controllers are managed via the Routing System.
By keeping the controller and views separate, it allows for a better separation of concerns. This helps keep a system flexible moving forward.
- drupal.org - Getting Started - Background & Prerequisites (Drupal 8)
- drupal.org - PSR-4 namespaces and autoloading in Drupal 8
- phptherightway.com - Design Patterns: