Drupal 8 for Developers: Difference between revisions
Lsokolowski1 (talk | contribs) |
Lsokolowski1 (talk | contribs) mNo edit summary |
||
Line 14: | Line 14: | ||
{{Can I use your material}} | {{Can I use your material}} | ||
== Overview of Drupal | == Overview of Drupal == | ||
* What Is Drupal? | * What Is Drupal? | ||
Line 20: | Line 20: | ||
* Drupal '''Add-Ons''': ''Modules'', ''Themes'', ''Distributions'', and ''Translations'' | * Drupal '''Add-Ons''': ''Modules'', ''Themes'', ''Distributions'', and ''Translations'' | ||
== Overview Con't | == Overview Con't == | ||
Major '''subsystems''' in Drupal | Major '''subsystems''' in Drupal | ||
* Routing, Menus | * Routing, Menus | ||
Line 29: | Line 29: | ||
* <small>''[https://www.drupal.org/docs/drupal-apis Related docs]''</small> | * <small>''[https://www.drupal.org/docs/drupal-apis Related docs]''</small> | ||
=== Routing | === Routing === | ||
* Handles '''interactions''' | * Handles '''interactions''' | ||
** user (or system) accessing a certain path (or resource) | ** user (or system) accessing a certain path (or resource) | ||
Line 39: | Line 39: | ||
* <small>''[https://www.drupal.org/docs/drupal-apis/routing-system Related docs]''</small> | * <small>''[https://www.drupal.org/docs/drupal-apis/routing-system Related docs]''</small> | ||
=== Menus | === Menus === | ||
* Navigation | * Navigation | ||
** provides details about how the site itself is '''organized''' | ** provides details about how the site itself is '''organized''' | ||
Line 49: | Line 49: | ||
=== Entities | === Entities === | ||
* Powerful way of '''modeling data and content''' in Drupal | * Powerful way of '''modeling data and content''' in Drupal | ||
* ''Node'', ''taxonomy'', ''user'', ''comment'', ''file'', ''media'', etc - or our '''custom''' one | * ''Node'', ''taxonomy'', ''user'', ''comment'', ''file'', ''media'', etc - or our '''custom''' one | ||
Line 60: | Line 60: | ||
* <small>''[https://www.drupal.org/docs/drupal-apis/entity-api Related docs]''</small> | * <small>''[https://www.drupal.org/docs/drupal-apis/entity-api Related docs]''</small> | ||
=== Fields | === Fields === | ||
* '''Entity bundles''' can have various fields | * '''Entity bundles''' can have various fields | ||
* Are responsible for '''holding data''' | * Are responsible for '''holding data''' | ||
Line 69: | Line 69: | ||
*** '''exported''' via configuration | *** '''exported''' via configuration | ||
==== Fields con't | ==== Fields con't ==== | ||
* Can also be of '''multiples types''', depending on the data they store | * Can also be of '''multiples types''', depending on the data they store | ||
** string (or text) fields, numeric, date, email, and so on | ** string (or text) fields, numeric, date, email, and so on | ||
Line 79: | Line 79: | ||
=== Views | === Views === | ||
* '''Listing''' content and data | * '''Listing''' content and data | ||
* Allows the creation of '''configurable''' listings | * Allows the creation of '''configurable''' listings | ||
Line 86: | Line 86: | ||
* <small>''Related [https://api.drupal.org/api/drupal/core%21modules%21views%21views.api.php/group/views_overview/9.4.x Views API]''</small> | * <small>''Related [https://api.drupal.org/api/drupal/core%21modules%21views%21views.api.php/group/views_overview/9.4.x Views API]''</small> | ||
=== Forms | === Forms === | ||
* Capture user input | * Capture user input | ||
* Powerful Form API | * Powerful Form API | ||
Line 95: | Line 95: | ||
* <small>''[https://www.drupal.org/docs/drupal-apis/form-api Related docs]''</small> | * <small>''[https://www.drupal.org/docs/drupal-apis/form-api Related docs]''</small> | ||
=== Configuration | === Configuration === | ||
* '''Centralized''' configuration system | * '''Centralized''' configuration system | ||
* Can be stored in: '''database''' (default), '''files''' in a specific dir, '''other''' storage back-ends | * Can be stored in: '''database''' (default), '''files''' in a specific dir, '''other''' storage back-ends | ||
Line 105: | Line 105: | ||
* <small>''[https://www.drupal.org/docs/drupal-apis/configuration-api Related docs]''</small> | * <small>''[https://www.drupal.org/docs/drupal-apis/configuration-api Related docs]''</small> | ||
=== Plugins | === Plugins === | ||
* '''Encapsulating''' functionality - very widely used in D9 | * '''Encapsulating''' functionality - very widely used in D9 | ||
* Components of '''reusable''' code - used and managed by a central system | * Components of '''reusable''' code - used and managed by a central system | ||
Line 126: | Line 126: | ||
* <small>''[https://www.drupal.org/docs/drupal-apis/plugin-api Related docs]''</small> | * <small>''[https://www.drupal.org/docs/drupal-apis/plugin-api Related docs]''</small> | ||
=== The theme system | === The theme system === | ||
* Is spread out over Drupal core, modules, and themes | * Is spread out over Drupal core, modules, and themes | ||
* Both '''modules and themes''' can theme data or content | * Both '''modules and themes''' can theme data or content | ||
Line 137: | Line 137: | ||
* <small>''[https://www.drupal.org/docs/theming-drupal Related docs]''</small> | * <small>''[https://www.drupal.org/docs/theming-drupal Related docs]''</small> | ||
=== Caching | === Caching === | ||
* Improves the '''performance''' of building pages and rendering data | * Improves the '''performance''' of building pages and rendering data | ||
* '''Cache backend''' - stores the results of complex data calculations | * '''Cache backend''' - stores the results of complex data calculations | ||
Line 145: | Line 145: | ||
* <small>''[https://www.drupal.org/docs/drupal-apis/cache-api Related docs]''</small> | * <small>''[https://www.drupal.org/docs/drupal-apis/cache-api Related docs]''</small> | ||
== The Evolution of Drupal | == The Evolution of Drupal == | ||
Changes introduced in Drupal 8: | Changes introduced in Drupal 8: | ||
* improved '''the usability and accessibility''' of the administrative UI | * improved '''the usability and accessibility''' of the administrative UI | ||
Line 157: | Line 157: | ||
*** usable by other open-source projects | *** usable by other open-source projects | ||
=== Changes in internal systems and APIs | === Changes in internal systems and APIs === | ||
* URL request handler from the '''Symfony''' | * URL request handler from the '''Symfony''' | ||
* Swappable '''services''' (also from Symfony) | * Swappable '''services''' (also from Symfony) | ||
Line 172: | Line 172: | ||
* '''Views''' in core, Poll out of the core, etc | * '''Views''' in core, Poll out of the core, etc | ||
== Handling HTTP Requests | == Handling HTTP Requests == | ||
* The main Drupal ''index.php'' file is loaded and executed by the server to handle the request | * The main Drupal ''index.php'' file is loaded and executed by the server to handle the request | ||
* Drupal 8 uses Symfony to handle HTTP requests | * Drupal 8 uses Symfony to handle HTTP requests | ||
Line 184: | Line 184: | ||
** Symfony handles the req and returns result to browser | ** Symfony handles the req and returns result to browser | ||
=== Symfony HTTP request system in Drupal 8 | === Symfony HTTP request system in Drupal 8 === | ||
* Modules can register '''routes''', which map URLs and conditions (context) to '''controller''' classes | * Modules can register '''routes''', which map URLs and conditions (context) to '''controller''' classes | ||
Line 199: | Line 199: | ||
*** providing dynamic URL routes | *** providing dynamic URL routes | ||
== Cache in Drupal | == Cache in Drupal == | ||
* System, which allows modules to precalculate data or output and store it | * System, which allows modules to precalculate data or output and store it | ||
Line 207: | Line 207: | ||
*** when data is invalidated (changes in dependent data), any module that uses caching needs to clear it | *** when data is invalidated (changes in dependent data), any module that uses caching needs to clear it | ||
=== Examples of cached information | === Examples of cached information === | ||
* Page output for anonymous users (can be disabled on Performance config page) | * Page output for anonymous users (can be disabled on Performance config page) | ||
Line 216: | Line 216: | ||
* Theme information (list of theme regions, theme-related info from modules and themes) | * Theme information (list of theme regions, theme-related info from modules and themes) | ||
=== Cache API in Drupal 8 | === Cache API in Drupal 8 === | ||
* Services do the job | * Services do the job | ||
Line 230: | Line 230: | ||
*** set(), get(), invalidate(), etc | *** set(), get(), invalidate(), etc | ||
==== Clearing caches | ==== Clearing caches ==== | ||
* When we use default core cache bins, it will be flushed automatically | * When we use default core cache bins, it will be flushed automatically | ||
Line 252: | Line 252: | ||
</small> | </small> | ||
==== Tagging mechanism | ==== Tagging mechanism ==== | ||
* To flush parts of the cache when data they’re related to is changed or removed | * To flush parts of the cache when data they’re related to is changed or removed | ||
Line 266: | Line 266: | ||
** Custom tags<br><syntaxhighlight lang="php">\Drupal\Core\Cache\Cache::invalidateTags($tags);</syntaxhighlight> | ** Custom tags<br><syntaxhighlight lang="php">\Drupal\Core\Cache\Cache::invalidateTags($tags);</syntaxhighlight> | ||
== Automatic Class Loading in Drupal | == Automatic Class Loading in Drupal == | ||
* Automatic loading of files containing PHP '''class''', '''interface''', and '''trait''' declarations | * Automatic loading of files containing PHP '''class''', '''interface''', and '''trait''' declarations | ||
Line 273: | Line 273: | ||
** Reduce the burden on developers by automatically loading files containing class definitions (instead of making them load the classes explicitly in code) | ** Reduce the burden on developers by automatically loading files containing class definitions (instead of making them load the classes explicitly in code) | ||
=== Drupal 8 Specific Way | === Drupal 8 Specific Way === | ||
* Incorporates the '''class loader''' from the ''Composer'' | * Incorporates the '''class loader''' from the ''Composer'' | ||
Line 288: | Line 288: | ||
** The directory that each class file goes in depends on its namespace (examples in the next slide) | ** The directory that each class file goes in depends on its namespace (examples in the next slide) | ||
==== Dirs and namespaces, Examples | ==== Dirs and namespaces, Examples ==== | ||
Relations between dirs and namespaces | Relations between dirs and namespaces | ||
* Add-on module’s class | * Add-on module’s class | ||
Line 303: | Line 303: | ||
'vendor/symfony/dependency-injection/Container.php' | 'vendor/symfony/dependency-injection/Container.php' | ||
==== Drupal 8 OOP Examples (Not Yet In D9, works only in D8) | ==== Drupal 8 OOP Examples (Not Yet In D9, works only in D8) ==== | ||
<small> | <small> | ||
Modules to enable: ''oop_examples'', ''oop_design_patterns'' | Modules to enable: ''oop_examples'', ''oop_design_patterns'' | ||
Line 312: | Line 312: | ||
<!-- TODOS starts here --> | <!-- TODOS starts here --> | ||
== Drupal Rules, Programming | == Drupal Rules, Programming == | ||
* Alterability | * Alterability | ||
Line 322: | Line 322: | ||
* Tests, Documentation | * Tests, Documentation | ||
=== Alterability | === Alterability === | ||
* Drupal core and contributed modules are almost fully alterable | * Drupal core and contributed modules are almost fully alterable | ||
Line 334: | Line 334: | ||
** YAML-based menu | ** YAML-based menu | ||
==== Drupal Hooks | ==== Drupal Hooks ==== | ||
* PHP file or function you can put into a module or theme | * PHP file or function you can put into a module or theme | ||
* Will be '''invoked''' (called or included) at an appropriate time by a Drupal core or contributed module | * Will be '''invoked''' (called or included) at an appropriate time by a Drupal core or contributed module | ||
Line 353: | Line 353: | ||
</small> | </small> | ||
==== Drupal Plugins | ==== Drupal Plugins ==== | ||
* Replaced many of the generic hooks from Drupal 7 with an object-oriented system | * Replaced many of the generic hooks from Drupal 7 with an object-oriented system | ||
Line 370: | Line 370: | ||
</small> | </small> | ||
==== Drupal Dependency Injection | ==== Drupal Dependency Injection ==== | ||
* Allows modules to replace fundamental core systems of Drupal (or services) | * Allows modules to replace fundamental core systems of Drupal (or services) | ||
** for example the mechanism for storing cached data | ** for example the mechanism for storing cached data | ||
==== Drupal Routing | ==== Drupal Routing ==== | ||
* Symfony-based routing system | * Symfony-based routing system | ||
Line 381: | Line 381: | ||
** allows to alter what happens at various points during an HTTP request by defining event subscribers | ** allows to alter what happens at various points during an HTTP request by defining event subscribers | ||
==== Drupal Links | ==== Drupal Links ==== | ||
* YAML-based system | * YAML-based system | ||
Line 396: | Line 396: | ||
--> | --> | ||
==== Drupal Module Themeable, Output | ==== Drupal Module Themeable, Output ==== | ||
* Render arrays should be returned from all functions that return output | * Render arrays should be returned from all functions that return output | ||
Line 412: | Line 412: | ||
<!-- TODO: Do all those empty sections below --> | <!-- TODO: Do all those empty sections below --> | ||
=== Separation of: Content, Configuration, State Data | === Separation of: Content, Configuration, State Data === | ||
* '''Content''' - Entities, Bundles, Fields, data in db | * '''Content''' - Entities, Bundles, Fields, data in db | ||
* '''Configuration''' - setups, YAML files, definitions | * '''Configuration''' - setups, YAML files, definitions | ||
Line 421: | Line 421: | ||
** <small>[https://www.drupal.org/docs/drupal-apis/state-api Related docs]</small> | ** <small>[https://www.drupal.org/docs/drupal-apis/state-api Related docs]</small> | ||
=== i18n (internationalization) | === i18n (internationalization) === | ||
* Drupal translations - <small>''https://localize.drupal.org/''</small> | * Drupal translations - <small>''https://localize.drupal.org/''</small> | ||
* Related <small>''[https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Language%21language.api.php/group/i18n/9.4.x i18n API]''</small> | * Related <small>''[https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Language%21language.api.php/group/i18n/9.4.x i18n API]''</small> | ||
* <small>''[https://www.drupal.org/docs/8/api/translation-api/overview Related docs]''</small> | * <small>''[https://www.drupal.org/docs/8/api/translation-api/overview Related docs]''</small> | ||
=== Accessibility, Usability | === Accessibility, Usability === | ||
* Accessibility | * Accessibility | ||
** <small>''[https://www.drupal.org/docs/accessibility Related docs]''</small> | ** <small>''[https://www.drupal.org/docs/accessibility Related docs]''</small> | ||
Line 432: | Line 432: | ||
** <small>''[https://www.drupal.org/docs/develop/user-interface-standards Related docs]''</small> | ** <small>''[https://www.drupal.org/docs/develop/user-interface-standards Related docs]''</small> | ||
=== DB Independence (database) | === DB Independence (database) === | ||
* <small>''Related [https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21database.api.php/group/database/9.4.x DB API]''</small> | * <small>''Related [https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21database.api.php/group/database/9.4.x DB API]''</small> | ||
* <small>''[https://www.drupal.org/docs/drupal-apis/database-api Related docs]''</small> | * <small>''[https://www.drupal.org/docs/drupal-apis/database-api Related docs]''</small> | ||
=== Security (all user-provided input is insecure) | === Security (all user-provided input is insecure) === | ||
* <small>''Related [https://api.drupal.org/api/drupal/core%21core.api.php/group/best_practices/9.4.x Security API]''</small> | * <small>''Related [https://api.drupal.org/api/drupal/core%21core.api.php/group/best_practices/9.4.x Security API]''</small> | ||
* <small>''[https://www.drupal.org/docs/security-in-drupal Related docs]''</small> | * <small>''[https://www.drupal.org/docs/security-in-drupal Related docs]''</small> | ||
=== Tests, Documentation | === Tests, Documentation === | ||
* Should we bother at all, huh? | * Should we bother at all, huh? | ||
Line 446: | Line 446: | ||
** Yes, everything should be documented | ** Yes, everything should be documented | ||
==== Everything should be tested | ==== Everything should be tested ==== | ||
* SimpleTest, PHPUnit - both adopted in D8, 1st deprecated (new tests should be written with 2nd) | * SimpleTest, PHPUnit - both adopted in D8, 1st deprecated (new tests should be written with 2nd) | ||
Line 453: | Line 453: | ||
* Adopted also by lots of contributed modules (to some extent, at least) | * Adopted also by lots of contributed modules (to some extent, at least) | ||
==== Everything should be documented | ==== Everything should be documented ==== | ||
* standards for in-code documentation | * standards for in-code documentation | ||
Line 464: | Line 464: | ||
*** and 'Do Not Ask Over and Over The Same Boring Questions' (that's the lighter version, for sure!) | *** and 'Do Not Ask Over and Over The Same Boring Questions' (that's the lighter version, for sure!) | ||
== Drupal Mistakes, Programming | == Drupal Mistakes, Programming == | ||
* Programming Too Much | * Programming Too Much | ||
Line 471: | Line 471: | ||
* Working Alone | * Working Alone | ||
=== Programming Too Much | === Programming Too Much === | ||
* Avoiding Custom Programming with Fielded Data | * Avoiding Custom Programming with Fielded Data | ||
* Defining Theme Regions for Block Placement | * Defining Theme Regions for Block Placement | ||
=== Over-Executing Code | === Over-Executing Code === | ||
* Executing Code on Every Page Load | * Executing Code on Every Page Load | ||
* Using an Overly General Hook | * Using an Overly General Hook | ||
=== Saving PHP Code in the Database | === Saving PHP Code in the Database === | ||
* It's hacking | * It's hacking | ||
Line 488: | Line 488: | ||
** Hard to track | ** Hard to track | ||
==== Alternatives for php code in db | ==== Alternatives for php code in db ==== | ||
* Control '''block''' visibility | * Control '''block''' visibility | ||
** Use '''Context''' or '''Layout Builder''' modules, which allow much more '''flexibility on block placement''' | ** Use '''Context''' or '''Layout Builder''' modules, which allow much more '''flexibility on block placement''' | ||
Line 500: | Line 500: | ||
** Create your own custom field or formatter | ** Create your own custom field or formatter | ||
=== Working Alone | === Working Alone === | ||
* Participating in Groups and IRC | * Participating in Groups and IRC | ||
Line 506: | Line 506: | ||
* Contributing to the Drupal Community in Other Ways | * Contributing to the Drupal Community in Other Ways | ||
== Programming Examples | == Programming Examples == | ||
* Module skeleton | * Module skeleton | ||
Line 514: | Line 514: | ||
* Programming with Entities and Fields | * Programming with Entities and Fields | ||
=== Module skeleton | === Module skeleton === | ||
<small>'''Exercises:''' | <small>'''Exercises:''' | ||
* [[Drupal_8_for_Developers#1._Create_your_own_module|Create your own module]] | * [[Drupal_8_for_Developers#1._Create_your_own_module|Create your own module]] | ||
</small> | </small> | ||
=== Registering for URLs and Displaying Content | === Registering for URLs and Displaying Content === | ||
* Registering for a URL in Drupal 8 | * Registering for a URL in Drupal 8 | ||
Line 532: | Line 532: | ||
* Generating paged output | * Generating paged output | ||
==== Page callback, menu link | ==== Page callback, menu link ==== | ||
<small> | <small> | ||
Related D8 api/doc references: | Related D8 api/doc references: | ||
Line 547: | Line 547: | ||
</small> | </small> | ||
==== Block, plugin, annotation | ==== Block, plugin, annotation ==== | ||
<small> | <small> | ||
Related D8 api/doc references: | Related D8 api/doc references: | ||
Line 560: | Line 560: | ||
</small> | </small> | ||
=== Using the Drupal Form API | === Using the Drupal Form API === | ||
* Form Arrays, Form State Arrays, and Form State Objects | * Form Arrays, Form State Arrays, and Form State Objects | ||
Line 568: | Line 568: | ||
* Altering Forms | * Altering Forms | ||
=== Programming with Ajax in Drupal | === Programming with Ajax in Drupal === | ||
* Setting Up a Form for Ajax | * Setting Up a Form for Ajax | ||
Line 574: | Line 574: | ||
* Command-based Ajax Callback Functions in Drupal 8 | * Command-based Ajax Callback Functions in Drupal 8 | ||
==== Form, validation, submit, ajax | ==== Form, validation, submit, ajax ==== | ||
<small> | <small> | ||
<!-- TODO | <!-- TODO | ||
Line 587: | Line 587: | ||
</small> | </small> | ||
=== Programming with Entities and Fields | === Programming with Entities and Fields === | ||
* Terminology of Entities and Fields | * Terminology of Entities and Fields | ||
Line 597: | Line 597: | ||
* Programming with Field Formatters | * Programming with Field Formatters | ||
==== Entity, field | ==== Entity, field ==== | ||
<small> | <small> | ||
<!-- TODO | <!-- TODO | ||
Line 610: | Line 610: | ||
</small> | </small> | ||
== Programming Tools and Tips | == Programming Tools and Tips == | ||
* Where to Find More Information | * Where to Find More Information | ||
** Drupal Site Building and General Drupal Information | ** Drupal Site Building and General Drupal Information | ||
Line 622: | Line 622: | ||
** https://github.com/mglaman/drupal-check | ** https://github.com/mglaman/drupal-check | ||
=== Command liners | === Command liners === | ||
* '''Composer''' - packages manager, updater | * '''Composer''' - packages manager, updater | ||
** <small>''https://getcomposer.org/doc/''</small> | ** <small>''https://getcomposer.org/doc/''</small> | ||
Line 630: | Line 630: | ||
** <small>''https://drupalconsole.com/docs/en//''</small> | ** <small>''https://drupalconsole.com/docs/en//''</small> | ||
=== Other Useful Modules | === Other Useful Modules === | ||
* https://www.drupal.org/project/pingme | * https://www.drupal.org/project/pingme | ||
* https://www.drupal.org/project/drush9_example | * https://www.drupal.org/project/drush9_example | ||
=== Errors logging | === Errors logging === | ||
Enable logging all errors (only on testing envi!): | Enable logging all errors (only on testing envi!): | ||
/admin/config/development/logging | /admin/config/development/logging | ||
Line 649: | Line 649: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Exercises | == Exercises == | ||
=== 1. Create your own module === | === 1. Create your own module === |
Latest revision as of 12:08, 22 May 2024
THIS IS A DRAFT
This text may not be complete.
Drupal 8 for Developers
Drupal 8 for Developers Training Materials
Copyright Notice
Copyright © 2004-2023 by NobleProg Limited All rights reserved.
This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise.
Overview of Drupal
- What Is Drupal?
- Drupal Core
- Drupal Add-Ons: Modules, Themes, Distributions, and Translations
Overview Con't
Major subsystems in Drupal
- Routing, Menus
- Entities, Fields, Views, Forms
- Configuration, Plugins
- Theme system, Caching
- All Drupal APIs
- Related docs
Routing
- Handles interactions
- user (or system) accessing a certain path (or resource)
- translates into a route, which maps that resource to a flow
- returns a response back - success or graceful failure
- Replacement for hook_menu - Symfony Routing component
- Route mapped to a controller which renders drupal page
- Related Routing API
- Related docs
Menus
- Navigation
- provides details about how the site itself is organized
- keeps a structure of how content is related
- Provide APIs to generate, retrieve, and modify elements of the site structure
- Hierarchical - have a tree-like structure
- Related Menu API
- Related docs
Entities
- Powerful way of modeling data and content in Drupal
- Node, taxonomy, user, comment, file, media, etc - or our custom one
- Can have multiple bundles
- different variations of the same entity type
- can have different fields on them (while sharing some base fields)
- New type of entity - configuration
- exportable items, to be reused/shared in environments
- Related Entity API
- Related docs
Fields
- Entity bundles can have various fields
- Are responsible for holding data
- Two main types - base and configurable
- Base - defined in code for each entity type
- Configurable - usually created and configured in the UI
- attached to a bundle of that entity type
- exported via configuration
Fields con't
- Can also be of multiples types, depending on the data they store
- string (or text) fields, numeric, date, email, and so on
- We can create our own field types
- with its own data input widget
- and output formatter
- Related Field API
- Related docs
Views
- Listing content and data
- Allows the creation of configurable listings
- Includes - filters, sorts, display options, and many other features
- Extendable via custom plugins
- Related Views API
Forms
- Capture user input
- Powerful Form API
- securely and efficiently rendering and processing the submitted data
- abstraction over having to output our own form elements and deal with posted values
- allows to define our own form definition in OOP and handle validation and submission in a logical way
- Related Form API
- Related docs
Configuration
- Centralized configuration system
- Can be stored in: database (default), files in a specific dir, other storage back-ends
- Exportable into YAML files (to be re-imported later)
- Two kinds
- simple - always singular (one instance only) - site name, email address, etc
- complex - follows a certain schema and we can have multiple definitions - View, Entity, etc
- Related Config API
- Related docs
Plugins
- Encapsulating functionality - very widely used in D9
- Components of reusable code - used and managed by a central system
- System handles a task in a certain way (plugin X)
- other modules to provide different ways (plugin Y or Z).
- Opposite to entities: not used for data storage, but for functionality
- Discoverability - usually via
- Annotations - form of DocBlock comments, borrowed from the Doctrine library
- can describe classes(Drupal), methods, and even properties with certain metadata
- YAML files - for example menu links
- Annotations - form of DocBlock comments, borrowed from the Doctrine library
- Related Plugin API
- Related docs
The theme system
- Is spread out over Drupal core, modules, and themes
- Both modules and themes can theme data or content
- Best practice - to ensure that modules are able to theme their data
- themes can then come into play to style the output or override whats needed
- D8 moved to the open source Twig templating system (twig.symfony.com)
- separation of logic from a presentation layer
- makes front-end developers' jobs much easier and secured
- Related Theme API
- Related docs
Caching
- Improves the performance of building pages and rendering data
- Cache backend - stores the results of complex data calculations
- cache invalidation - something requires the calculations to be redone
- Render cache - wraps output with metadata that describes when the cache of that output needs to be invalidated
- Related Cache API
- Related docs
The Evolution of Drupal
Changes introduced in Drupal 8:
- improved the usability and accessibility of the administrative UI
- the administrative UI and the core themes work well on mobile devices
- procedural-code-based system transformed into a mostly object-oriented
- incorporates code from many outside open-source projects
- use and improve code from other projects where possible
- don't invent everything itself
- some of the core systems of Drupal were rewritten
- as portable PHP classes without Drupal-specific dependencies
- usable by other open-source projects
- incorporates code from many outside open-source projects
Changes in internal systems and APIs
- URL request handler from the Symfony
- Swappable services (also from Symfony)
- many aspects of Drupal core have been converted to services (allowing contributed modules to replace them)
- OO plugin system (from Symfony and Doctrine)
- many of the hooks have been converted to use plugins
- reduction of procedural code for more OO code
- Some informational hooks have been converted to use YAML-formatted files
- Storing configuration information has new API, easy to export and import
- to share configuration between sites,
- or store it in a revision control system (git, etc)
- Templating system from the Twig
- Class loader from the Composer
- Views in core, Poll out of the core, etc
Handling HTTP Requests
- The main Drupal index.php file is loaded and executed by the server to handle the request
- Drupal 8 uses Symfony to handle HTTP requests
- The dynamic class loader and error handlers are started (db connections, etc)
- Loading and executing of settings.php file (can be multi-site setup, additional sites.php file required)
- Drupal kernel starts up the Dependency Injection Container
- defines services provided by classes, configuration system requests
- Initialization of PHP session variables and cookies
- For anonymous users page cache is checked (doesn't apply to 'logged-in' users)
- Various files are loaded and executed (core include files and enabled modules' .module files)
- Symfony handles the req and returns result to browser
Symfony HTTP request system in Drupal 8
- Modules can register routes, which map URLs and conditions (context) to controller classes
- Controller classes: produce generic page output, present forms and process form submissions, etc
- The best match to the context is determined by Symfony and it chooses the controller method to execute
- Controller returns a Symfony response object containing HTTP header and content information
- Symfony returns the response information to browser
- event subscribers can be also registered by modules
- they can intercept and override lots of steps in response process
- examples of use in core modules
- translating URL aliases to system URL paths
- enabling maintenance mode
- handling language selection
- providing dynamic URL routes
Cache in Drupal
- System, which allows modules to precalculate data or output and store it
- the next time it is needed it doesn’t have to be calculated again
- saves a lot of time on page loads
- expense is more complexity
- when data is invalidated (changes in dependent data), any module that uses caching needs to clear it
Examples of cached information
- Page output for anonymous users (can be disabled on Performance config page)
- Block output (can't be turned off in Drupal 8)
- Data collected from hooks
- Lists with available plugins (plugin managers)
- Form arrays
- Theme information (list of theme regions, theme-related info from modules and themes)
Cache API in Drupal 8
- Services do the job
- Different sets of cached data (or bins) can use different storage mechanisms
- Allways call, to get correct cache class
\Drupal::cache($bin)
- Instance of cache class implements
\Drupal\Core\Cache\CacheBackendInterface
- Use '$container' as an alternative
// The name of the service for a particular cache bin is // 'cache.' . $bin. $cache_class = $container->get('cache.' . $bin);
- Available methods
- set(), get(), invalidate(), etc
Clearing caches
- When we use default core cache bins, it will be flushed automatically
- When custom cache system in use
- if cache clear is requested via 'drupal_flush_all_caches()' function
- flash data with custom 'hook_cache_flush()'
- custom 'hook_rebuild()' may be also needed
- if cache clear is requested via 'drupal_flush_all_caches()' function
Related D8 api/doc references:
Modules to enable: Examples for Developers, Cache example
Exercises:
- Clear cache with
- performance section in admin config
- webprofiler (additional module from devel package)
- drush
- in your own module via hook (we'll do it later in another exercise)
Tagging mechanism
- To flush parts of the cache when data they’re related to is changed or removed
- Example: module is caching data related to a specific node content item
- we tag it with the node ID
// $cid is a unique cache ID key for your data, which is $data. // $nid is the ID of the node whose data is cached. $cache_class->set($cid, $data, CacheBackendInterface::CACHE_PERMANENT, array('node:' . $nid));
- when calls to the core Node module modify this node content item, it will call cache methods to invalidate all cached data that was tagged with this node ID
- we tag it with the node ID
- Custom tags
\Drupal\Core\Cache\Cache::invalidateTags($tags);
- Example: module is caching data related to a specific node content item
Automatic Class Loading in Drupal
- Automatic loading of files containing PHP class, interface, and trait declarations
- Integrate with PHP's native class-auto-loading system (functions can be registered as class loaders)
- Reduce the load on the server by only loading files containing class definitions if those classes are being used in a specific page request
- Reduce the burden on developers by automatically loading files containing class definitions (instead of making them load the classes explicitly in code)
Drupal 8 Specific Way
- Incorporates the class loader from the Composer
- based on the PSR-0 and PSR-4 standards, and PHP namespaces.
- How does it work
- Classes are declared to be in a PHP namespace with a PHP namespace declaration
- Drupal uses namespaces beginning with the name \Drupal ,
- Namespace(s) for a specific module mymodule should begin with \Drupal\mymodule
- Files that depend on classes outside their own namespace have PHP use declarations for the outside classes
- Each class declaration is in its own file
- Drupal tells Symfony(to it's class loading system) about its namespace and directory conventions
- for instance: look only in enabled modules' src dirs (omit those disabled)
- If needed, class is defined via Symfony class loader, which automatically locates and loads the related include file
- The directory that each class file goes in depends on its namespace (examples in the next slide)
Dirs and namespaces, Examples
Relations between dirs and namespaces
- Add-on module’s class
'\Drupal\mymodule\subnamespace\Foo' will go in file in dir 'mymodule/src/subnamespace/Foo.php'
- 'core/lib' dir for core (but not part of specific D8 core module)
class '\Drupal\Component\Datetime\DateTimePlus' lives in file 'core/lib/Drupal/Component/Datetime/DateTimePlus.php'
- 'vendor/*' - classes adopted from outside projects
class '\Symfony\Component\DependencyInjection\Container' lives in file 'vendor/symfony/dependency-injection/Container.php'
Drupal 8 OOP Examples (Not Yet In D9, works only in D8)
Modules to enable: oop_examples, oop_design_patterns
Exercise:
Drupal Rules, Programming
- Alterability
- Separation of: Content, Configuration, State Data
- i18n (internationalization)
- Accessibility, Usability
- DB Independency (database)
- Security (all user-provided input is insecure)
- Tests, Documentation
Alterability
- Drupal core and contributed modules are almost fully alterable
- they provide mechanisms that you can use to customize and add to their behaviour and output
- The basic idea is that instead of editing downloaded code, you should create an add-on module or theme, which uses the existing alteration mechanisms
- Drupal Alteration Mechanisms
- Hooks
- Plugins
- Dependency Injection
- Symfony-based routing
- YAML-based menu
Drupal Hooks
- PHP file or function you can put into a module or theme
- Will be invoked (called or included) at an appropriate time by a Drupal core or contributed module
- in order to let your module or theme alter (or add to) behaviour and output
- Types: generic hooks, alter hooks, theme hooks (they use theme functions or template files .html.twig)
- Theme hooks: theme preprocessing hooks and theme processing hooks
Modules to enable: Examples for Developers, Hooks example
Exercises:
Drupal Plugins
- Replaced many of the generic hooks from Drupal 7 with an object-oriented system
- modules can add classes that define additions to Drupal behavior
Modules to enable: Examples for Developers, Plugin type example
Exercises:
Drupal Dependency Injection
- Allows modules to replace fundamental core systems of Drupal (or services)
- for example the mechanism for storing cached data
Drupal Routing
- Symfony-based routing system
- allows modules to define URLs and their output by defining routes and controllers
- allows to alter what happens at various points during an HTTP request by defining event subscribers
Drupal Links
- YAML-based system
- For defining
- default menu links
- contextual links
- and related data for the menu system
Drupal Module Themeable, Output
- Render arrays should be returned from all functions that return output
- these will be rendered by calls to internal functions
- the 'theme()' function does not exist in Drupal 8 for modules to call directly
- calling 'drupal_render()' directly is also discouraged
https://training-course-material.com/training/Drupal_8_Themes
https://www.drupal.org/node/2216195
render_example
theming_example
Separation of: Content, Configuration, State Data
- Content - Entities, Bundles, Fields, data in db
- Configuration - setups, YAML files, definitions
- State - stored in db, per system, temporary (dies with db)
- in D7 handled by variables
- common keys examples: system.cron_last, install_time
$pairs = \Drupal::state()->getMultiple($keys);
- Related docs
i18n (internationalization)
- Drupal translations - https://localize.drupal.org/
- Related i18n API
- Related docs
Accessibility, Usability
- Accessibility
- Usability
DB Independence (database)
- Related DB API
- Related docs
Security (all user-provided input is insecure)
- Related Security API
- Related docs
Tests, Documentation
- Should we bother at all, huh?
- Yes, everything should be tested
- Yes, everything should be documented
Everything should be tested
- SimpleTest, PHPUnit - both adopted in D8, 1st deprecated (new tests should be written with 2nd)
- All new changes in core must pass all of existing automated tests
- All new core functionalities or bug-fixes must have new tests
- Adopted also by lots of contributed modules (to some extent, at least)
Everything should be documented
- standards for in-code documentation
- each distinct code item (function, class, file, method, etc.) should include a documentation header
- later these documentation headers are parsed to create the Drupal API reference site (api.drupal.org)
- No change should be committed without its accompanying documentation being complete,
- critical-priority bug if documentation omitted
- Write README files, API documentation, and end-user documentation
- prevention for next users (of course also for us, The Blessed Creators) to 'know how'
- and 'Do Not Ask Over and Over The Same Boring Questions' (that's the lighter version, for sure!)
- prevention for next users (of course also for us, The Blessed Creators) to 'know how'
Drupal Mistakes, Programming
- Programming Too Much
- Over-Executing Code
- Saving PHP Code in the Database
- Working Alone
Programming Too Much
- Avoiding Custom Programming with Fielded Data
- Defining Theme Regions for Block Placement
Over-Executing Code
- Executing Code on Every Page Load
- Using an Overly General Hook
Saving PHP Code in the Database
- It's hacking
- Security risk
- Risk of bugs
- Hard to debug
- Hard to track
Alternatives for php code in db
- Control block visibility
- Use Context or Layout Builder modules, which allow much more flexibility on block placement
- Database queries
- Use Views core module
- Custom page or block output
- Create your block or page in a module
- Change how something is displayed
- Override a theme function or template
- Calculations for a field
- Create your own custom field or formatter
Working Alone
- Participating in Groups and IRC
- Reporting Issues and Contributing Code to the Drupal Community
- Contributing to the Drupal Community in Other Ways
Programming Examples
- Module skeleton
- Registering for URLs and Displaying Content
- Using the Drupal Form API
- Programming with Ajax in Drupal
- Programming with Entities and Fields
Module skeleton
Exercises:
Registering for URLs and Displaying Content
- Registering for a URL in Drupal 8
- mm.routing.yml, HiUnivController.php, mm.permissions.yml
- Providing administrative links
- mm.links.menu.yml, mm.links.task.yml, mm.links.action.yml
- to alter: hook_menu_links_discovered_alter(), hook_menu_local_tasks_alter(), hook_menu_local_actions_alter(), hook_menu_local_tasks()
- Altering Routes and Providing Dynamic Routes in Drupal 8
- mm.services.yml, mmRouting.php, mm.info.yml
- Registering a Block in Drupal 8
- Creating Render Arrays for Page and Block Output
- Generating paged output
Related D8 api/doc references:
Drupal 8 Interconnections example:
Modules to enable: Examples for Developers, Page example
Exercises:
Block, plugin, annotation
Related D8 api/doc references:
Modules to enable: Examples for Developers, Block Example
Exercises:
Using the Drupal Form API
- Form Arrays, Form State Arrays, and Form State Objects
- Basic Form Generation and Processing in Drupal 8
- Creating Confirmation Forms
- Adding Auto-Complete to Forms
- Altering Forms
Programming with Ajax in Drupal
- Setting Up a Form for Ajax
- Wrapper-based Ajax Callback Functions
- Command-based Ajax Callback Functions in Drupal 8
Form, validation, submit, ajax
Modules to enable: Examples for Developers, Form API Example
Exercises:
Programming with Entities and Fields
- Terminology of Entities and Fields
- Defining a Content Entity Type in Drupal 8
- Defining a Configuration Entity Type in Drupal 8
- Querying and Loading Entities in Drupal 8
- Defining a Field Type
- Programming with Field Widgets
- Programming with Field Formatters
Entity, field
Modules to enable: Examples for Developers, Config entity example, Content Entity Example, Field Example, Field Permission Example
Exercises:
Programming Tools and Tips
- Where to Find More Information
- Drupal Site Building and General Drupal Information
- Drupal Programming Reference and Background
- PHP Resources
- Database Resources
- Other Web Technology Resources
- Drupal Development Tools Devel
- Discovering Drupal API Functions and Classes
- Other Programming Tips and Suggestions
Command liners
- Composer - packages manager, updater
- Drush - enabling extensions, administering, generating code
- Drupal Console - like drush, but mostly for generating code
Other Useful Modules
Errors logging
Enable logging all errors (only on testing envi!):
/admin/config/development/logging
Edit settings.php and add at the end of it:
/**
*
* Enable all errors reporting
*
*/
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
Exercises
1. Create your own module
Make new folder modules/custom_
1.1. Config file
- Make another directory and name it custom_/mm (shortcut from 'my module')
- Provide some metadata about the project
- Create file mm/mm.info.yml
- Add entries in mm.info.yml as shown below
# Required 1 line name: My Own Module # 2 optional lines description: Here I will learn how to code with Drupal 8 package: Custom # Required 2 lines type: module core: 8.x # Version number. If not private module, then it will be added automatically by the packager on drupal.org version: VERSION
- Install the module
- What now? How can we fix it? (-:
1.2. Module file and access granting
- Create file mm/mm.module
- Put this code into it
<?php /** * @file * My own module. * * This file can be omitted if we do not need to define any functions or implement hooks * */
- Put this code into it
- Add file for permissions mm/mm.permissions.yml
# Permission example administer mm: title: 'Administer mm settings' description: 'If we really need more descriptions'
- Clear caches - or not (=
- Do we have to clear them now? Why?
1.3. Add WARNING description to permission from the previous exercise
- Use d8 api website to find the solution
- ..or search your drupal web folder (-;
Controller
- Create file mm/src/Controller/HiUnivController.php
<?php namespace Drupal\mm\Controller; use Drupal\Core\Controller\ControllerBase; class HiUnivController extends ControllerBase { /** * Display the markup. * * @return array */ public function hiuniv() { return array( '#type' => 'markup', '#markup' => $this->t('Hi, Universe!'), ); } }
Router
- Another file /mm/mm.routing.yml
mm.hiuniv: path: '/hiuniv' defaults: _controller: '\Drupal\mm\Controller\HiUnivController::hiuniv' _title: 'Hi Universe' requirements: _permission: 'access content'
Menu link
- Almost there /mm/mm.links.menu.yml
mm.admin: title: 'Hi universe simple page example' description: 'My module settings - link to hiuniv page' parent: system.admin_config_development route_name: mm.hiuniv weight: 99
Clearing Caches, playing around
- Why do we have to clear it now?
- What about the path, do you see anything interesting about it?
- Change it accordingly to other links in that section - clear caches again, what do we see now (or how) and why?
3. Drupal 8 OOP specifics
The module with examples works only in D8, not yet in D9
In D9+ download the module only and don't install it
3.1. Class
- Create simple module which will prepare OO skeleton for 3 specific "real life" things from your company/website, etc
- Use example 04 or 05
- Example of business scenario:
NobleProg Services Training Consultancy
3.2. Subclass with properties
- Extend the module from 3.1.
- Use example 06 and 07
- Example of business scenario:
- Define some static properties for one of your classes
Training Venue Number of delegates
- Provide a method to wrap one property within a string
Number of delegates "There will be 6 people on this training"
- Define some static properties for one of your classes
3.3. Interface
- Extend the module from 3.2.
- Use example 08 and 09
- Example of business scenario:
- Define the interface and getProperty() method (replace the Property with your business property name)
getVenue();
- Implement getProperty() method in both classes, one should return literal value, second should use property's value
Training getVenue() { ... } Consultancy getVenue() { ... }
- Define the interface and getProperty() method (replace the Property with your business property name)
4. Make your own drupal block
Plugin
- New file /mm/src/Plugin/Block/HiUnivBlock.php
<?php namespace Drupal\mm\Plugin\Block; use Drupal\Core\Block\BlockBase; /** * Provides a 'Hi Universe' Block. * * @Block( * id = "hiuniv_block", * admin_label = @Translation("Hi Universe block"), * ) */ class HiUnivBlock extends BlockBase { /** * {@inheritdoc} */ public function build() { return array( '#markup' => $this->t('Hi, Universe!'), ); } }
Clearing Caches
- What about now? Does it work straight away or not? Why?
5. Alter route, dynamic route
Router
- New file /mm/src/Routing/mmRouting.php
<?php /** * @file * Contains \Drupal\mm\Routing\mmRouting. */ namespace Drupal\mm\Routing; use Drupal\Core\Routing\RouteSubscriberBase; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; /** * Provides dynamic route and route alter. * */ class mmRouting extends RouteSubscriberBase { /** * {@inheritdoc} */ protected function alterRoutes(RouteCollection $collection) { // Alter the title of the mm module page (drupal/hiuniv). $route = $collection->get('mm.hiuniv'); $route->setDefault('_title', 'Altered title'); // Add a dynamic route at 'hiuniv/mm' $path = $route->getPath(); // Constructor parameters: path, defaults, requirements // like in a routing.yml file. $newroute = new Route($path . '/mm', array( '_controller' => '\Drupal\mm\Controller\HiUnivController::hiuniv', '_title' => 'New page title', ), array( '_permission' => 'administer_mm', )); $collection->add('mm.newroutename', $newroute); } }
Service
- New file /mm/mm.services.yml
# Service definitions for module. services: # Machine name of the service. mm.mm_routing: # Class providing the service. class: Drupal\mm\Routing\mmRouting # Service tags. This service is an event subscriber. tags: - { name: event_subscriber }
Final steps
- What about caches, huh?
- Do we follow here all the best practices?
- Correct what should be fixed, please (=
- Hint: router file can be better (-;
- Don't forget to change the service too (---;
- Reference: use 'token' module
- Correct what should be fixed, please (=
- Play with dynamic parameter ../mm
6. Create own form
Extend the mm module
Use drush gen form command, but only to see it's example in the command line
- do it with --dry-run option
6.1. Provide class form
- New file /mm/src/Form/mmForm.php
<?php /** * @file * Contains \Drupal\mm\Form\mmForm. **/ namespace Drupal\mm\Form; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; class mmForm extends FormBase { /** * {@inheritdoc} */ public function getFormId() { return 'mm_mm_form'; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { // Return array of Form API elements. $form['franchisee_name'] = array( '#type' => 'textfield', '#title' => $this->t('Franchisee name'), ); $form['submit'] = array( '#type' => 'submit', '#value' => $this->t('Save'), ); return $form; } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { // Validation required to satisfy interface } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { // Submit required to satisfy interface } }
- Update router /mm/mm.routing.yml
mm.form: path: '/mm-form' defaults: _title: 'mm form' _form: '\Drupal\mm\Form\mmForm' requirements: _access: 'TRUE'
- Update links /mm/mm.links.menu.yml
mm.form: title: 'Hi universe form' description: 'My own form' route_name: mm.form
Final bits
- Does it work? Why (the usual)? (=
- Rewrite it into mm.links.task.yml. How?
- Hint: Use pathauto module as a reference
6.2. Validate the form
Check if not empty and if it is less than 3 characters
6.3. Submit handler
Handle form submission
- Use messenger() and put Franchisee name in message via replacement variable (for example @frname) in t() function
6.4. Altering the form
Alter mm form with new email field ( use proper hook: hook_form_FORM_ID_alter() )
7. Custom Plugin
- Create a new plugin type that will work with units of measurement and conversions (ft to m, etc)
- Discuss the best way to solve it (-:
- Create a new plugin type related to your business
- You need: plugin manager, plugin interface, base class, and plugin definition.
- Implement it
8. Custom Field
- Fix me - in samples/licence_plate example
- Add licence plate custom field to Article content type
- Add new content of Article type
- Data is not saved for some reason - find out why
- In the section Manage form display (admin/structure/types/manage/article/form-display)
- HINT: use link core field to answer that (-:
- In the section Manage display (admin/structure/types/manage/article/display)
- There is no cog button there - fix it (How?)
- There is also missing short summary of format settings - add it
- HINT: use comment core field to find the answers (-:
- New file /mm/src/Plugin/Field/FieldType/RealName.php
<?php /** * @file * Contains \Drupal\mm\Plugin\Field\FieldType\RealName. */ namespace Drupal\mm\Plugin\Field\FieldType; use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\DataDefinition; /** * Plugin implementation of the 'realname' field type. * * @FieldType( * id = "realname", * label = @Translation("Real name"), * description = @Translation("This field stores a first and last name."), * category = @Translation("General"), * default_widget = "string_textfield", * default_formatter = "string" * ) */ class RealName extends FieldItemBase { /** * {@inheritdoc} */ public static function schema(\Drupal\Core\Field\FieldStorageDefinitionInterface $field_definition){ return array( 'columns' => array( 'first_name' => array( 'description' => 'First name.', 'type' => 'varchar', 'length' => '255', 'not null' => TRUE, 'default' => '', ), 'last_name' => array( 'description' => 'Last name.', 'type' => 'varchar', 'length' => '255', 'not null' => TRUE, 'default' => '', ), ), 'indexes' => array( 'first_name' => array('first_name'), 'last_name' => array('last_name'), ), ); } /** * {@inheritdoc} */ public static function propertyDefinitions(\Drupal\Core\Field\FieldStorageDefinitionInterface $field_definition) { $properties['first_name'] = \Drupal\Core\TypedData\DataDefinition::create('string')->setLabel(t('First name')); $properties['last_name'] = \Drupal\Core\TypedData\DataDefinition::create('string')->setLabel(t('Last name')); return $properties; } }
Add new field of type RealName to Article content type
- Create content Article - what can we say?
- Fix it!
- Hint: compare it with code from field_example
9. Hooks
- Write our custom hook_help()
- HINT: use token module as a reference
- Add custom token from our module
- HINT1: use hook_token_info() and hook_tokens()
- HINT2: use one of hook_token_info() implementations described in it's docs