<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: HomelessCoder</title>
    <description>The latest articles on DEV Community by HomelessCoder (@homeless-coder).</description>
    <link>https://dev.to/homeless-coder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3522575%2F17fb8c99-8006-422b-91c7-788821632278.png</url>
      <title>DEV Community: HomelessCoder</title>
      <link>https://dev.to/homeless-coder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/homeless-coder"/>
    <language>en</language>
    <item>
      <title>Building a Blueprint Marketplace for Reusable Project Structures</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Tue, 07 Apr 2026 21:07:20 +0000</pubDate>
      <link>https://dev.to/homeless-coder/building-a-blueprint-marketplace-for-reusable-project-structures-5e7l</link>
      <guid>https://dev.to/homeless-coder/building-a-blueprint-marketplace-for-reusable-project-structures-5e7l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Flexible platforms create repeated modeling work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I saw that clearly while building &lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;Omnismith&lt;/a&gt;. Users can model their own business domains with templates, attributes, references, and entities. Many of those domains still start from similar structures.&lt;/p&gt;

&lt;p&gt;Product catalogs, asset tracking, server monitoring, and CRM-style workflows use different labels and fields. They still reuse many of the same structural ideas.&lt;/p&gt;

&lt;p&gt;That observation led to the Blueprint Marketplace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6dlriuaslfltozmp6jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6dlriuaslfltozmp6jq.png" alt=" " width="800" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeated domain work
&lt;/h2&gt;

&lt;p&gt;Flexible platforms give users room to model their own systems. They also push a lot of design work onto each new workspace.&lt;/p&gt;

&lt;p&gt;When the same structures are rebuilt in one workspace after another, that is repeated setup cost.&lt;/p&gt;

&lt;p&gt;It also keeps useful ideas trapped inside isolated projects.&lt;/p&gt;

&lt;p&gt;If one team has already designed a strong starting structure for a product catalog, a monitoring workspace, or a roadmap tracker, the product should make that reusable. Otherwise every new project starts by rediscovering work that was already done.&lt;/p&gt;

&lt;p&gt;Omnismith needed a way to turn repeated domain modeling into reusable project structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Template Marketplace" was not enough
&lt;/h2&gt;

&lt;p&gt;The first internal name for this was Template Marketplace.&lt;/p&gt;

&lt;p&gt;The name was too narrow.&lt;/p&gt;

&lt;p&gt;A template is only one piece of what makes a project usable. A real starting point usually includes several templates, multiple attributes, references between models, and a set of decisions about how those pieces fit together. Publishing templates alone would expose fragments. I needed a way to package and publish a coherent project structure.&lt;/p&gt;

&lt;p&gt;That is why the concept became Blueprint Marketplace.&lt;/p&gt;

&lt;p&gt;The term “blueprint” covered the larger unit more accurately. It described a reusable structure that could be installed into another project and still make sense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmja50icsozk9u3vr5vg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmja50icsozk9u3vr5vg.png" alt=" " width="800" height="1261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The publication boundary mattered early
&lt;/h2&gt;

&lt;p&gt;The most important constraint was safety.&lt;/p&gt;

&lt;p&gt;I wanted users to publish reusable structures. I also needed a clear guarantee that the marketplace would not expose actual project records.&lt;/p&gt;

&lt;p&gt;That boundary shaped the model early.&lt;/p&gt;

&lt;p&gt;User-published blueprints carry reusable definitions. They do not carry live business data. Featured blueprints are curated and reviewed before they appear in onboarding or other product-controlled surfaces.&lt;/p&gt;

&lt;p&gt;That distinction made the marketplace usable as both a user-facing feature and an internal product distribution channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  The implementation started from an existing example
&lt;/h2&gt;

&lt;p&gt;The Product Catalog dataset had already been written in code with reuse in mind.&lt;/p&gt;

&lt;p&gt;That reduced the amount of conceptual redesign. The real work was everything around it: persistence, publication flow, API support, and UI support.&lt;/p&gt;

&lt;p&gt;I started with console support because it was the fastest way to exercise the publishing model and test the rules around what a blueprint could contain. That gave the rest of the implementation a clearer contract.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu803elfouov13s4e0efi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu803elfouov13s4e0efi.png" alt=" " width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this changes the product
&lt;/h2&gt;

&lt;p&gt;The Blueprint Marketplace changes how Omnismith can evolve as a platform.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reusable structures can be installed into new projects&lt;/li&gt;
&lt;li&gt;useful domain knowledge becomes portable inside the product&lt;/li&gt;
&lt;li&gt;curated official blueprints can guide important product flows&lt;/li&gt;
&lt;li&gt;onboarding and starter experiences can reuse the same underlying publication system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once reusable blueprints exist as a first-class capability, onboarding does not need to depend on special-case demo logic forever. The same system that supports marketplace publishing can also support official starter projects and curated first-session paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The Blueprint Marketplace started from a simple product observation: flexible platforms still accumulate repeated domain work.&lt;/p&gt;

&lt;p&gt;I did not want Omnismith users to keep rebuilding the same project structures in isolation. I wanted those structures to become publishable, installable, and reusable inside the product itself.&lt;/p&gt;

&lt;p&gt;That made the marketplace worth building on its own. It also created the foundation for the onboarding work that came after it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>startup</category>
      <category>ux</category>
    </item>
    <item>
      <title>Designing Onboarding for a Flexible Data Platform</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Sat, 21 Mar 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/homeless-coder/designing-onboarding-for-a-flexible-data-platform-2j32</link>
      <guid>https://dev.to/homeless-coder/designing-onboarding-for-a-flexible-data-platform-2j32</guid>
      <description>&lt;p&gt;Flexible data products have a predictable onboarding problem. The empty state is technically correct. It also hides the product model.&lt;/p&gt;

&lt;p&gt;That was the issue in Omnismith's original onboarding flow. New users were shown the interface, then dropped into a project with no templates, no attributes, and no entities. The tutorial explained the controls, but it did not show how a real workspace should be structured.&lt;/p&gt;

&lt;p&gt;Users could follow individual actions. They still lacked a working mental model of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blank-canvas problem
&lt;/h2&gt;

&lt;p&gt;Omnismith is flexible by design. Users define templates, attributes, references, and entities to fit their own domain.&lt;/p&gt;

&lt;p&gt;That flexibility creates a cost during the first session. A new user has to understand both the interface and the shape of a finished system.&lt;/p&gt;

&lt;p&gt;The original onboarding handled the first part and missed the second.&lt;/p&gt;

&lt;p&gt;A flexible product is easier to understand when users can inspect a coherent example. Omnismith's onboarding gap came from missing context around the model and the workspace structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the first session needed
&lt;/h2&gt;

&lt;p&gt;The onboarding flow needed to expose the model immediately.&lt;/p&gt;

&lt;p&gt;That meant starting with a project that contained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a concrete schema&lt;/li&gt;
&lt;li&gt;example records&lt;/li&gt;
&lt;li&gt;visible relationships between records&lt;/li&gt;
&lt;li&gt;enough structure to make browsing and editing feel like real work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example also needed to stay small and coherent while still showing how the parts fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first redesign: a seeded demo project
&lt;/h2&gt;

&lt;p&gt;The first redesign used a seeded Product Catalog project.&lt;/p&gt;

&lt;p&gt;The dataset was intentionally small, but structurally complete enough to teach the model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 templates: Category, Brand, Product&lt;/li&gt;
&lt;li&gt;11 attributes across those templates&lt;/li&gt;
&lt;li&gt;shared fields such as &lt;code&gt;Name&lt;/code&gt; and &lt;code&gt;Description&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;references from Product to Category and Brand&lt;/li&gt;
&lt;li&gt;a self-reference for related products&lt;/li&gt;
&lt;li&gt;19 sample entities with realistic values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was enough to make the system legible. A user could open the project and immediately see that templates define structure, entities hold values, and references connect records into something closer to a real application than an empty workspace does.&lt;/p&gt;

&lt;p&gt;That scope was deliberate. The dataset's job was to make the product model visible within the first few minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the guided tour still mattered
&lt;/h2&gt;

&lt;p&gt;The demo project provided context, and the guided tour directed attention.&lt;/p&gt;

&lt;p&gt;Users had a concrete workspace to inspect, and the tour moved them through the important surfaces in the right order.&lt;/p&gt;

&lt;p&gt;Together, they reduced the time between first login and first useful understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this solved, and what it did not
&lt;/h2&gt;

&lt;p&gt;This redesign fixed the immediate blank-canvas failure. New users no longer had to infer the product model from an empty screen.&lt;/p&gt;

&lt;p&gt;It did not solve every onboarding problem.&lt;/p&gt;

&lt;p&gt;One seeded dataset is still one opinionated starting point. It helps users understand the system, but it also assumes that one example should be relevant to everyone. That later became the next onboarding problem to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For flexible products, an empty workspace is a poor first lesson.&lt;/p&gt;

&lt;p&gt;Omnismith became easier to understand after onboarding began with a small working system. Clear visibility into the product model improved first-session comprehension.&lt;/p&gt;

&lt;p&gt;That was the first onboarding redesign. The next question was how to keep that clarity without forcing every user into the same example.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>showdev</category>
      <category>ux</category>
    </item>
    <item>
      <title>Why Omnismith Uses Flexible Schema for Operational Data</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Thu, 19 Mar 2026 07:30:00 +0000</pubDate>
      <link>https://dev.to/homeless-coder/why-omnismith-uses-flexible-schema-for-operational-data-4gg1</link>
      <guid>https://dev.to/homeless-coder/why-omnismith-uses-flexible-schema-for-operational-data-4gg1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Omnismith was built around a simple constraint: many operational systems need different schemas, but the same platform capabilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Many operational systems have different schemas but the same platform requirements.&lt;/p&gt;

&lt;p&gt;A product catalog needs categories, brands, and products. An infrastructure workspace needs servers, services, incidents, and metrics. An IT asset system needs contracts, devices, licenses, and owners.&lt;/p&gt;

&lt;p&gt;The fields are different. The surrounding platform needs are usually the same.&lt;/p&gt;

&lt;p&gt;You still need structured records, permissions, history, dashboards, imports, API access, and, in some cases, time-series data.&lt;/p&gt;

&lt;p&gt;That is the problem Omnismith was built to address.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Repeated Problem
&lt;/h2&gt;

&lt;p&gt;The core issue is not that teams need databases. The issue is that they repeatedly need systems built around different data models while the surrounding platform requirements stay mostly stable.&lt;/p&gt;

