"YAML is a human friendly data serialization standard for all programming languages."
-yaml.org
"...and XML has cooties ;)" -chx
http://www.drupal4hu.com/node/377
name = Demo Module
description = "A demo module used to explain the basics."
core = 7.x
package = "D7 Demo"
dependencies[] = node
name: Demo Module
type: module
description: "A demo module used to explain the basics."
core: 8.x
package: D8 Demo
dependencies:
- node
The Framework Interoperatabilty Group is a collection of individuals that discuss the commonalities between their PHP-based projects and find ways we can all work together.
This group established the PSR-0 standard which is a guideline to be followed for autoloader interoperability.
\<Vendor Name>\(<Namespace>\)*<Class Name>
DIRECTORY_SEPARATOR
when loading from the file system._
character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR
. The _
character has no special meaning in the namespace.\Guzzle\Common\Event => /Guzzle/Common/Event.php
\Symfony\Component\Yaml\Parser => /Symfony/Component/Yaml/Parser.php
\Drupal\Core\Config\FileStorage => /Drupal/Core/Config/FileStorage.php
\namespace\package\Class_Name => /namespace/package/Class/Name.php
namespace Guzzle\Common;
use Symfony\Component\EventDispatcher\Event as SymfonyEvent;
/**
* Default event for Guzzle notifications
*/
class Event extends SymfonyEvent implements ToArrayInterface, \ArrayAccess, \IteratorAggregate
{
...
\Drupal\demo\Controller\MyController =>
/modules/demo/lib/Drupal/demo/Controller/MyController.php
namespace Drupal\demo\Controller;
class MyController {
private function text() {
return t('Hello, world');
}
public function page() {
return array(
'#type' => 'markup',
'#markup' => $this->text(),
);
}
}
/modules/demo/lib/Drupal/demo/Controller/FinalController.php
namespace Drupal\demo\Controller;
use Drupal\demo\Controller\MyController;
class FinalController extends MyController {
final public function page() {
return array(
'#type' => 'markup',
'#markup' => t('Goodbye, cruel world!'),
);
}
}
A controller is a PHP function you create that takes information from the HTTP request [then] constructs and returns an HTTP response (as a Symfony2 Response object). The response could be an HTML page, an XML document, a serialized JSON array, an image, a redirect, a 404 error or anything else you can dream up. The controller contains whatever arbitrary logic your application needs to render the content of a page.
http://symfony.com/doc/current/book/controller.html
/Drupal/demo/Controller/MyController::page
A route is a path which is defined for Drupal to return some sort of content on. For example, the default front page, '/node' is a route. When Drupal receives a request, it tries to match the requested path to a route it knows about. If the route is found, then the route's definition is used to return content. Otherwise, Drupal returns a 404.
Drupal's routing system works with the Symfony HTTP Kernel [and] is responsible for matching paths to controllers, and you define those relations in routes. You can pass on additional information to your controllers in the route [while] access checking is integrated.http://drupal.org/developing/api/8/routing
/sites/all/modules/demo/demo.module
function demo_menu() {
$items['hello'] = array(
'title' => 'Demo',
'page callback' => 'demo_page',
'access arguments' => array('access content'),
);
return $items;
}
function demo_page() {
return t('Hello, world!');
}
/modules/demo/demo.routing.yml
demo_page:
path: '/hello'
defaults:
_title: 'Demo'
_content: 'Drupal\demo\Controller\MyController::page'
requirements:
_permission: 'access content'
demo_final_page:
path: '/goodbye'
defaults:
_title: 'Demo'
_content: 'Drupal\demo\Controller\FinalController::page'
requirements:
_permission: 'access content'
http://drupal.org/node/2092643
/sites/all/modules/demo/demo.module
function demo_menu() {
$items['hello'] = array(
'title' => 'Demo',
'page callback' => 'demo_page',
'access callback' => 'demo_anonymous_access_only',
);
return $items;
}
function demo_anonymous_access_only() {
global $user;
if (in_array('anonymous user', $user->roles)) {
return TRUE;
}
else {
return FALSE;
}
}
/modules/demo/demo.routing.yml
demo_final_page:
path: '/goodbye'
defaults:
_title: 'Demo'
_content: 'Drupal\demo\Controller\FinalController::page'
requirements:
_permission: 'access content'
_access_user_is_anonymous: 'TRUE'
/modules/demo/demo.services.yml
services:
access_check.demo.user_is_anonymous:
class: Drupal\demo\Access\UserIsAnonymousAccessCheck
tags:
- { name: access_check }
/modules/demo/lib/Drupal/demo/Access/UserIsAnonymousAccessCheck.php
namespace Drupal\demo\Access;
use Drupal\Core\Access\AccessCheckInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
class UserIsAnonymousAccessCheck implements AccessCheckInterface {
public function applies(Route $route) {
return $route->hasRequirement('_access_user_is_anonymous');
}
public function access(Route $route, Request $request, AccountInterface $account) {
return (!$account->isAnonymous() ? static::DENY : static::ALLOW);
}
}
What does one do if they need to create a route dynamically?
How does one create routes based on things that don't yet exist?
In Drupal 7, we created dynamic routes with a loop in hook_menu().
In Drupal 8, dynamic routes are created in a callback method.
/sites/all/modules/octocat/octocat.module
function octocat_menu() {
$items = array();
foreach (octocat_get_types() as $type) {
$items['octocat/add/' . $type->machine_name] = array(
'title' => $type->title,
'page callback' => 'octocat_type_add_page',
'access arguments' => 'create ' . $type->type,
);
}
return $items;
}
/modules/octocat/octocat.routing.yml
route_callbacks:
- '\Drupal\octocat\Routing\OctoCatRoutes::routes'
/modules/octocat/lib/Drupal/octocat/Routing/OctoCatRoutes.php
namespace Drupal\octocat\Routing;
use Symfony\Component\Routing\Route;
class OctoCatRoutes {
public function routes() {
$routes = array();
foreach (octocat_get_types() as $type) {
$routes['octocat.add.' . $type] = new Route(
'/octocat/add/' . $type,
array(
'_title' => t('Add @type', array('@type' => ucwords($type))),
'_content' => '\Drupal\octocat\Controller\OctoCatController::add',
'type' => $type,
),
array('_access' => 'TRUE')
);
}
return $routes;
}
}
/modules/octocat/config/octocat.settings.yml
octocats:
- original
- drupalcat
- dojocat
- codercat
/modules/octocat/lib/Drupal/octocat/Routing/OctoCatRoutes.php
class OctoCatRoutes {
public function routes() {
$config = \Drupal::config('octocat.settings');
$routes = array();
foreach ($config->get('octocats') as $type) {
$routes['octocat.page.' . $type] = new Route(
'/octocat/' . $type,
array(
'_title' => t('@type', array('@type' => ucwords($type))),
'_content' => '\Drupal\octocat\Controller\OctoCatController::page',
'type' => $type,
),
array('_access' => 'TRUE')
);
}
return $routes;
}
}
(No, you won't have to get a shot.)
-James Shore
http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html
class User {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
class Tweet {
private $user;
private $tweet;
public function __construct($tweet, $user_name) {
$this->user = new User($user_name);
$this->tweet = $tweet;
}
public function getUser() {
return $this->user;
}
public function getTweet() {
return $this->tweet;
}
}
$user_name
passed to the Tweet
constructor is not used inside Tweet
's scope. $user_name
should remain inside the User
class - it has nothing to do with the actual Tweet
.User
class is tightly coupled with the Tweet
class. If we add a new parameter to User
's constructor, we must then modify every class where we've created a User
object. This is technical debt we'd like to avoid.Tweet
class is has become a convoluted chore as it now requires testing the User
class as well.class User {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
class Tweet {
private $user;
private $tweet;
public function __construct($tweet, User $user) {
$this->user = $user;
$this->tweet = $tweet;
}
public function getUser() {
return $this->user;
}
public function getTweet() {
return $this->tweet;
}
}
/core/core.services.yml
services:
cache_factory:
class: Drupal\Core\Cache\CacheFactory
arguments: ['@settings']
calls:
- [setContainer, ['@service_container']]
...
But you may need to define your own services, for example, when providing custom interfaces. Thankfully, its a simple as providing your own mymodule.services.yml
file for Drupal to discover.
Drupal
acts as a global accessor to arbitrary services within the system.
// Returns a Drupal\Core\Datetime\Date object.
$date = \Drupal::service('date');
http://drupal.org/node/2133171
/modules/octocat/lib/Drupal/octocat/Routing/OctoCatRoutes.php
ContainerInjectionInterface
ConfigFactoryInterface
as a service.create
inherited from ContainerInjectionInterface
getOctoCats
that returns the config octocat.settings
via the injected service.getOctoCats
inside the routes
method....
public function getOctoCatsTest() {
$types = array('spectrocat', 'octobiwan');
$config = $this->getMockBuilder('Drupal\Core\Config\Config')
->disableOriginalConstructor()
->getMock();
$config->expects($this->once())
->method('get')
->with('octocats')
->will($this->returnValue($types));
$this->configFactory->expects($this->once())
->method('get')
->with('octocat.settings')
->will($this->returnValue($config));
$octocatRoutes = new OctoCatRoutes($this->configFactory);
$octocats = $octocatRoutes->getOctoCats();
$this->assertNotEmpty($octocats);
$this->assertCount(2, $octocats);
}
For example, running a single method:
./vendor/bin/phpunit --filter=MyMethodTest
composer global require drush/drush:dev-master
/modules/octocat/octocat.module
function octocat_theme() {
return array(
'octocat_page' => array(
'variables' => array('type' => NULL),
'template' => 'octocat-page',
),
);
}
/modules/octocat/lib/Drupal/octocat/Controller/OctoCatController.php
namespace Drupal\octocat\Controller;
use Symfony\Component\HttpFoundation\Request;
class OctoCatController {
public function page(Request $request, $type) {
return array(
'#theme' => 'octocat_page',
'#type' => $type,
);
}
}
/modules/octocat/templates/octocat-page.html.twig
<div id="octocat-{{ type }}" class="octocat">
<img src="/modules/octocat/images/{{ type }}.jpg" title="{{ type }}" />
</div>
hook_block_info
and hook_block_view
(at a minimum). If you wanted to learn about Views or Panels, you leared about the CTools module and its advanced plugin system, etc."You just have to learn one single idea and you can apply it anywhere."Drupal 8 wins: unified entities n' plugins!
-Daniel Wehner
/modules/demo/lib/Drupal/demo/Plugin/Block/DemoBlock.php
/**
* Provides a demo block.
*
* @Block(
* id = "demo_block",
* admin_label = @Translation("Demo Block")
* )
*/
class DemoBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array(
'text' => 'Default text',
);
}
...
getInfo()
method on each plugin class could be a memory issue, requiring parsing the PHP of every plugin class during discovery.
/**
* Provides a demo block.
*
* @Block(
* id = "demo_block",
* admin_label = @Translation("Demo Block")
* )
*/
Enter Doctrine's Annotation mechanism (Patched by chx to use the php tokenizer)
Annotations are now available as a mechanism for plugin discovery (replacing info hooks)!
https://drupal.org/node/1683644
Use a spacebar or arrow keys to navigate