This repository is a proof of concept (PoC) for studying Drupal 11, focused on progressive exercises covering everything from the most basic concepts to advanced framework features.
The goal is not to build a final product, but to explore and document each layer of Drupal in practice, building a real understanding of how the CMS works under the hood.
- Drupal: 11.x
- PHP: 8.4
- Database: MariaDB 11.8
- Server: nginx-fpm via DDEV
- Local URL: http://drupal-treino-b.ddev.site
The project simulates a fictional pet shop website, used as a practical context for the exercises. The public-facing scope includes:
- Home: hero section, Shop by Pet category buttons, frontpage listing
- Shop: product listing page filtered by pet type and badge
- Product page: detailed product description
- Blog: article listing (
/blog) and article detail pages - Login journey: login, register and password reset pages
- Footer: global layout component across all breakpoints
- Wishlist: product wishlist page
- 404: custom error page (in progress)
The scope expands as new topics are covered in the exercises.
- Environment setup and configuration with DDEV
- Drupal directory structure and essential files
- Content Types and Fields
- Taxonomies and vocabularies
-
Pet Typevocabulary with image field — used to drive Shop by Pet category buttons
-
- Menus and navigation links
- Blocks: creation, placement and visibility
- Custom Block plugins (
@Blockannotation) — Hero block, Shop by Pet block - Block rendering a Node entity via
EntityTypeManager+ view builder
- Custom Block plugins (
- Users, roles and permissions
-
Content Editorrole — can create/edit/delete products, hero, articles and media -
Customerrole — authenticated read-only access
-
- Admin theme with Gin
- Views: listing pages, filters and displays
- Journal listing — page display at
/blog, fields-based rows, sorted by date desc - Shop listing — page display at
/shop, exposed filters by pet type and badge - Frontpage — promoted content view with bundle filter excluding articles
- Journal listing — page display at
- Config API: export and import configuration with
cex/cim - Custom module creation (hook_theme, routing, controller)
-
hook_theme— custom template registration withtemplateandpathkeys - routing — via plugin system (Block)
- controller — page route with render array
-
- Form API
- Custom theme with Starterkit
- Twig: templates, variables, filters and functions
- Preprocessors (
hook_preprocess_*)-
hook_preprocess_node()— exposes formatted date, reading time, author name and category label to article templates
-
- Asset libraries (
libraries.yml,#attached) - Responsiveness: media queries in custom theme CSS, mobile/tablet/desktop breakpoints
- All public pages covered across desktop (1440), tablet (768) and mobile (390)
- Basic migrations with Migrate API
- Services and Dependency Injection (Service Container)
- Plugins: custom block, field and formatter types
- Block plugin —
@Blockannotation,build()returning render array - Custom field — FieldType plugin
- Custom formatter — FieldFormatter plugin
- Block plugin —
- Event Subscribers and hooks via classes
- Queue API and background processing
- Cache API: cache tags, contexts and invalidation
- Node cache tags (
[node:X]) applied in article detail preprocessor — page invalidates on node save - Custom cache contexts
- Manual cache invalidation via
cache_tags.invalidator
- Node cache tags (
- REST API and JSON:API
- Native JSON:API — products stored in Drupal, consumed via
/jsonapi/node/waggy_productin the Shop by Pet block; includes taxonomy term images resolved throughmedia → file → urirelationship chain - Guzzle HTTP client (server-side) — used internally to query Drupal's own JSON:API from a Block plugin via
\Drupal::httpClient() - Guzzle consuming external API — fetch from an external backend; API key protected server-side, URL never exposed to the browser
- JavaScript fetch (client-side) — filter products without page reload; URL exposed to the browser
- Native JSON:API — products stored in Drupal, consumed via
- Paragraphs and layouts with Layout Builder
-
drupal/paragraphsinstalled and configured —text_blockparagraph type validated - Paragraph types wired to footer columns via custom Block plugin (footer_brand, footer_links, footer_newsletter)
-
- Advanced Migrate API: migrations with complex transformations
- Automated testing with PHPUnit and Nightwatch
- Core auth forms — override via
hook_form_alter()(login, register, password reset) - OAuth 2.0 —
simple_oauthmodule; authorization code flow for third-party clients - JWT — JSON Web Tokens for headless/decoupled authentication
- SSO — Single Sign-On via SAML 2.0 (
simplesamlphp_auth) or OpenID Connect - Two-factor authentication — TOTP via
tfamodule - Rate limiting and brute force protection —
floodAPI,captchamodule - HTTPS, CSRF protection, Content Security Policy headers
- Password policies —
password_policymodule; complexity rules per role
See docs/deploy/cicd-architecture.md for the full pipeline design, architectural decisions and AI spec.
- Environment config split —
config_splitmodule for dev/staging/prod differences -
settings.phpper environment — database, trusted hosts, reverse proxy config - Drush deploy script —
updatedb,cim,cr,deployhook - GitHub Actions — CI Basic: lint + build on every push to
feat/*,refactor/*,chore/*— fast feedback, ~30s - GitHub Actions — CI Full: complete quality gate (lint + audit + PHPCS + PHPStan + PHPUnit + SonarCloud) — runs on PR approval
- Branch protection —
mainimmutable, merge only via approved PR - Staging deployment — Pantheon Dev via SSH git push on every snapshot build
- Production deployment — Pantheon Live via release tag (wired up, not yet deployed)
- Production hardening — file permissions,
settings.phpwrite-protect, error reporting off - Composer in production —
--no-dev, lockfile pinned, patches applied - Rollback strategy — config revert, database snapshot before deploy
Every build is tagged. The tag type signals the pipeline stage and what environments are allowed to receive it.
| Stage | Trigger | Tag pattern | Example | Target |
|---|---|---|---|---|
| Push to feature branch | automatic | {last-release}-SNAPSHOT.{sha} |
0.1.0-SNAPSHOT.a1b2c3 |
Pantheon Dev |
Merge to main |
automatic (on push to main) | {major}.{minor}.{patch} |
0.2.0 |
Homolog → Prod |
Bump type is controlled by a label added to the PR before merging:
| Label | Effect | Use when |
|---|---|---|
release:patch |
0.1.0 → 0.1.1 |
Bug fixes, docs, chores |
release:minor |
0.1.0 → 0.2.0 |
New features, backwards-compatible |
release:major |
0.1.0 → 1.0.0 |
Breaking changes |
If no label is set, the default is patch.
Rules:
- SNAPSHOT tags are never deployed to production — they identify unstable builds
- Only clean semver tags (
x.y.z) reach Homolog and Prod - The bump decision is recorded in the PR history as a label, making it auditable
Progress logs summarizing what was built and learned each week, written for professional context.
ddev start
ddev drush si --existing-config # install with existing config
ddev drush cr # clear cacheAccess: http://drupal-treino-b.ddev.site
See scripts/SCRIPTS.md for the full reference of Drush automation scripts and git hooks — including preconditions, side effects, risk levels and AI agent guidance.