&lt;p&gt;The implementation pattern is familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define a schema&lt;/li&gt;
&lt;li&gt;build CRUD screens&lt;/li&gt;
&lt;li&gt;add permissions&lt;/li&gt;
&lt;li&gt;add history&lt;/li&gt;
&lt;li&gt;add imports and exports&lt;/li&gt;
&lt;li&gt;add charts or reporting&lt;/li&gt;
&lt;li&gt;expose the result through an API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That work is manageable when the schema is stable and the system is narrow.&lt;/p&gt;

&lt;p&gt;It becomes expensive when the structure changes often or when the same product needs to support several operational domains. In those cases, too much work goes into rebuilding the platform around the schema instead of working on the domain itself.&lt;/p&gt;

&lt;p&gt;This is common in internal tools, operational backoffice systems, monitoring workspaces, and domain-specific SaaS products. The schema changes with the business model, but the surrounding platform needs remain familiar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Flexible Schema Is The Core Model
&lt;/h2&gt;

&lt;p&gt;Omnismith uses an Entity-Attribute-Value model so the structure itself can be managed as data.&lt;/p&gt;

&lt;p&gt;In practical terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;attributes define fields&lt;/li&gt;
&lt;li&gt;templates define structure&lt;/li&gt;
&lt;li&gt;entities store actual records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That allows a workspace to define its own schema without requiring code changes for every new field or template.&lt;/p&gt;

&lt;p&gt;If a team needs a &lt;code&gt;Server&lt;/code&gt; template with hostname, CPU cores, environment, and owner, that structure can be created directly in the product. If another team needs &lt;code&gt;Supplier&lt;/code&gt;, &lt;code&gt;Contract&lt;/code&gt;, and &lt;code&gt;Renewal Date&lt;/code&gt;, it can define a different model in the same system.&lt;/p&gt;

&lt;p&gt;The point is not schema flexibility for its own sake. The point is to reduce the cost of building and maintaining operational systems whose structure changes over time.&lt;/p&gt;

&lt;p&gt;This changes the development boundary. Instead of treating every schema adjustment as an application change, the product can treat structure as workspace data and handle the surrounding operational behavior in a consistent way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Dynamic Schema Alone Is Not Enough
&lt;/h2&gt;

&lt;p&gt;Flexible schema alone is not enough for the types of systems Omnismith is meant to support.&lt;/p&gt;

&lt;p&gt;Operational data usually needs more than field definition and record editing.&lt;/p&gt;

&lt;p&gt;It also needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;history, so changes are traceable&lt;/li&gt;
&lt;li&gt;role-based access control, so access is scoped properly&lt;/li&gt;
&lt;li&gt;API-first access, so the workspace can be integrated into other systems&lt;/li&gt;
&lt;li&gt;time-series support, so metrics can be attached to entities instead of stored somewhere else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination defines the actual product boundary.&lt;/p&gt;

&lt;p&gt;Omnismith is meant for systems where the schema is flexible, but auditability and operational visibility still matter.&lt;/p&gt;

&lt;p&gt;That is also why the product is opinionated in a few specific directions. Changes are tracked. Access is scoped. API access is a first-class interface rather than an afterthought. Metrics can be stored against the same entities that define the operational model.&lt;/p&gt;

&lt;p&gt;A server, sensor, contract, or product record does not need a separate system just because one part of its state changes over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Use Cases
&lt;/h2&gt;

&lt;p&gt;This model fits several kinds of structured operational work particularly well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IT asset management&lt;/li&gt;
&lt;li&gt;server and service monitoring&lt;/li&gt;
&lt;li&gt;product and catalog management&lt;/li&gt;
&lt;li&gt;consent and compliance tracking&lt;/li&gt;
&lt;li&gt;internal backoffice systems with changing data models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common pattern is not the domain itself. The common pattern is that each use case needs structured records together with operational behavior around those records.&lt;/p&gt;

&lt;p&gt;An IT asset workspace needs devices, owners, contracts, warranties, and licenses. A monitoring workspace needs services, incidents, servers, and metric history. A catalog needs products, categories, pricing, stock state, and references. These are different models, but they benefit from the same platform shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope
&lt;/h2&gt;

&lt;p&gt;Omnismith is not trying to be a universal replacement for every spreadsheet or content tool.&lt;/p&gt;

&lt;p&gt;The intended scope is narrower and more practical: structured operational data that changes over time, needs clear access control, and benefits from being exposed through an API.&lt;/p&gt;

&lt;p&gt;That is why flexible schema is combined with time-series support, full history, dashboards, RBAC, and API-driven workflows. Those capabilities make the schema model useful in day-to-day operation rather than only convenient during setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Omnismith was built for teams that need to model structured operational data without rebuilding the surrounding platform every time the schema changes.&lt;/p&gt;

&lt;p&gt;Flexible schema is the core mechanism. History, permissions, API access, and time-series support are what make that mechanism useful in practice.&lt;/p&gt;

&lt;p&gt;That is the product boundary: not only storing custom fields, but supporting the operational systems built on top of them.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>showdev</category>
      <category>software</category>
    </item>
    <item>
      <title>Building ng-beacon: a lightweight guided tour library for Angular</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Wed, 11 Mar 2026 08:17:00 +0000</pubDate>
      <link>https://dev.to/homeless-coder/building-ng-beacon-a-lightweight-guided-tour-library-for-angular-3j8i</link>
      <guid>https://dev.to/homeless-coder/building-ng-beacon-a-lightweight-guided-tour-library-for-angular-3j8i</guid>
      <description>&lt;p&gt;I was working on &lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;Omnismith&lt;/a&gt; and needed a decent product tour. That sounded like a solved problem, so I went looking for an Angular package I could install and forget about.&lt;/p&gt;

&lt;p&gt;There were usable options. Some were solid. But none fit the shape I wanted for this app: no manual scripts or stylesheets, no generic abstraction I had to translate back into Angular, and nothing that felt awkward in a &lt;strong&gt;zoneless Signals-based&lt;/strong&gt; app.&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;

&lt;p&gt;It's called &lt;a href="https://www.npmjs.com/package/ng-beacon" rel="noopener noreferrer"&gt;&lt;code&gt;ng-beacon&lt;/code&gt;&lt;/a&gt;: a lightweight guided tour library for Angular 19+, built around Signals, SVG spotlight overlays, and a deliberately small setup.&lt;/p&gt;

&lt;p&gt;This post is less about “I published a package” and more about the constraints that shaped it, which parts turned out to matter, and how the API changed once it had to survive real application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint Was Simplicity
&lt;/h2&gt;

&lt;p&gt;I didn't want a tour system that became its own mini-framework. I wanted something that looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;provideBeacon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beaconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;STEPS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the baseline: no Angular Material dependency, no CDK requirement, no big configuration surface, no assumptions about which translation library a consumer uses, and no architectural gravity that would make the feature feel more expensive than it should.&lt;/p&gt;

&lt;p&gt;There are good generic tour libraries. I just didn't want to adapt a generic engine into Angular-shaped application code if I could avoid it. That constraint ended up shaping almost every decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Parts That Were Actually Fun to Build
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/HomelessCoder/ng-beacon/raw/main/assets/output.gif" rel="noopener noreferrer"&gt;https://github.com/HomelessCoder/ng-beacon | Demo GIF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started with an SVG spotlight overlay because it gave me clean geometry and smooth control over the cutout. That part worked early. The more interesting problems were everything around it.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Click blocking around the spotlight
&lt;/h3&gt;

&lt;p&gt;The SVG handled the visual spotlight nicely, but I still wanted the interaction model to be explicit.&lt;/p&gt;

&lt;p&gt;The highlighted area may need to stay interactive while the rest of the screen still behaves like an overlay. I could have pushed all of that into the SVG layer, but I preferred to separate the visual layer from the interaction layer.&lt;/p&gt;

&lt;p&gt;So I used dynamic click-blocking &lt;code&gt;div&lt;/code&gt;s around the spotlight rectangle. Instead of one full-screen blocker doing everything, the page gets four blocker regions around the highlighted area.&lt;/p&gt;

&lt;p&gt;It is not the only way to solve it, but it made pointer behavior much more explicit and predictable.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tooltip positioning that reacts to real size
&lt;/h3&gt;

&lt;p&gt;The tooltip height changes from step to step. Position changes too. Which means you can't just hardcode offsets and hope for the best.&lt;/p&gt;

&lt;p&gt;So the positioning logic measures the tooltip, calculates where it wants to be, and then clamps it to the viewport edges. That sounds small on paper, but it is the difference between “works in the demo” and “feels stable while resizing the window and moving between steps.”&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Iterating on the motion until it stopped feeling mechanical
&lt;/h3&gt;

&lt;p&gt;I also spent time on the transitions. Nothing dramatic. Just enough to make the spotlight, blockers, and tooltip move like they belong to the same system instead of three separate UI layers reacting independently.&lt;/p&gt;

&lt;p&gt;That polish mattered more than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First API Was Too Simple
&lt;/h2&gt;

&lt;p&gt;My first API was basically this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BeaconStep&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for simple cases, that is still fine. But real application code pushed it pretty quickly. Some tour steps only make sense while a specific component exists, so the library needed a way for components to contribute their own steps instead of keeping everything in one central list.&lt;/p&gt;

&lt;p&gt;That led to &lt;code&gt;registerTourSteps()&lt;/code&gt; and, from there, to &lt;code&gt;startContextTour()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;_tour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;registerTourSteps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DASHBOARD_STEPS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beaconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startContextTour&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that point the API made more sense. Components owned their local tour context, and the running tour could stay aligned with the actual UI. If a component disappeared during navigation, its steps disappeared with it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;start(steps)&lt;/code&gt; stayed as the simple option. &lt;code&gt;startContextTour()&lt;/code&gt; became the better option for tours that need to follow real application state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internationalization Without Coupling
&lt;/h2&gt;

&lt;p&gt;Omnismith supports eight languages, so I knew very early that hardcoded English strings inside the tour UI were not going to survive long.&lt;/p&gt;

&lt;p&gt;At the same time, I didn't want the library coupled to a specific translation library.&lt;/p&gt;

&lt;p&gt;In Omnismith I use &lt;code&gt;ngx-translate&lt;/code&gt;. Someone else might use Transloco. Someone else might want their own function. So instead of picking a winner, I exposed a helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;provideBeaconTranslateFn&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TranslateService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That handles both step content and built-in UI labels like close / next / previous. Consumers get flexibility without the package forcing one i18n stack on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests Helped Me Stop Guessing
&lt;/h2&gt;

