Commit b4138560 authored by Rodrigo Aguilera's avatar Rodrigo Aguilera
Browse files

Initial module.

parent 53fb289e
Pipeline #183579840 passed with stages
in 4 minutes and 29 seconds
#PHPUnit output
# Ignore local overrides.
- project: 'drupalspoons/webmasters'
# Best practice is to pin to a tag or a SHA1.
ref: 1.7.0-rc1
# The template below may be inspected at
file: 'templates/.gitlab-ci.yml'
# Projects may override anything in the template above.
# The code below is specific to devel project. You dont need to copy it to your new project.
# Run tests on Drupal 9.0 by default. Avoids compat breaks in 9.1+
namespace Spoons;
use Composer\Script\Event;
use Symfony\Component\Process\Process;
* A Composer script handler.
class ScriptHandler {
* Create a web/modules/[SLUG] dir and symlink all project files into it.
* @param \Composer\Script\Event $event
* A Composer package event.
public static function createSymlinks(Event $event) {
$full_name = $event->getComposer()->getPackage()->getName();
[, $project_name] = explode('/', $full_name);
$cmd = "rm -rf web/modules/custom/$project_name && mkdir -p web/modules/custom/$project_name";
$process = new Process($cmd);
$cmd = 'find ../../../.. -maxdepth 1 ! -name .git ! -name web ! -name vendor ! -name .idea -print | while read file; do ln -s "$file" .; done';
$process = new Process($cmd, "web/modules/custom/$project_name");
# Autoservices and autowiring
Brings features from symfony 3.3 and above into the Drupal
service container.
## Features
* Register services without a just the class.
* [Autowire]( those services.
* Register aliases for many Drupal services to ease autowiring.
## Usage
This module provides new ways for other modules to register services so
it does nothing by itself and it should be installed as a dependency.
Once enabled any class placed in `modulename/src/Autoservice/` will be
registered into the container with autowiring enabled. This means that
if the constructor has all the classes that it needs correctly type-hinted
with the corresponding interfaces the service will be available
with its full class name as the identifier.
Since the autowiring needs to know what service corresponds with an
interface and Drupal doesn't provide that mapping this module also
adds many aliases so the autowiring knows better what service to inject.
It won't work if you interface doesn't follow the pattern of being in
the same namespace as the service and the same name with the `Interface`
Other services that can be registered:
* `modulename/src/AutoEventListener/`: The event_listener tag is automatically added. Autowired.
* `modulename/src/AutoPluginManager/`: Inherits from the `default_plugin_manager`. Not autowired.
## Contributions
The project is open to improvements about how to bring more
symfony container features into Drupal but also feel free to open any discussion about
how to promote and stabilize it further so more modules
can rely on it.
Patches on are accepted but merge requests on
[gitlab]( are preferred.
## Real time communication
Join the [#autoservices](
channel on [](
## Notes
This project started as a way to ease development of
[Libraries provider](
so at the moment it works for the services defined there (no services.yml file!)
If you want to contribute to bring this features into core
[there is a meta issue.](
The inspiration about how to achieve the auto-registrations came from
[this talk]( (mute the left channel of your audio).
## Similar modules
[Extended container]( it needs
patches to Drupal and that makes it more difficult to install and maintain.
name: Autoservices
type: module
description: Register services into the container without configuration.
package: Container
core_version_requirement: ^8.8 || ^9
"name": "drupal/autoservices",
"description": "Register services into the container without configuration",
"type": "drupal-module",
"homepage": "",
"support": {
"source": "",
"issues": ""
"license": "GPL-2.0-or-later",
"repositories": [
"type": "composer",
"url": ""
"require": {
"require-dev": {
"composer/installers": "^1",
"cweagans/composer-patches": "~1.0",
"drupal/core-composer-scaffold": "^9.0",
"drupal/core-dev": "^9.0",
"drupal/core-recommended": "^9.0",
"drush/drush": "^10",
"mglaman/phpstan-drupal": "^0.12",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-deprecation-rules": "^0.12",
"zaporylie/composer-drupal-optimizations": "^1.0"
"autoload": {
"classmap": [".spoons/ScriptHandler.php"]
"config": {
"process-timeout": 36000
"scripts": {
"si": "drush si -v --db-url=${SIMPLETEST_DB:-mysql://root:password@mariadb/db}",
"phpcs": "phpcs --runtime-set ignore_warnings_on_exit 1 --runtime-set ignore_errors_on_exit 1 web/modules/custom",
"lint": "parallel-lint --exclude web --exclude vendor .",
"webserver": "cd web && php -S .ht.router.php",
"chromedriver": "chromedriver --port=9515 --verbose --whitelisted-ips --log-path=/tmp/chromedriver.log --no-sandbox",
"unit": "phpunit --verbose web/modules/custom",
"phpstan": "phpstan analyse web/modules/custom",
"stylelint": "yarn --silent --cwd web/core stylelint --formatter verbose --config ./.stylelintrc.json ../modules/custom/**/*.css",
"eslint": "yarn --silent --cwd web/core eslint -c ./.eslintrc.json ../modules/custom",
"post-update-cmd": ["Spoons\\ScriptHandler::createSymlinks"]
"extra": {
"installer-paths": {
"web/core": ["type:drupal-core"],
"web/libraries/{$name}": ["type:drupal-library"],
"web/modules/contrib/{$name}": ["type:drupal-module"],
"web/profiles/{$name}": ["type:drupal-profile"],
"web/themes/{$name}": ["type:drupal-theme"],
"drush/{$name}": ["type:drupal-drush"]
"drupal-scaffold": {
"locations": {
"web-root": "web/"
version: "3.1"
# More info at
image: wodby/php:${PHP_TAG-7.4-dev}
command: composer webserver
COLUMNS: ${COLUMNS-80} # Set 80 columns for docker exec -it.
## Read instructions at
# The line below is commented out because the mere presence of that env variable loads XDebug regardless of the value.
# Enable XDebug when you `up` your container: PHP_XDEBUG=1 docker-compose up -d
# Specify 'drupal' instead of so that chrome service can reach it.
SIMPLETEST_BASE_URL: http://drupal:8888
- ./:/var/www/html:cached
- ./.docker/zz-php.ini:/usr/local/etc/php/conf.d/zz-php.ini
# ports:
# - '${WEB_PORT-8889}:8888'
# More info at
image: wodby/mariadb:${MARIADB_TAG-10.3}
stop_grace_period: 30s
- mariadb-datavolume:/var/lib/mysql
image: drupalci/webdriver-chromedriver:production
soft: -1
hard: -1
- /dev/shm:/dev/shm
- chromedriver
- "--no-sandbox"
- "--log-path=/tmp/chromedriver.log"
- "--verbose"
- "--whitelisted-ips="
#data volumes
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Module">
<arg name="extensions" value="php,module,inc,install,test,profile,theme,css,info,txt,md,yml"/>
<config name="drupal_core_version" value="8"/>
<!-- Initially include all Drupal and DrupalPractice sniffs. -->
<rule ref="vendor/drupal/coder/coder_sniffer/Drupal"/>
<rule ref="vendor/drupal/coder/coder_sniffer/DrupalPractice"/>
<!-- Use 's' to print the full sniff name in the report. -->
<!-- A '-' is prefixed to each of these, so s becomes -s, etc. -->
<arg value="s"/>
<arg value="-colors"/>
<arg name='report-width' value='120'/>
<!-- Ignore all files that match these patterns. They are matched against -->
<!-- the full file path and there is an implied wildcard at each end. -->
<!-- Periods must be escaped using \. -->
<!-- Examples for how you disable rules you do not like. -->
<!-- Exclude a sniff from running on specific files. -->
<rule ref="Drupal.Files.TxtFileLineLength.TooLong">
<!-- Exclude .md files from the line limit rule. -->
<rule ref="Drupal.Commenting.DocComment.ParamNotFirst">
<!-- Drush commands are most readable with @command at top. -->
<!-- Use a rule, but exclude one of its sniffs from all files. -->
<rule ref="Drupal.Arrays.Array.LongLineDeclaration">
<!-- Method declarations should be exempt. -->
customRulesetUsed: true
reportUnmatchedIgnoredErrors: false
# Ignore phpstan-drupal extension's rules.
- '#\Drupal calls should be avoided in classes, use dependency injection instead#'
- '#Plugin definitions cannot be altered.#'
- '#Missing cache backend declaration for performance.#'
- '#Plugin manager has cache backend specified but does not declare cache tags.#'
# Migrate test fixtures kill phpstan, too much PHP.
- */tests/fixtures/*.php
- vendor/mglaman/phpstan-drupal/extension.neon
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
<?xml version="1.0" encoding="UTF-8"?>
<!-- TODO set checkForUnintentionallyCoveredCode="true" once is resolved. -->
<!-- PHPUnit expects functional tests to be run with either a privileged user
or your current system user. See core/tests/ and for details.
<phpunit bootstrap="web/core/tests/bootstrap.php" colors="true"
<!-- Set error reporting to E_ALL. -->
<ini name="error_reporting" value="32767"/>
<!-- Do not limit the amount of memory tests take to run. -->
<ini name="memory_limit" value="-1"/>
<!-- Example SIMPLETEST_BASE_URL value: http://localhost -->
<env name="SIMPLETEST_BASE_URL" value=""/>
<!-- Example SIMPLETEST_DB value: mysql://username:password@localhost/databasename#table_prefix -->
<env name="SIMPLETEST_DB" value="mysql://root:password@mariadb/db"/>
<!-- Example BROWSERTEST_OUTPUT_DIRECTORY value: /path/to/webroot/sites/simpletest/browser_output -->
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value="/tmp"/>
<!-- To have browsertest output use an alternative base URL. For example if
SIMPLETEST_BASE_URL is an internal DDEV URL, you can set this to the
external DDev URL so you can follow the links directly.
<env name="BROWSERTEST_OUTPUT_BASE_URL" value=""/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="disabled"/>
<!-- Example for changing the driver class for mink tests MINK_DRIVER_CLASS value: 'Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver' -->
<env name="MINK_DRIVER_CLASS" value=''/>
<!-- Example for changing the driver args to mink tests MINK_DRIVER_ARGS value: '[""]' -->
<env name="MINK_DRIVER_ARGS" value=''/>
<!-- Example for changing the driver args to phantomjs tests MINK_DRIVER_ARGS_PHANTOMJS value: '[""]' -->
<env name="MINK_DRIVER_ARGS_PHANTOMJS" value=''/>
<!-- Example for changing the driver args to webdriver tests MINK_DRIVER_ARGS_WEBDRIVER value: '["chrome", { "chromeOptions": { "w3c": false } }, "http://localhost:4444/wd/hub"]' For using the Firefox browser, replace "chrome" with "firefox" -->
<!-- Example from>
<env name="MINK_DRIVER_ARGS_WEBDRIVER" value='["chrome", {"browserName":"chrome","chromeOptions":{"args":["--disable-gpu","--headless"]}}, "http://chrome:9515"]'/>
<testsuite name="unit">
<testsuite name="kernel">
<testsuite name="functional">
<testsuite name="functional-javascript">
<testsuite name="build">
<listener class="\Drupal\Tests\Listeners\DrupalListener">
<!-- The Symfony deprecation listener has to come after the Drupal listener -->
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
namespace Drupal\autoservices;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\Finder\Finder;
* Defines a service provider for the Autoservices module.
class AutoservicesServiceProvider extends ServiceProviderBase {
* {@inheritdoc}
public function register(ContainerBuilder $container) {
foreach ($container->getParameter('container.modules') as $moduleName => $moduleInfo) {
$autoservices = [
'Autoservice' => [
'autowire' => TRUE,
'AutoPluginManager' => [
'autowire' => FALSE,
'parent' => 'default_plugin_manager',
'AutoEventSubscriber' => [
'autowire' => TRUE,
'tags' => [
['name' => 'event_subscriber'],
foreach ($autoservices as $namespace => $configuration) {
$path = dirname($moduleInfo['pathname']) . '/src/' . $namespace;
if (file_exists($path)) {
$finder = new Finder();
foreach ($finder as $fileInfo) {
$class = 'Drupal\\' . $moduleName . '\\' . $namespace . '\\' . substr($fileInfo->getFilename(), 0, -4);
if ($container->hasDefinition($class)) {
$definition = isset($configuration['parent']) ? new ChildDefinition($configuration['parent']) : new Definition($class);
if (isset($configuration['tags'])) {
foreach ($configuration['tags'] as $tag) {
$container->setDefinition($class, $definition);
* Add aliases for all services that implement and obvious interface.
protected function aliasInterfaces(ContainerBuilder $container) {
foreach ($container->getDefinitions() as $definitionName => $definition) {
$definitionInitial = substr($definitionName, 0, 1);
$interfaceCandidate = $definition->getClass() . 'Interface';
if (
$definitionInitial === strtolower($definitionInitial) &&
interface_exists($interfaceCandidate) &&
) {
$container->setAlias($interfaceCandidate, $definitionName);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment