ORANGECOLLAR/MODULE-WORDPRESS-INTEGRATION · ARCHITECTURE
Directory structure, dependency injection, and request flow for OrangeCollar_WordPressIntegration
Module Directory Structure
app/code/OrangeCollar/WordPressIntegration/
├── Controller/
│ ├── Router.php # Custom router (sortOrder 25)
│ ├── Blog/Index.php # Blog listing
│ ├── Post/View.php # Single post
│ ├── Category/View.php # Category archive
│ ├── Tag/View.php # Tag archive
│ ├── Author/View.php # Author archive
│ ├── Search/Results.php # Search results
│ └── Webhook/CachePurge.php # Webhook receiver
├── Model/
│ ├── Api/
│ │ ├── ClientInterface.php # API client interface
│ │ ├── Client.php # Guzzle implementation
│ │ └── Authentication.php # Header auth helper
│ ├── Cache/
│ │ ├── WordPressCache.php # Cache wrapper
│ │ └── CacheTag.php # Cache tag constants
│ ├── Repository/
│ │ ├── PostRepository.php
│ │ ├── CategoryRepository.php
│ │ ├── TagRepository.php
│ │ └── MenuRepository.php
│ ├── Config.php # All config access
│ ├── ContentProcessor.php # HTML processing pipeline
│ ├── Post.php # Post data model
│ ├── Category.php # Category data model
│ ├── Tag.php # Tag data model
│ └── Menu.php # Menu data model
├── Plugin/
│ ├── SeoPlugin.php # Injects WP SEO meta
│ └── BreadcrumbPlugin.php # Blog breadcrumbs
├── Observer/
│ └── LayoutLoadBefore.php # Adds layout handles
├── ViewModel/
│ ├── Post.php
│ ├── PostList.php
│ └── Breadcrumbs.php
├── Block/
│ ├── Widget/
│ │ ├── RecentPosts.php
│ │ ├── FeaturedPost.php
│ │ └── CategoryPosts.php
│ └── Sidebar/
│ ├── Categories.php
│ ├── Tags.php
│ ├── Archive.php
│ ├── Search.php
│ └── RecentPosts.php
├── Cron/
│ └── WarmCache.php
├── etc/
│ ├── di.xml # Global DI preferences
│ ├── frontend/di.xml # Frontend router registration
│ ├── adminhtml/system.xml # Admin config fields
│ ├── config.xml # Default config values
│ ├── crontab.xml # Cron schedules
│ ├── widget.xml # Widget declarations
│ └── events.xml # Event observers
└── view/frontend/
├── layout/ # Layout XML per handle
└── templates/ # Phtml templates Dependency Injection
Global DI (etc/di.xml)
<!-- API client preference -->
<preference for="OrangeCollar\WordPressIntegration\Model\Api\ClientInterface"
type="OrangeCollar\WordPressIntegration\Model\Api\Client" />
<!-- Repository preference -->
<preference for="OrangeCollar\WordPressIntegration\Model\Repository\PostRepositoryInterface"
type="OrangeCollar\WordPressIntegration\Model\Repository\PostRepository" />
<!-- Virtual logger -->
<virtualType name="OrangeCollar\WordPressIntegration\Logger\Handler"
type="Magento\Framework\Logger\Handler\Base">
<arguments>
<argument name="fileName" xsi:type="string">/var/log/orangecollar_wordpress.log</argument>
</arguments>
</virtualType> Frontend DI (etc/frontend/di.xml)
The custom router is registered here - it must be in the frontend scope DI config:
<type name="Magento\Framework\App\RouterList">
<arguments>
<argument name="routerList" xsi:type="array">
<item name="wordpress" xsi:type="array">
<item name="class" xsi:type="string">OrangeCollar\WordPressIntegration\Controller\Router</item>
<item name="disable" xsi:type="boolean">false</item>
<item name="sortOrder" xsi:type="string">25</item>
</item>
</argument>
</arguments>
</type> Request Flow
- Browser requests
/blog/my-post-slug - Magento’s router list runs in sortOrder
- At sortOrder 25,
Router::match()checks if the URL starts with the configured base path - Router parses the path and sets module, controller, action, and params on the request
- Magento dispatches to the resolved controller (e.g.,
Post\View) LayoutLoadBeforeobserver adds the layout handle (e.g.,orangecollar_post_view)- Controller fetches data via the repository, which calls the API client and manages caching
- Blocks and ViewModels render the content via layout XML and templates
Observer Pattern
The LayoutLoadBefore observer listens to the layout_load_before event and adds layout handles dynamically:
// Generates handles like: orangecollar_post_view, orangecollar_blog_index $handle = 'orangecollar_' . $moduleName . '_' . $actionName; $layout->getUpdate()->addHandle($handle);
This allows per-page-type layout customization without controller inheritance complexity.
Data Model Pattern
All data models are immutable value objects:
final class Post
{
public function __construct(
public readonly int $id,
public readonly string $slug,
public readonly string $title,
// ... other properties
) {}
public static function fromApiResponse(array $data): self
{
return new self(
id: $data['id'],
slug: $data['slug'],
title: $data['title']['rendered'],
// ... map other fields
);
}
} The fromApiResponse() factory handles the WP REST API response structure, including embedded author and term data from _embedded.