&lt;p&gt;I added tests with AI assistance. What mattered was covering the behavior that was easy to break while iterating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;step filtering&lt;/li&gt;
&lt;li&gt;navigation behavior&lt;/li&gt;
&lt;li&gt;focus movement and restoration&lt;/li&gt;
&lt;li&gt;overlay rendering&lt;/li&gt;
&lt;li&gt;tooltip positioning branches&lt;/li&gt;
&lt;li&gt;label translation and configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those were in place, I could keep refining the code without mentally re-running the same manual checks every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Final Result Looks Like
&lt;/h2&gt;

&lt;p&gt;The setup stayed small, which was the whole point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideBeacon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ng-beacon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;provideBeacon&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if (beaconService.isActive()) {
  &lt;span class="nt"&gt;&amp;lt;beacon-overlay&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Direct tour&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beaconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MY_TOUR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Component-scoped tour&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beaconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startContextTour&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you need the more structured path, that is there too with &lt;code&gt;registerTourSteps&lt;/code&gt;, &lt;code&gt;startContextTour&lt;/code&gt;, and &lt;code&gt;provideBeaconTranslateFn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It also has two small lifecycle events now, &lt;code&gt;finished&lt;/code&gt; and &lt;code&gt;dismissed&lt;/code&gt;, which turned out to be useful for simple tracking and analytics.&lt;/p&gt;

&lt;p&gt;That mattered to me more than I expected. I didn't just want a tour overlay. I wanted something that fit naturally into Angular application code, including translation, component ownership, reactive lifecycle handling, and app-level integration points.&lt;/p&gt;

&lt;p&gt;That was the real target from the start: &lt;strong&gt;simple by default, but not boxed in&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Things I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Small libraries need stronger constraints, not fewer.&lt;/strong&gt; If you don't decide what to exclude, they become messy surprisingly fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The API you start with is rarely the API you actually need.&lt;/strong&gt; &lt;code&gt;start(BeaconStep[])&lt;/code&gt; was fine until the app got bigger, tour context became local, and components started destroying themselves mid-tour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework-agnostic integration points are usually worth it.&lt;/strong&gt; The translation hook is a good example. Coupling would have been easier in the short term and worse for everyone else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Polish matters more than feature count for UI infrastructure.&lt;/strong&gt; Smooth motion, stable positioning, and clean focus behavior are what make a tour feel trustworthy.&lt;/p&gt;

&lt;p&gt;If you're building Angular apps and want something that feels more native to Angular application structure than a generic wrapper, maybe this is useful.&lt;/p&gt;

&lt;p&gt;This is my first public TypeScript / Angular library, which makes it more significant to me than a normal internal extraction. Not because it's a giant technical achievement. Just because shipping a public package feels different. It's one thing to write code for your own app. It's another to write something other people might install and judge in five minutes.&lt;/p&gt;

&lt;p&gt;If nothing else, building it reminded me that sometimes the right answer to “surely a package already exists for this” is “yes, but not one I actually want to use.”&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/ng-beacon" rel="noopener noreferrer"&gt;ng-beacon&lt;/a&gt;&lt;br&gt;
👉 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/HomelessCoder/ng-beacon" rel="noopener noreferrer"&gt;HomelessCoder/ng-beacon&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it and something feels awkward, I'd genuinely rather hear that than a polite “looks nice.” That's the only way these packages get better.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>angular</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From an Unemployed Side-Project to AWS Community Builder: Why you should start sharing today</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Wed, 04 Mar 2026 10:23:36 +0000</pubDate>
      <link>https://dev.to/homeless-coder/from-an-unemployed-side-project-to-aws-community-builder-why-you-should-start-sharing-today-4gkf</link>
      <guid>https://dev.to/homeless-coder/from-an-unemployed-side-project-to-aws-community-builder-why-you-should-start-sharing-today-4gkf</guid>
      <description>&lt;p&gt;Six months ago I was unemployed. I was also nervous, uncertain, and had never "built in public" in my life.&lt;/p&gt;

&lt;p&gt;Yesterday, I received an email confirming that I've been accepted into the &lt;strong&gt;AWS Community Builders&lt;/strong&gt; program for 2026-2027.&lt;/p&gt;

&lt;p&gt;I'm writing this because I want to talk to the version of myself from six months ago - the one who was (and still is!) hesitant to hit "Publish" on that first post.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it started
&lt;/h2&gt;

&lt;p&gt;When I found myself between jobs, I decided to pour my energy into an open-source project: &lt;strong&gt;a PHP modular framework&lt;/strong&gt;. I had a specific vision for modular monolith architecture and willingness to share it with the PHP community. I started writing here on dev.to to document the "why" and the "how": &lt;a href="https://dev.to/homeless-coder/series/33542"&gt;Building a Modular PHP Paradigm Series' Articles&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the time, I didn't think of myself as a "writer" or a "content creator". I was just a dev with an idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turning Point
&lt;/h2&gt;

&lt;p&gt;Sharing my work was not just about the code. It was about building confidence, engaging with the community, and developing the idea with insights that were not on my radar before. It led to a very unexpected, yet rewarding outcome: my acceptance into the AWS Community Builders program.&lt;/p&gt;

&lt;h2&gt;
  
  
  My message to you
&lt;/h2&gt;

&lt;p&gt;If you are working on something, even if it feels niche, even if you think "no one will care": Share it anyway.&lt;/p&gt;

&lt;p&gt;You don't just build software when you build in public; you are developing yourself. You build a reputation, a network, and opportunities that you can't even see yet.&lt;/p&gt;

&lt;p&gt;I'm so proud to join this cohort, and I'm incredibly grateful to the dev.to community for being the place where this journey actually began.&lt;/p&gt;

&lt;p&gt;Such an exciting moment!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>buildinpublic</category>
      <category>career</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Launched. Users Showed Up. And Then I Watched Them Get Lost.</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Tue, 24 Feb 2026 07:46:54 +0000</pubDate>
      <link>https://dev.to/homeless-coder/i-launched-users-showed-up-and-then-i-watched-them-get-lost-2d19</link>
      <guid>https://dev.to/homeless-coder/i-launched-users-showed-up-and-then-i-watched-them-get-lost-2d19</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is the original founder-side version of the story. From post 3 onward, the series was rewritten in a calmer, more technical style.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first post in this series was about why Omnismith exists. This one is about what happened after I launched it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Parts Nobody Sees on the Commit Graph
&lt;/h2&gt;

&lt;p&gt;I've been a software engineer for a long time. What I had never done was build a complete commercial product from zero — alone — with real money and real legal obligations on the line.&lt;/p&gt;

&lt;p&gt;Some of what that actually involved:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two full architectural iterations.&lt;/strong&gt; The first codebase went in a direction I wasn't satisfied with. I scrapped it and started over with a modular monolith on &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;my own framework&lt;/a&gt;, DDD, and hexagonal/use-case architecture. It was the right call — the backend genuinely became enjoyable to work in, not something to fight against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening a legal entity while in immigration.&lt;/strong&gt; I'm not an EU citizen. I opened a UK LTD — something I'd never done in my life, in a country that isn't mine, in a second language. It's surprisingly straightforward once you start, but it took mental energy I hadn't budgeted for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing based on actual economics.&lt;/strong&gt; Not "what sounds reasonable" but walking through unit cost, comparable tools, where the free tier lines make sense, and why flat pricing over per-seat billing fits the developer audience I'm building for. That analysis took longer than I expected and I'm glad I didn't rush it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A full DevOps pipeline, built and maintained by me.&lt;/strong&gt; GitHub CI/CD, my own Kubernetes cluster, ArgoCD for GitOps deployments. I'm the infrastructure team. I'm also the backend. I'm also the frontend. I'm also the support desk.&lt;/p&gt;

&lt;p&gt;Nobody helped me build any of this. I'm not complaining — it's genuinely something I'm proud of, even though bragging isn't really my thing. I'm writing it down because I think solo builders often undersell how much ground they're covering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Registrations
&lt;/h2&gt;

&lt;p&gt;After the soft launch, a small group of closed beta testers — people I actually know and trust — tried the early version and gave honest feedback. They caught small things I'd stopped seeing — a missing translation here, a confusing label there. They pointed at specific moments where they got confused. They told me the onboarding wasn't landing.&lt;/p&gt;

&lt;p&gt;That feedback was more valuable than months of me staring at my own code. I'm genuinely grateful for it.&lt;/p&gt;

&lt;p&gt;And then real users — people I don't know — started showing up. Each one felt way more significant than it probably should have. After years as a hired engineer building things for other people's products, seeing a real stranger's account appear in &lt;em&gt;my&lt;/em&gt; system landed differently. It still does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blank Canvas Problem
&lt;/h2&gt;

&lt;p&gt;Here's what the beta feedback actually taught me.&lt;/p&gt;

&lt;p&gt;Omnismith's v1 onboarding worked like this: register → step through a tutorial → land in an empty project with nothing in it — no templates, no attributes, no data.&lt;/p&gt;

&lt;p&gt;I thought that was fine. The tutorial explained the concepts. The user had a clean slate to start.&lt;/p&gt;

&lt;p&gt;What actually happened: users finished the tutorial and then stared at an empty screen, having memorized a sequence of UI steps but with no immediate sense of &lt;em&gt;what&lt;/em&gt; to actually do with them. The mental model wasn't formed yet. And blank canvases are paralyzing — they're for people who already know what they want to paint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v2 is different.&lt;/strong&gt; Now when you register, you don't get an empty project. You get a demo project pre-populated with a real dataset: a product catalog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzua973pg1r4zkom9e77.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzua973pg1r4zkom9e77.webp" alt="Omnismith demo project — product catalog with categories, brands, and products" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It has 3 templates — &lt;strong&gt;Category&lt;/strong&gt;, &lt;strong&gt;Brand&lt;/strong&gt;, and &lt;strong&gt;Product&lt;/strong&gt; — with 11 attributes across them. Some attributes are shared across templates (&lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;Description&lt;/code&gt;). The Product template uses References to link each product to its Category and Brand, and even has a self-referencing "Related Product" link. There are 19 sample entities: 4 categories (Electronics, Furniture, Apparel, Books), 5 brands, and 10 products with real prices, stock status, release dates, and tags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb9pnt1zfgzh1t6suxpk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsb9pnt1zfgzh1t6suxpk.webp" alt="Omnismith v2 onboarding — interactive tour with contextual pointers" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's not a lot. But it's &lt;em&gt;enough&lt;/em&gt; to instantly understand the shape of the platform. There's something to click on. There's data to edit. The templates tell you "this is how structure works." The entities tell you "this is what your data looks like."&lt;/p&gt;

&lt;p&gt;On top of that, I added an interactive tour — something in the spirit of Intro.js — that walks you through the actual UI with contextual pointers. Not a wall of text in a help doc, but a live guide that highlights the element you're looking at right now. That combination — real data you can touch &lt;em&gt;plus&lt;/em&gt; a tour that shows you where to look — landed much better than v1's tutorial-then-empty-canvas approach.&lt;/p&gt;

&lt;p&gt;My beta testers stopped asking "what do I do now?" Which was the only metric that mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson That Should Have Been Obvious
&lt;/h2&gt;

&lt;p&gt;The blank canvas problem is not new. Every tool with a new user flow has solved or failed to solve it. But when it's &lt;em&gt;your&lt;/em&gt; tool, your mental model of it is so complete that you forget how much context you're carrying that your user doesn't have yet.&lt;/p&gt;

&lt;p&gt;You see a blank project and feel possibilities. Your user sees nothing.&lt;/p&gt;

&lt;p&gt;Show them a story they can edit. Don't make them write the opening chapter.&lt;/p&gt;

&lt;p&gt;That was probably the most valuable lesson of my early founder &lt;em&gt;days&lt;/em&gt;. Not a technical insight. Not an architecture decision. A UX realization delivered by real people being kind enough to tell me where they got stuck instead of just quietly leaving.&lt;/p&gt;

&lt;p&gt;If you're building a tool and your new user flow ends with "and now they have a blank canvas" — reconsider that.&lt;/p&gt;




&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;omnismith.io&lt;/a&gt;&lt;/strong&gt; — log in and see the demo project yourself&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://app.omnismith.io" rel="noopener noreferrer"&gt;app.omnismith.io&lt;/a&gt;&lt;/strong&gt; — free tier, no credit card&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://docs.omnismith.io" rel="noopener noreferrer"&gt;docs.omnismith.io&lt;/a&gt;&lt;/strong&gt; — now actually worth reading&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of the "I'm Building Omnismith in Public" series. &lt;a href="https://omnismith.io/blog/i-built-this-because-i-was-tired-of-building-admin-panels" rel="noopener noreferrer"&gt;Previous: I Built This Because I Was Tired of Building Admin Panels.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>programming</category>
      <category>ux</category>
      <category>startup</category>
    </item>
    <item>
      <title>I Built This Because I Was Tired of Building Admin Panels</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Mon, 16 Feb 2026 20:29:41 +0000</pubDate>
      <link>https://dev.to/homeless-coder/i-built-this-because-i-was-tired-of-building-admin-panels-4e97</link>
      <guid>https://dev.to/homeless-coder/i-built-this-because-i-was-tired-of-building-admin-panels-4e97</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This is the original founder-side version of the story. From post 3 onward, the series was rewritten in a calmer, more technical style.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's Feb 16, 2026, and I'm doing something that feels both exciting and terrifying: I'm opening &lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;Omnismith&lt;/a&gt; to the public. After several months of development, the landing page is live, payments are integrated, the app works. This is the moment.&lt;/p&gt;

&lt;p&gt;But let me back up and tell you why this exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem I Kept Running Into
&lt;/h2&gt;

&lt;p&gt;If you've worked as a developer-for-hire or in an agency, you know this pattern by heart:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client needs an admin panel&lt;/li&gt;
&lt;li&gt;You build it (again)&lt;/li&gt;
&lt;li&gt;"Can we add a field for X?"&lt;/li&gt;
&lt;li&gt;You write a migration, update the model, modify the form&lt;/li&gt;
&lt;li&gt;"Actually, can we track Y over time?"&lt;/li&gt;
&lt;li&gt;You add TimescaleDB, write aggregation queries, build charts&lt;/li&gt;
&lt;li&gt;"Who changed this value last week?"&lt;/li&gt;
&lt;li&gt;You implement audit logging&lt;/li&gt;
&lt;li&gt;Repeat for the next 15 clients&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every project had the same base requirements, just different &lt;em&gt;data structures&lt;/em&gt;. CRM for one client, inventory management for another, server monitoring for a third. Same features (CRUD, history, charts, access control), different schemas.&lt;/p&gt;

&lt;p&gt;At some point, I got tired of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The EAV Realization
&lt;/h2&gt;

&lt;p&gt;I started thinking: what if the &lt;em&gt;structure&lt;/em&gt; itself was data?&lt;/p&gt;

&lt;p&gt;This led me to Entity-Attribute-Value (EAV) architecture. Instead of hardcoding schemas in migrations, you define them in the UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Attributes&lt;/strong&gt; are your building blocks (like table columns)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates&lt;/strong&gt; are your schemas (like table definitions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entities&lt;/strong&gt; are your actual data (like table rows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You want to track servers? Create attributes for &lt;code&gt;Hostname&lt;/code&gt;, &lt;code&gt;IP Address&lt;/code&gt;, &lt;code&gt;CPU Cores&lt;/code&gt;, &lt;code&gt;Memory GB&lt;/code&gt;. Combine them into a "Server" template. Done. No migration files. No deployment. No downtime.&lt;/p&gt;

&lt;p&gt;You want to add OS version? Create the attribute, add it to the template. It's available immediately.&lt;/p&gt;

&lt;p&gt;This isn't a new idea—Airtable does this beautifully, Notion databases do this, hell, even WordPress custom fields do a version of this. But I needed something that also handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metric ingestion&lt;/strong&gt; for time-series data (think Datadog, but for any entity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full audit history&lt;/strong&gt; (who changed what, when, and why)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper RBAC&lt;/strong&gt; (role-based + resource-level access control)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API-first architecture&lt;/strong&gt; (with OpenAPI spec, ready for integrations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built it. Using my own &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;modular PHP framework&lt;/a&gt; (yes, the one I wrote about before), PostgreSQL with TimescaleDB for time-series, and Angular 21 for the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes This Different From Airtable
&lt;/h2&gt;

&lt;p&gt;If you just need flexible schemas and basic CRUD, Airtable is fantastic. It's polished, has a great ecosystem, and works well for business teams. But here's what it &lt;em&gt;doesn't&lt;/em&gt; do:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time-series metric ingestion.&lt;/strong&gt; Airtable stores snapshots—each record is a moment in time. Omnismith stores &lt;em&gt;timelines&lt;/em&gt;. You can push metrics via API every 30 seconds (CPU usage, temperature, API request counts, sensor readings) and query them like: &lt;em&gt;"Show me average CPU usage for the last 7 days."&lt;/em&gt; This isn't a workaround with linked records and formulas—it's TimescaleDB under the hood, built for this exact use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full audit history without manual versioning.&lt;/strong&gt; Every field change is logged automatically with who/what/when. No need to create separate "Version History" tables or complex automation rules to track changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI that executes, not suggests.&lt;/strong&gt; Airtable's AI generates formulas and field descriptions. Omnismith's AI &lt;em&gt;creates templates, adds attributes, inserts entities, and queries your data&lt;/em&gt;. It has direct function-calling access to your database through the OpenAPI spec. It's not a documentation helper—it's a schema builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flat pricing for developers.&lt;/strong&gt; $99/month gets you 50,000 entities with all features unlocked. Airtable Team costs $20/seat, so 5 users = $100/month for 50K records—but you're locked into per-seat billing even if you're just using the API. Need RBAC? That's Business tier at $45/seat. Need audit logs? Same. Omnismith: everything's included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API-first design.&lt;/strong&gt; Full OpenAPI spec generated from code. Every UI action has an API equivalent. Built for developers who need programmatic control over their data platform, not just business users clicking through forms.&lt;/p&gt;

&lt;p&gt;If Airtable is for marketing teams managing campaigns, Omnismith is for developers building IoT platforms, SaaS backends, or internal tools that need both flexible schemas &lt;em&gt;and&lt;/em&gt; time-series analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Assistant That Actually Works
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. I added an AI Assistant—but not the kind that gives you generic advice or writes code snippets. This one &lt;em&gt;executes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It's powered by Google Gemini 2.5 and has direct access to your Omnismith data through function calling. Watch what happens when you talk to it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "Create a Server template with hostname, IP, OS, and CPU cores"&lt;/p&gt;

&lt;p&gt;The assistant doesn't just &lt;em&gt;tell&lt;/em&gt; you how to do it. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a StringAttribute for "Hostname"&lt;/li&gt;
&lt;li&gt;Creates a StringAttribute for "IP Address"
&lt;/li&gt;
&lt;li&gt;Creates a ListAttribute for "OS" with options like "Ubuntu", "CentOS", "Windows"&lt;/li&gt;
&lt;li&gt;Creates a NumberAttribute for "CPU Cores"&lt;/li&gt;
&lt;li&gt;Creates a Template called "Server"&lt;/li&gt;
&lt;li&gt;Associates all 4 attributes with the template&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You see all of this happen in real-time—streaming responses with visible tool execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "Add a new server called web-prod-01 with IP 10.0.1.50, Ubuntu, 8 cores"&lt;/p&gt;

&lt;p&gt;It creates the entity, fills in all the attributes. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; "Show me all servers with more than 4 CPU cores"&lt;/p&gt;

&lt;p&gt;It queries your data and returns the results in a readable format.&lt;/p&gt;

&lt;p&gt;This isn't a chatbot that &lt;em&gt;suggests&lt;/em&gt; changes. It's an agent that &lt;em&gt;makes&lt;/em&gt; them. The LLM proxy layer (a custom Fastify server I built) handles streaming, tool orchestration, and authentication. The tools themselves are auto-generated from the OpenAPI spec. Everything is schema-aware.&lt;/p&gt;

&lt;p&gt;For developers building systems for non-technical clients, this is huge. Your client can literally describe what they need in plain language, and the assistant scaffolds it. No training documentation, no "click here, then here, then submit" tutorials. Just conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can Actually Do With It
&lt;/h2&gt;

&lt;p&gt;Right now, Omnismith is being used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personal AI assistants&lt;/strong&gt; (data from Telegram bots, reminder systems)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IoT device monitoring&lt;/strong&gt; (temperature, humidity, motion sensors pushing metrics via API)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User consent tracking&lt;/strong&gt; (GDPR compliance, versioned agreements)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server fleet management&lt;/strong&gt; (infrastructure inventory + metric dashboards)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product catalogs&lt;/strong&gt; (headless backend for custom storefronts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The free tier gives you 150 entities and 7 days of history retention—enough to validate an idea or run a small personal project. The Scale tier ($99/month) gives you 50,000 entities, 180 days of retention, and all features unlocked: AI Assistant, full RBAC, audit logging, dashboards, automations—no per-seat charges, no premium tiers. (Compare: Airtable Team is $20/seat for similar record limits, but RBAC and audit logs require Business tier at $45/seat.)&lt;/p&gt;

&lt;p&gt;Full OpenAPI spec, so you can integrate it with anything. I use it via API tokens from custom scripts. The UI is in 8 languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack (For the Curious)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.4 (strict types, readonly properties, enums, attributes)&lt;/li&gt;
&lt;li&gt;Custom modular framework (explicit DI, hexagonal architecture, DDD)&lt;/li&gt;
&lt;li&gt;PostgreSQL + TimescaleDB&lt;/li&gt;
&lt;li&gt;Transactional outbox pattern for domain events&lt;/li&gt;
&lt;li&gt;OpenAPI spec auto-generated from PHP attributes&lt;/li&gt;
&lt;li&gt;PHPUnit: Tests: 996, Assertions: 5775&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular 21 (zoneless, standalone components)&lt;/li&gt;
&lt;li&gt;Angular Signals for state management&lt;/li&gt;
&lt;li&gt;Angular Material + TailwindCSS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rxResource&lt;/code&gt; for data fetching&lt;/li&gt;
&lt;li&gt;AG Charts for visualizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom LLM proxy (Fastify + TypeScript)&lt;/li&gt;
&lt;li&gt;Google Gemini 2.5&lt;/li&gt;
&lt;li&gt;Function calling with OpenAPI-derived tools&lt;/li&gt;
&lt;li&gt;SSE streaming with visible tool execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've written about the framework before on dev.to (see my &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;modular architecture series&lt;/a&gt;), and yes, Omnismith is built entirely on it. That's validation #1 that the architecture actually works at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Focus &amp;amp; Roadmap
&lt;/h2&gt;

&lt;p&gt;I'm building this in public and prioritizing core stability over "feature bloat". While it is in beta, I'm focused on perfecting the desktop experience and core data visualization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current Visuals: High-performance line charts, stats, gauges, and lists (Advanced chart types in development).&lt;/li&gt;
&lt;li&gt;Workflow: Currently focused on core data-handling; a visual automation builder (aka "Business Processes") to trigger automated data updates on specific events is the next major milestone.&lt;/li&gt;
&lt;li&gt;Mobile: Optimized for desktop power-users; responsive mobile tweaks are rolling out weekly, but it's already usable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't a "replace everything" tool yet - it's a lean, functional engine for users who need simplicity and effectiveness without the enterprise clutter. It's functional, tested, and solving real problems for the handful of users I've soft-launched with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm Telling You This
&lt;/h2&gt;

&lt;p&gt;I'm a developer. I know how to write code, architect systems, and ship products. What I don't know how to do is &lt;em&gt;market&lt;/em&gt; things with loud, empty promises.&lt;/p&gt;

&lt;p&gt;I'm not going to tell you Omnismith is "revolutionary" or "unprecedented" or will "transform your workflow." It's a tool. It solves a specific set of problems (flexible schemas + time-series + audit history) better than cobbling together Airtable + Datadog + custom code.&lt;/p&gt;

&lt;p&gt;If you've ever built an admin panel and thought "there has to be a better way," this might be that way. If you need to track &lt;em&gt;things&lt;/em&gt; (servers, products, sensors, contracts, whatever) and see how they change over time, this is built for that.&lt;/p&gt;

&lt;p&gt;Try it. Break it. Tell me what's wrong. I'm listening.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://omnismith.io" rel="noopener noreferrer"&gt;omnismith.io&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://app.omnismith.io" rel="noopener noreferrer"&gt;app.omnismith.io&lt;/a&gt;&lt;/strong&gt; (150 entities free, no credit card)&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://swagger.omnismith.io" rel="noopener noreferrer"&gt;swagger.omnismith.io&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; The AI Assistant demo is real. Log in, hit the AI Assistant menu, and try: &lt;em&gt;"Create a Product template with name, price, and SKU"&lt;/em&gt;. Watch it work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S.&lt;/strong&gt; If you're curious about the modular framework powering all of this, I've written a series about it: &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;Solving PHP's Module Coupling Problem&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>ai</category>
      <category>startup</category>
    </item>
    <item>
      <title>Auto-Discovering Console Commands in Power Modules</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Thu, 16 Oct 2025 00:05:33 +0000</pubDate>
      <link>https://dev.to/homeless-coder/auto-discovering-console-commands-in-power-modules-4j8a</link>
      <guid>https://dev.to/homeless-coder/auto-discovering-console-commands-in-power-modules-4j8a</guid>
      <description>&lt;p&gt;A couple of days ago, I was working on a PHP project that uses the Power Modules framework and Symfony Console, and I realized I was repeating the same command registration pattern across multiple projects. Time to build something better!&lt;/p&gt;

&lt;p&gt;For those new to the series: Power Modules is a modular PHP framework where each module encapsulates its own logic and dependencies, communicating through well-defined interfaces while maintaining strict boundaries.&lt;/p&gt;

&lt;p&gt;I already had a working setup/boilerplate for registering console commands exported from a Power Module (using the &lt;code&gt;ExportsComponents&lt;/code&gt; interface) that I copy-pasted across projects. It follows the Power Modules framework principles: keep modules encapsulated and their dependencies private, and delegate the instantiation and dependency resolution to their DI container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrdersModule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UsersModule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other modules&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/../'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Symfony\Component\Console\Application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'My Console Application'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0.1.0'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$modules&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$module&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// $exportedComponent is just a class-string that references to a power module DI container&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;This works, but it felt clunky repeating this boilerplate across projects. I knew that Symfony DI can manage console commands, and I wanted to see if I could build something that automatically discovers and registers console commands from different modules. So I thought: Why not apply the &lt;code&gt;PowerModuleSetup&lt;/code&gt; concept for this? (you can read more about it here: &lt;a href="https://dev.to/homeless-coder/the-night-i-discovered-id-built-something-revolutionary-and-didnt-know-it-2oik"&gt;The Night I Discovered I'd Built Something Revolutionary (And Didn't Know It)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Solution
&lt;/h2&gt;

&lt;p&gt;The implementation was quite straightforward - create a &lt;code&gt;PowerModuleSetup&lt;/code&gt; that does the same thing as the above code, but in a more modular and reusable way.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Iteration: Direct Registration
&lt;/h3&gt;

&lt;p&gt;This &lt;code&gt;PowerModuleSetup&lt;/code&gt; bridges Symfony Console with the Power Modules framework's modular architecture. Modules export console commands while maintaining encapsulation principles, and commands are auto-discovered and registered into a central &lt;code&gt;Console\Application&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsoleCommandsSetup&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModuleSetup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;\Symfony\Component\Console\Application&lt;/span&gt; &lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Symfony\Component\Console\Application&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PowerModuleSetupDto&lt;/span&gt; &lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;ExportsComponents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setupPhase&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nc"&gt;SetupPhase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exportedComponent&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Lazy-Loading with ContainerCommandLoader
&lt;/h3&gt;

&lt;p&gt;I wanted to take it further with deferred command instantiation. Symfony's &lt;code&gt;ContainerCommandLoader&lt;/code&gt; loads commands from a DI container on-demand. Since &lt;code&gt;ExportsComponentsSetup&lt;/code&gt; already registers exported components in the root container, I could leverage that. This meant updating the setup to use a two-phase approach: collect commands in the Pre phase and register them in the Post phase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConsoleCommandsSetup&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModuleSetup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Application&lt;/span&gt; &lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?CommandLoaderInterface&lt;/span&gt; &lt;span class="nv"&gt;$commandLoader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @var array&amp;lt;string,class-string&amp;lt;Command&amp;gt;&amp;gt; $commandMap
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$commandMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PowerModuleSetupDto&lt;/span&gt; &lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;ExportsComponents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setupPhase&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;SetupPhase&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Pre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// PRE phase: collect all commands to be registered later&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_subclass_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attribute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReflectionClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AsCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$attribute&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;newInstance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$component&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandLoader&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandLoader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContainerCommandLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$powerModuleSetupDto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rootContainer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setCommandLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;commandLoader&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Now I have a reusable &lt;code&gt;PowerModuleSetup&lt;/code&gt; that can be added to any Power Modules application to automatically discover and register console commands from modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/../'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrdersModule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UsersModule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPowerSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConsoleCommandsSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Console application is now available with all module commands registered&lt;/span&gt;
&lt;span class="nv"&gt;$console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Symfony\Component\Console\Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$console&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The complete implementation is available in the repository: &lt;a href="https://github.com/power-modules/console" rel="noopener noreferrer"&gt;power-modules/console&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install via Composer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require power-modules/console
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern eliminated the boilerplate I was copy-pasting and made command registration automatic. If you're building modular applications with Power Modules, this setup might save you some time too.&lt;/p&gt;

</description>
      <category>php</category>
      <category>software</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From Vision to Visualization: Building Pluggable Architecture for PHP</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Tue, 30 Sep 2025 18:24:49 +0000</pubDate>
      <link>https://dev.to/homeless-coder/from-vision-to-visualization-building-pluggable-architecture-for-php-1olb</link>
      <guid>https://dev.to/homeless-coder/from-vision-to-visualization-building-pluggable-architecture-for-php-1olb</guid>
      <description>&lt;h2&gt;
  
  
  The spark: from architecture to ecosystems 🔌
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;first post&lt;/a&gt;, I shared how explicit module boundaries (imports/exports) make architecture visible. In my &lt;a href="https://dev.to/homeless-coder/the-night-i-discovered-id-built-something-revolutionary-and-didnt-know-it-2oik"&gt;second post&lt;/a&gt;, I realized that same foundation quietly unlocked an ecosystem pattern that goes beyond web apps.&lt;/p&gt;

&lt;p&gt;This post shows the first concrete piece of that ecosystem story: a dependency‑injection‑native plugin system with static analysis support (Layer 1), a real extension point in the dependency graph module (Layer 2), and a third‑party implementation you can use today—the Mermaid renderers (Layer 3).&lt;/p&gt;

&lt;p&gt;You'll see how plugins are discovered automatically, resolved with DI, and used to visualize your architecture with one command. This foundation enables everything from content filter pipelines to payment gateway adapters—patterns we'll explore in Part 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem layers, now practical
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Layer 1: Foundation for plugins (generic, domain‑agnostic)&lt;/li&gt;
&lt;li&gt;Layer 2: Domain‑specific cores exposing plugin points (e.g., dependency‑graph → Renderer)&lt;/li&gt;
&lt;li&gt;Layer 3: Third‑party plugins (e.g., Mermaid renderers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll walk through all three, end‑to‑end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1 — The plugin foundation (generic but powerful) 🧩
&lt;/h2&gt;

&lt;p&gt;The goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plugins are first‑class citizens with metadata&lt;/li&gt;
&lt;li&gt;Registries provide IDE/static analysis support (via PHPDoc generics) and lazy loading (instantiate on demand)&lt;/li&gt;
&lt;li&gt;Discovery is automatic at app boot, integrated with module isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key pieces (&lt;a href="https://packagist.org/packages/power-modules/plugin" rel="noopener noreferrer"&gt;&lt;code&gt;power-modules/plugin&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Plugin&lt;/code&gt;: every plugin returns &lt;code&gt;PluginMetadata&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PluginRegistry&amp;lt;TPlugin&amp;gt;&lt;/code&gt;: register, list, and instantiate plugins (generic annotation for IDE support)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GenericPluginRegistry&lt;/code&gt;: default, DI‑native implementation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProvidesPlugins&amp;lt;TPlugin&amp;gt;&lt;/code&gt;: a module declares its plugins for one or more registries&lt;/li&gt;
&lt;li&gt;Setups:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GenericPluginRegistrySetup&lt;/code&gt;: provides a default registry in root&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PluginRegistryModuleConvenienceSetup&lt;/code&gt;: makes the registry injectable in modules&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PluginRegistrySetup&lt;/code&gt;: discovers modules that implement &lt;code&gt;ProvidesPlugins&lt;/code&gt; and registers their plugins&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;A tiny example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Framework\App\ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Framework\PowerModule\Contract\PowerModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Framework\Container\ConfigurableContainerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\Contract\Plugin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\PluginMetadata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\Contract\PluginRegistry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\Contract\ProvidesPlugins&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\PowerModule\Setup\PluginRegistrySetup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;TextProcessor&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;TextProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPluginMetadata&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;PluginMetadata&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PluginMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Uppercase'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1.0.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Converts text to UPPERCASE'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;strtoupper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$in&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextPluginModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProvidesPlugins&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPlugins&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;PluginRegistry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt; &lt;span class="c1"&gt;// declare plugins&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ConfigurableContainerInterface&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// DI binding&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPowerSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nc"&gt;PluginRegistrySetup&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withDefaults&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextPluginModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="cd"&gt;/** @var PluginRegistry&amp;lt;TextProcessor&amp;gt; $registry */&lt;/span&gt;
&lt;span class="nv"&gt;$registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PluginRegistry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$plugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$registry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makePlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UppercasePlugin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$plugin&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// HELLO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At boot, &lt;code&gt;PluginRegistrySetup&lt;/code&gt; scans modules that implement &lt;code&gt;ProvidesPlugins&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For each plugin class, it registers a (class → owning container) mapping in the root registry&lt;/li&gt;
&lt;li&gt;At runtime, &lt;code&gt;makePlugin()&lt;/code&gt; resolves the instance from that module's container (full DI)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Layer 2 — The dependency graph exposes a plugin point 🧭
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://packagist.org/packages/power-modules/dependency-graph" rel="noopener noreferrer"&gt;&lt;code&gt;power-modules/dependency-graph&lt;/code&gt;&lt;/a&gt; module collects your modules and their relationships during app bootstrap.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DependencyGraphSetup&lt;/code&gt; builds a &lt;code&gt;DependencyGraph&lt;/code&gt; from &lt;code&gt;ModuleNode&lt;/code&gt;s and their imports&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Renderer&lt;/code&gt; is a plugin contract for turning that graph into outputs (text, JSON, Mermaid, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RendererPluginRegistry&lt;/code&gt; (extends &lt;code&gt;GenericPluginRegistry&amp;lt;Renderer&amp;gt;&lt;/code&gt;) is exported by &lt;code&gt;RendererModule&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Renderer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;DependencyGraph&lt;/span&gt; &lt;span class="nv"&gt;$graph&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getFileExtension&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g. mmd, json&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getMimeType&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getDescription&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us a clean "slot" where anyone can add a new renderer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 3 — Third‑party Mermaid renderers 🧪
&lt;/h2&gt;

&lt;p&gt;Now here's where it gets interesting. This repository (&lt;a href="https://packagist.org/packages/power-modules/dependency-graph-mermaid" rel="noopener noreferrer"&gt;&lt;code&gt;power-modules/dependency-graph-mermaid&lt;/code&gt;&lt;/a&gt;) is a third‑party plugin package that extends the dependency-graph module with visual renderers.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MermaidRendererModule&lt;/code&gt; registers three plugins implementing &lt;code&gt;Renderer&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flowchart&lt;/strong&gt;: &lt;code&gt;MermaidGraph&lt;/code&gt; (LR direction, shows imports as labeled arrows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class diagram&lt;/strong&gt;: &lt;code&gt;MermaidClassDiagram&lt;/code&gt; (TB direction + YAML frontmatter, exports as class members)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeline&lt;/strong&gt;: &lt;code&gt;MermaidTimeline&lt;/code&gt; (dependency levels as phases, Infrastructure/Domain sections)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're regular plugins declared via &lt;code&gt;ProvidesPlugins&amp;lt;Renderer&amp;gt;&lt;/code&gt; and discovered automatically—no special wiring needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Visual tour: your architecture, rendered
&lt;/h2&gt;

&lt;p&gt;The best part? You get immediate visual feedback. Below are small, self‑contained Mermaid snippets inspired by the &lt;a href="https://github.com/power-modules/dependency-graph-mermaid/tree/main/examples/ecommerce/mermaid" rel="noopener noreferrer"&gt;ecommerce example&lt;/a&gt;. You can paste them into &lt;a href="https://mermaid.live/" rel="noopener noreferrer"&gt;Mermaid Live&lt;/a&gt; or VS Code's Mermaid preview to see them rendered.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Flowchart (modules and imports)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faljmlyqpnw23hl8ofqm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faljmlyqpnw23hl8ofqm7.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Class diagram (exports as members, imports as dashed deps)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqabv1ypfklqwqi82jiea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqabv1ypfklqwqi82jiea.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Timeline (boot phases by dependency levels)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9rnbye2s6o1lns1kjfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9rnbye2s6o1lns1kjfs.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tip: in code, &lt;code&gt;MermaidTimeline&lt;/code&gt; computes phases Kahn‑style and separates Infrastructure/Domain with a simple classifier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete setup pattern 🚀
&lt;/h2&gt;

&lt;p&gt;Here's the complete setup pattern—replace the example modules with your own:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the necessary setups and modules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DependencyGraphSetup()&lt;/code&gt; to collect the graph during app bootstrap&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;...PluginRegistrySetup::withDefaults()&lt;/code&gt; to enable plugin discovery&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RendererModule&lt;/code&gt; to expose the &lt;code&gt;RendererPluginRegistry&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MermaidRendererModule&lt;/code&gt; to register the three Mermaid plugins
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Framework\App\ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\PowerModule\Setup\DependencyGraphSetup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\Renderer\RendererModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\Renderer\Mermaid\MermaidRendererModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\Graph\DependencyGraph&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\DependencyGraph\Renderer\RendererPluginRegistry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Modular\Plugin\PowerModule\Setup\PluginRegistrySetup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPowerSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DependencyGraphSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPowerSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nc"&gt;PluginRegistrySetup&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withDefaults&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;// your app modules&lt;/span&gt;
        &lt;span class="nc"&gt;App\User\UserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Product\ProductModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Order\OrderModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Payment\PaymentModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Notification\NotificationModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;App\Database\DatabaseModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// plugin point + plugins&lt;/span&gt;
        &lt;span class="nc"&gt;RendererModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;MermaidRendererModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DependencyGraph&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$renderers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RendererPluginRegistry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$renderers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRegisteredPlugins&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$rendererClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$renderers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;makePlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rendererClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$renderer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$graph&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/mermaid/out.'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$renderer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFileExtension&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want ready‑made scripts? Check out &lt;a href="https://github.com/power-modules/dependency-graph-mermaid/tree/main/examples" rel="noopener noreferrer"&gt;&lt;code&gt;examples&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters (and how teams use it) 📌
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For refactoring:&lt;/strong&gt; Spot central "god" modules that import from everyone, or unused modules that export but no one imports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For onboarding:&lt;/strong&gt; Give new teammates a real map—not just "here's the codebase," but "here's how modules connect and what they export."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For architecture reviews:&lt;/strong&gt; Validate intended boundaries before they calcify. Catch cycles early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For documentation:&lt;/strong&gt; Check in generated &lt;code&gt;.mmd&lt;/code&gt; files alongside code. PRs that change module relationships show visual diffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For evolution planning:&lt;/strong&gt; Identify natural seams for service extraction—modules with low in‑degree and clear export contracts are good candidates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For CI guardrails:&lt;/strong&gt; Add analyzers to fail builds on cycles or unexpected coupling.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Foundation: &lt;code&gt;composer require power-modules/plugin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Graph + plugin point: &lt;code&gt;composer require power-modules/dependency-graph&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Mermaid renderers: &lt;code&gt;composer require power-modules/dependency-graph-mermaid&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then add the setups and modules as shown above, render, and paste the &lt;code&gt;.mmd&lt;/code&gt; into Mermaid Live.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next 🔮
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 2: Building your own plugin ecosystem&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom registries with domain-specific validation (payment gateways, content filters)&lt;/li&gt;
&lt;li&gt;Config-driven plugin pipelines (tenant-specific processing steps)&lt;/li&gt;
&lt;li&gt;Metadata-driven admin UIs (plugin catalogs without instantiation)&lt;/li&gt;
&lt;li&gt;Real patterns: CMS filters, discount strategies, ETL steps, gateway adapters&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/framework" rel="noopener noreferrer"&gt;power-modules/framework&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin system&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/plugin" rel="noopener noreferrer"&gt;power-modules/plugin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency graph&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/dependency-graph" rel="noopener noreferrer"&gt;power-modules/dependency-graph&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mermaid renderers&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/dependency-graph-mermaid" rel="noopener noreferrer"&gt;power-modules/dependency-graph-mermaid&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try the examples&lt;/strong&gt;: Clone the repository and run &lt;code&gt;php examples/ecommerce/generate.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm building this ecosystem in the open. If you try it, build a renderer, or have ideas for Part 2—drop a comment or connect with me!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What architectural challenges keep you up at night? Have you tried similar visualization approaches?&lt;/strong&gt; 💬&lt;/p&gt;

</description>
      <category>php</category>
      <category>architecture</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Night I Discovered I'd Built Something Revolutionary (And Didn't Know It)</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Wed, 24 Sep 2025 18:12:52 +0000</pubDate>
      <link>https://dev.to/homeless-coder/the-night-i-discovered-id-built-something-revolutionary-and-didnt-know-it-2oik</link>
      <guid>https://dev.to/homeless-coder/the-night-i-discovered-id-built-something-revolutionary-and-didnt-know-it-2oik</guid>
      <description>&lt;h2&gt;
  
  
  "Oh Saint Cats. What Have I Done?" 🤯
&lt;/h2&gt;

&lt;p&gt;It was 11:47 PM on a Tuesday. I was writing documentation for my PHP framework, exploring plugin architecture patterns, when it hit me like a freight train.&lt;/p&gt;

&lt;p&gt;I stopped typing. Stared at my screen. And said out loud to my empty room:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Oh Saint Cats. What have I done."&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup: What I Thought I Was Building
&lt;/h2&gt;

&lt;p&gt;Just two days ago, I shared &lt;a href="https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao"&gt;my journey of building a modular PHP framework&lt;/a&gt; during my career transition. Coming from network engineering, PHP and Angular development, I was frustrated by the implicit, conventional dependencies and weak module boundaries in PHP applications. Most frameworks rely on conventions for module interaction, which often leads to accidental coupling and hidden dependencies.&lt;/p&gt;

&lt;p&gt;My goal was simple: bring explicit import/export contracts and true encapsulation to PHP web applications.&lt;/p&gt;

&lt;p&gt;I built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Module-scoped DI containers (each module isolated)&lt;/li&gt;
&lt;li&gt;Explicit import/export contracts (dependencies visible in code)&lt;/li&gt;
&lt;li&gt;PowerModuleSetup pattern (cross-cutting functionality without coupling)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I thought I was solving the architectural problems of web applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But I had underestimated the scale of the solution.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Realization: From Decoupled to Universal
&lt;/h2&gt;

&lt;p&gt;From the beginning, I designed the framework to be decoupled from any specific domain. I proved this concept by building the HTTP router as a completely separate, optional package. I knew this design made it flexible, and I had even documented use cases for web APIs and ETL pipelines.&lt;/p&gt;

&lt;p&gt;But the true "aha!" moment came while documenting the &lt;code&gt;PowerModuleSetup&lt;/code&gt; plugin system. I wasn't just looking at individual use cases anymore; I was looking at the universal pattern that connected them.&lt;/p&gt;

&lt;p&gt;That's when the penny dropped. The realization wasn't that the core was free of web code... I knew that. It was that the combination of &lt;code&gt;PowerModule&lt;/code&gt; isolation and the &lt;code&gt;PowerModuleSetup&lt;/code&gt; extension mechanism created a &lt;strong&gt;repeatable, universal pattern for building entire, domain-specific ecosystems.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting the Dots
&lt;/h2&gt;

&lt;p&gt;My heart was racing. It wasn't a frantic brainstorm of new ideas; it was the sudden, shocking realization that my existing examples were not just disparate use cases. They were all instances of the same powerful, underlying pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Web API Use Case...
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$webApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiCoreModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addPowerModuleSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RoutingSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// A web-specific setup&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The ETL Pipeline Use Case...
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$etlApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EtlCoreModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CsvExtractorModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addPowerModuleSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PipelineSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// An ETL-specific setup&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Potential CLI Tool Use Case...
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$cliApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CliCoreModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;GitModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addPowerModuleSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CommandSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// A CLI-specific setup&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The same modular patterns worked for everything.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ecosystem Revelation
&lt;/h2&gt;

&lt;p&gt;But it got bigger. Much bigger.&lt;/p&gt;

&lt;p&gt;I realized I hadn't just built a framework. I had built a &lt;strong&gt;framework for building ecosystems&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Layer 1: Core Framework &amp;amp; Universal Extensions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;power-modules/framework&lt;/code&gt;: The core modular architecture.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;power-modules/plugin&lt;/code&gt; (coming soon): Generic infrastructure for building plugin systems.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Domain-Specific Plugin Systems&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;power-cms/core&lt;/code&gt;: Provides the core interfaces and plugin registry for a CMS ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;power-gateway/core&lt;/code&gt;: Provides the core for an API Gateway ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;power-etl/core&lt;/code&gt;: Provides the core for an ETL pipeline ecosystem.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Third-Party Plugins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;power-cms/blog&lt;/code&gt;: A blog plugin for the CMS ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;power-gateway/auth&lt;/code&gt;: An authentication plugin for the API Gateway ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;power-etl/csv&lt;/code&gt;: A CSV processing plugin for the ETL ecosystem.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The same PowerModuleSetup pattern could enable plugin discovery across completely different domains.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Validation Mission
&lt;/h2&gt;

&lt;p&gt;By this point, it was 1:30 AM and I was buzzing with excitement and disbelief. But I needed validation. Was this really as innovative as it felt, or was I just having a late-night coding high?&lt;/p&gt;

&lt;p&gt;I crafted a comprehensive research prompt and submitted it for independent analysis. The task: compare my framework's architectural patterns against every major solution in the PHP ecosystem and beyond.&lt;/p&gt;

&lt;p&gt;The research covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Symfony's bundle system and compiled containers&lt;/li&gt;
&lt;li&gt;Laravel's service providers and packages&lt;/li&gt;
&lt;li&gt;WordPress's plugin architecture&lt;/li&gt;
&lt;li&gt;Drupal's module system&lt;/li&gt;
&lt;li&gt;OSGi (Java) for comparison with true modularity&lt;/li&gt;
&lt;li&gt;Node.js module patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted the brutal truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Verdict: Revolutionary, Not Just Useful
&lt;/h2&gt;

&lt;p&gt;After extensive analysis, the independent research concluded:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"The Modular Framework is a revolutionary and unique architecture in the PHP ecosystem."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key findings:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Module-scoped DI containers are unique in PHP&lt;/strong&gt; - no major framework provides true container isolation per module&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime-enforced import/export contracts are unprecedented&lt;/strong&gt; - most frameworks rely on convention, not architectural enforcement
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The PowerModuleSetup pattern enables ecosystem building&lt;/strong&gt; - a novel approach to cross-cutting functionality&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Not just useful. Not just an improvement. &lt;strong&gt;A genuine paradigm shift.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architectural Vision Document
&lt;/h2&gt;

&lt;p&gt;To formalize these findings, I created an &lt;a href="https://github.com/power-modules/framework/blob/main/ARCHITECTURAL_VISION.md" rel="noopener noreferrer"&gt;Architectural Vision Document&lt;/a&gt; that serves as a technical white paper, positioning the framework within the broader landscape of software architecture.&lt;/p&gt;

&lt;p&gt;It compares the framework directly against established solutions and demonstrates why this approach represents a new paradigm for building complex, maintainable PHP systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for PHP Development
&lt;/h2&gt;

&lt;p&gt;This discovery has massive implications:&lt;/p&gt;

&lt;h3&gt;
  
  
  For Large-Scale Applications
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;True encapsulation&lt;/strong&gt; prevents architectural decay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit dependencies&lt;/strong&gt; make systems maintainable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team scalability&lt;/strong&gt; through isolated module ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  For Ecosystem Building
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistent plugin patterns&lt;/strong&gt; across different domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universal extension mechanisms&lt;/strong&gt; via PowerModuleSetup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework-agnostic&lt;/strong&gt; approach to modularity&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  For Microservice Evolution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Natural service boundaries&lt;/strong&gt; defined by modules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear extraction path&lt;/strong&gt; from monolith to services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract-based communication&lt;/strong&gt; ready for HTTP APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Technical Reality
&lt;/h2&gt;

&lt;p&gt;Let me be clear about what this is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~1,600 lines of focused, well-tested core code&lt;/li&gt;
&lt;li&gt;PHPStan level 8, comprehensive test coverage&lt;/li&gt;
&lt;li&gt;Built on proven patterns from other ecosystems&lt;/li&gt;
&lt;li&gt;MIT licensed, completely open source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not about creating the "next big framework" - it's about bringing architectural rigor to PHP that was previously unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the Revolution Yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require power-modules/framework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Basic Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ExportsComponents&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ConfigurableContainerInterface&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Journey Continues
&lt;/h2&gt;

&lt;p&gt;This discovery has completely changed my perspective on what I've built. What started as personal frustration with PHP architecture has become something that could influence how we build complex backend systems.&lt;/p&gt;

&lt;p&gt;The next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building example ecosystems (CMS, ETL, CLI tools)&lt;/li&gt;
&lt;li&gt;Creating plugin developer guides&lt;/li&gt;
&lt;li&gt;Exploring interoperability patterns between ecosystems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/framework" rel="noopener noreferrer"&gt;power-modules/framework&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectural Vision&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/framework/blob/main/ARCHITECTURAL_VISION.md" rel="noopener noreferrer"&gt;Technical white paper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research Analysis&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/framework/blob/main/docs/research/original-prompt.md" rel="noopener noreferrer"&gt;Original research prompt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Lesson
&lt;/h2&gt;

&lt;p&gt;Sometimes the most innovative solutions come from focusing intensely on problems you truly understand. You might think you're building one thing, only to discover you've created something much more significant.&lt;/p&gt;

&lt;p&gt;That Tuesday night reminded me why I love programming: the moment when you realize you've built something that could change how people approach entire categories of problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What architectural challenges have been keeping you up at night? Have you had similar "aha!" moments in your development journey?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The discovery that a personal project has broader implications than you ever imagined is one of the most exhilarating experiences in software development. This is mine.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>software</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Solving PHP's Module Coupling Problem: A Journey Into Modular Architecture</title>
      <dc:creator>HomelessCoder</dc:creator>
      <pubDate>Mon, 22 Sep 2025 19:30:17 +0000</pubDate>
      <link>https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao</link>
      <guid>https://dev.to/homeless-coder/solving-phps-module-coupling-problem-a-journey-into-modular-architecture-3bao</guid>
      <description>&lt;h1&gt;
  
  
  How I Built a Modular PHP Framework and What I Learned About Architecture
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4cvxzehvf6dznbc8ela.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4cvxzehvf6dznbc8ela.png" alt="Before PowerModules Framework" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That Kept Me Up at Night 😴
&lt;/h2&gt;

&lt;p&gt;As a PHP developer with decades of experience, I've built my fair share of complex applications. But there was always one architectural problem that frustrated me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Module boundaries in PHP applications are often invisible and easily broken.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what I mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This looks innocent enough...&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;UserService&lt;/span&gt; &lt;span class="nv"&gt;$userService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;PaymentService&lt;/span&gt; &lt;span class="nv"&gt;$paymentService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;InventoryService&lt;/span&gt; &lt;span class="nv"&gt;$inventory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;EmailService&lt;/span&gt; &lt;span class="nv"&gt;$emailService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what happens when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone refactors &lt;code&gt;UserService&lt;/code&gt; without knowing &lt;code&gt;OrderService&lt;/code&gt; depends on it?&lt;/li&gt;
&lt;li&gt;You want to test &lt;code&gt;OrderService&lt;/code&gt; but need to mock 4 different modules?&lt;/li&gt;
&lt;li&gt;You need to extract the Payment module into a microservice?&lt;/li&gt;
&lt;li&gt;A new developer joins and can't tell what depends on what?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The dependencies are implicit and hidden.&lt;/strong&gt; Your DI container knows about them, but your code doesn't make them explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Inspiration: Learning from Other Ecosystems 💡
&lt;/h2&gt;

&lt;p&gt;Coming from a network engineering background and working with Angular on the frontend, I've seen how other ecosystems handle this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular Modules
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CommonModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UserModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;     &lt;span class="c1"&gt;// Explicit imports&lt;/span&gt;
  &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;OrderComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Explicit exports&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                &lt;span class="c1"&gt;// Internal services&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OSGi (Java)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bundle explicitly declares what it imports/exports&lt;/span&gt;
&lt;span class="nc"&gt;Import&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nl"&gt;Package:&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;service&lt;/span&gt;
&lt;span class="nc"&gt;Export&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nl"&gt;Package:&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The pattern was clear: successful module systems make dependencies explicit.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Solution: PowerModules Framework 🚀
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncc2kau3qv73j88uf5bk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncc2kau3qv73j88uf5bk.png" alt="After PowerModules Framework" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I decided to bring these patterns to PHP. Here's what I built:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Each Module Gets Its Own DI Container
&lt;/h3&gt;

&lt;p&gt;Instead of one global container, each module has its own isolated space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModule&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ConfigurableContainerInterface&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This container is ONLY for OrderModule&lt;/span&gt;
        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderRepository&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// These services are private to this module by default&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Explicit Export Contracts
&lt;/h3&gt;

&lt;p&gt;If you want to share a service, you must explicitly export it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ExportsComponents&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Only this is available to other modules&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ConfigurableContainerInterface&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PasswordHasher&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PasswordHasher&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Private!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Explicit Import Contracts
&lt;/h3&gt;

&lt;p&gt;If you want to use another module's service, you must explicitly import it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ImportsComponents&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;ImportItem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;ImportItem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PaymentService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ConfigurableContainerInterface&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Now UserService and PaymentService are available for injection&lt;/span&gt;
        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addArguments&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nc"&gt;PaymentService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The magic:&lt;/strong&gt; Your dependencies are now visible in your code, not hidden in configuration files!&lt;/p&gt;

&lt;h2&gt;
  
  
  The PowerModuleSetup Pattern ⚡
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. How do you add cross-cutting functionality (like routing, events, logging) to ALL modules without breaking encapsulation?&lt;/p&gt;

&lt;p&gt;I created the &lt;strong&gt;PowerModuleSetup&lt;/strong&gt; pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoutingSetup&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModuleSetup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;PowerModuleSetupDto&lt;/span&gt; &lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This runs for EVERY module during app building&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;HasRoutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Pull all routes defined in this module and register them with the router&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;registerRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;powerModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;moduleContainer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addPowerModuleSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RoutingSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;// Extends ALL modules with HasRoutes interface&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern allows extensions to work across all modules while maintaining isolation. This's how the &lt;a href="https://github.com/power-modules/router" rel="noopener noreferrer"&gt;power-modules/router&lt;/a&gt; extension works, and it also forms the foundation for the framework's explicit export/import system. Both cross-cutting features and module relationships are enabled through PowerModuleSetup, ensuring encapsulation and visibility at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the App: The Fluent API 🏗️
&lt;/h2&gt;

&lt;p&gt;Putting it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forAppRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;AuthModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;UserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nc"&gt;OrderModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;PaymentModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addPowerModuleSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RoutingSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addPowerModuleSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSetup&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Access any exported service&lt;/span&gt;
&lt;span class="nv"&gt;$orderService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned Building This 🎓
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Dependency Resolution is Complex&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I had to implement topological sorting to handle module dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a dependency graph from import statements&lt;/li&gt;
&lt;li&gt;Detect circular dependencies&lt;/li&gt;
&lt;li&gt;Sort modules in the correct loading order&lt;/li&gt;
&lt;li&gt;Cache the result for performance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Two-Phase Loading is Essential&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1&lt;/strong&gt;: Register all modules and collect exports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2&lt;/strong&gt;: Resolve imports and apply PowerModuleSetup extensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures all exports are available before any imports try to resolve them.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Container Hierarchy Matters&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Root Container
├── Module A Container (isolated)
├── Module B Container (isolated)
└── Exported Services (aliases to module containers)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module's container is completely isolated, but exported services are accessible through the root container.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Explicit is Better Than Implicit&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The import/export contracts make your architecture visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New developers can see module relationships at a glance&lt;/li&gt;
&lt;li&gt;Refactoring becomes safer with explicit dependencies&lt;/li&gt;
&lt;li&gt;Testing becomes easier with clear boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Impact 📊
&lt;/h2&gt;

&lt;p&gt;Here's what this approach enables:&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Team Scaling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Team A owns AuthModule&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ExportsComponents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Team B owns OrderModule  &lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ImportsComponents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;ImportItem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AuthModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teams can work independently with clear contracts between modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easier Testing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Test OrderModule in isolation&lt;/span&gt;
&lt;span class="nv"&gt;$testApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;MockUserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Test double&lt;/span&gt;
        &lt;span class="nc"&gt;OrderModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;      &lt;span class="c1"&gt;// Real implementation&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Microservice Evolution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Today: Modular monolith&lt;/span&gt;
&lt;span class="nc"&gt;ImportItem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Tomorrow: HTTP API call&lt;/span&gt;
&lt;span class="nc"&gt;ImportItem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserApiModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The import/export contracts naturally become API contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Details 🔧
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Framework Stats:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~1,600 lines of core code&lt;/li&gt;
&lt;li&gt;PHPStan level 8 (maximum static analysis)&lt;/li&gt;
&lt;li&gt;PHP 8.4+ with strict types&lt;/li&gt;
&lt;li&gt;Comprehensive test coverage&lt;/li&gt;
&lt;li&gt;PSR-11 container interoperability&lt;/li&gt;
&lt;li&gt;MIT licensed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Components:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PowerModule&lt;/code&gt;: Core module interface&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConfigurableContainer&lt;/code&gt;: Custom DI container with method chaining&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ModularAppBuilder&lt;/code&gt;: Fluent app construction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PowerModuleSetup&lt;/code&gt;: Extension system&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ImportItem&lt;/code&gt;: Dependency declaration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparison with Existing Solutions 🔍
&lt;/h2&gt;

&lt;h3&gt;
  
  
  vs Symfony Bundles
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Symfony (implicit dependencies in services.yaml)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Dependencies configured in YAML, not visible in code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// PowerModules (explicit imports in code)  &lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ImportsComponents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;ImportItem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  vs Laravel Service Providers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Laravel (shared container)&lt;/span&gt;
&lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// PowerModules (isolated containers)&lt;/span&gt;
&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addArguments&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// Resolved from module's container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try It Yourself 🚀
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require power-modules/framework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Basic Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyModule&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;PowerModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ExportsComponents&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ConfigurableContainerInterface&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModularAppBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyModule&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// $service is ready to use, and IDE autocompletion works!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next? 🔮
&lt;/h2&gt;

&lt;p&gt;I'm working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;power-modules/events&lt;/strong&gt;: Event-driven architecture extension&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better documentation&lt;/strong&gt;: More examples and patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance optimizations&lt;/strong&gt;: Caching for module routes&lt;/li&gt;
&lt;li&gt;... Suggestions welcome!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion 💭
&lt;/h2&gt;

&lt;p&gt;Building this framework taught me more about dependency injection, module systems, and architectural patterns than years of just using existing tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explicit dependencies are better than implicit ones&lt;/li&gt;
&lt;li&gt;Module boundaries should be enforced, not just conventional&lt;/li&gt;
&lt;li&gt;Cross-cutting concerns can be added without breaking encapsulation&lt;/li&gt;
&lt;li&gt;Good architecture supports evolution (monolith → microservices)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Is it revolutionary?&lt;/strong&gt; No - these patterns exist in other ecosystems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it useful?&lt;/strong&gt; I think so - it solves real problems I've faced in complex PHP applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources 📚
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/framework" rel="noopener noreferrer"&gt;power-modules/framework&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packagist&lt;/strong&gt;: &lt;a href="https://packagist.org/packages/power-modules/framework" rel="noopener noreferrer"&gt;power-modules/framework&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Router Extension&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/router" rel="noopener noreferrer"&gt;power-modules/router&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Cases &amp;amp; Examples&lt;/strong&gt;: &lt;a href="https://github.com/power-modules/framework/blob/main/docs/use-cases/README.md" rel="noopener noreferrer"&gt;Use Cases &amp;amp; Examples&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What architectural challenges do you face in your PHP projects? Have you tried similar approaches? I'd love to hear your thoughts in the comments!&lt;/strong&gt; 💬&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
