Architecture
Module Directory Structure
Section titled “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 templatesDependency Injection
Section titled “Dependency Injection”Global DI (etc/di.xml)
Section titled “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)
Section titled “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
Section titled “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
Section titled “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
Section titled “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.