Introducing Marko: The Truly Modular PHP Framework

Mark Shust created Marko, a new PHP framework that uses native PHP attributes instead of XML or YAML for routing and configuration. Modules nest automatically at every level and are discovered via Composer, while entities drive the migration system. The framework includes a CLI that auto-detects Docker Compose and pluggable view systems like Latte and Vue.
Chapters
Every PHP framework I've used makes you choose. You get Magento's extensibility, but then you drown in XML. You get Laravel's developer experience, but then lose the ability to truly override third-party code. I got so tired of choosing, so I built a brand new framework that doesn't make you. It's called Marko. I spent over a decade building and teaching on the Magento framework, and I learned a lot about what makes extensibility work and what makes it painful. And I've also tried overriding third-party packages in Laravel, and I don't need to tell you that it's not the prettiest thing. You're forking, monkey patching, or hoping the maintainer merges your PR. Magento solved this problem years ago with plugins and preferences, but buried it with tons of XML and made the modification process painful. Marco takes the best extensibility patterns from both and wraps them in clean, modern PHP. No XML, no YAML. Just PHP attributes and standard Composer conventions. Rather than walk you through slides, let's get right into the code. Marko is super modular. It has different packages available, each focused on doing one specific thing. Other frameworks have this, but Marko takes it a step further. Even the Marko framework itself is a module. It's modules all the way down. This provides a level of flexibility and extensibility that I just haven't seen with other PHP frameworks. You can pick and choose only the exact packages that you need and ignore the rest. And because modules are opt-in by default, your app will always load as fast as possible without any of the bloat. Since nearly every Marko app will have a set of basic packages installed, a simple skeleton package is available to quickly create a Marko app. By the way, all of this is documented at marko.build, so check out that site for the full documentation. Let's quickly bootstrap an app with that skeleton with composer create project project/marco/skeleton and then my-app. This will download Marco and all of the related packages that come along with it. Then we will just go into that directory. Marco is lightweight. You don't even need Docker to get started. It just uses PHP's built-in web server by default. But even those commands can be hard to remember and can grow in complexity as you start making updates and changes. So Marco provides a simple helper command to start the services. First, we will install the global Marko CLI command, and that gives us access to the Marko command, which allows us to run commands within the framework such as Marko up. This gets the Marko development server up and running in the background. You can also run Marko status to get the status of the running services or run Marko down to shut them down. And if you do want to use Docker, don't worry. Marko will automatically detect Docker Compose configurations and start and stop them automatically. Which means you don't even need to remember those Docker-specific commands. Let's now open up the site in a browser by typing Margot open. We'll see that the response returns not found. That's because we need to set up some routes. But first, since everything is a module, including our code, let's talk about Margot's module hierarchy. Margot is heavily inspired by Magento's vendor-to-module configuration, where packages are installed with Composer and can be overridden or plugged into at the local module level. With a fallback layer. Marko does this without any XML configuration and makes PHP a first-class citizen. The vendor directory, of course, stores modules that are installed with Composer. The modules directory allows you to install third-party modules locally, and then the app directory allows you to create local modules. This multiple levels of hierarchy gives you real power when it comes to distributing, overriding, and extending third-party code. And it's something that's genuinely hard to implement in platforms like Laravel or Symfony. Marco is a great framework choice when compared to platforms like Drupal, WordPress, or Magento, as they are each built for their respective industry niches, being content management, blogging, and commerce. And each one has its own quirks when it comes to overriding third-party code or needing to handle multiple client use cases, such as having a blog and commerce site optimally built within the same codebase. Due to its modular nature, Marco does not face any of these types of restrictions. Inside our app directory, we will go ahead and create a new directory for our module and we will just name this one hello. We will then create a related composer.json file for our module. This file will contain the name of our module, which is app/hello, the PSR-4 designation for defining our class namespace, as well as the extra Marco property, which defines this directory as a Marco module. Notice how everything just follows PHP standards and there's nothing proprietary going on. Why would we invent some sort of domain-specific language when PHP already gives us what we need out of the box? That's actually one of the core premises of Marko. And this composer.json file is actually all that is needed for creating a module in Marko. No XML or YAML config, commands to run, or registration processes needed. Marko automatically finds and loads the module just based on the single file. We will place all of our custom code within a new src directory, and we can define controllers within it within a controllers directory. Let's go ahead and create a new PHP file, and this will be named HelloController, and within it we will just define a basic controller that returns a response. Now to wire this controller up, all we need to do is define a PHP attribute directly on the method that handles this request. Request. We will use the GET attribute, which designates this as an HTTP GET request, and then we will pass in the root path as a string as a parameter. And this GET attribute comes from the Marco framework. This combination tells Marco how to respond to requests made to the root path of our site. Now when we visit localhost, we'll see our "Hello from Marco" response. Notice how we didn't even need to extend a base controller class or do anything like that. The code is just PHP. Now that you've seen the basics, let me show you what a real app built on Marko would look like. I made a simple demo app called MarkoTalk, which is also available on GitHub, so you can see how more of the advanced features work, like plugins, event observers, and database functionality. Let's open up the app directory, which again contains local_modules, and then we will open up the message module. We'll see that we have resources, source, tests, as well as a module.php file. Let's go ahead and open up this module.php file. Marko includes a comprehensive dependency injection layer. It was also inspired by Magento, but without needing to define any XML. Again, we just let PHP shine here, which keeps the DI implementation incredibly simple, yet still very powerful. The module.php file is optional, and it defines how classes bind to interfaces by returning an array with this bindings key. This includes assignments, which just relate interfaces to classes. This adds a powerful class resolution process without the complexity. There are also many other features we can tap into, such as executing specific authorization gating policies when our app boots up. Within our resources directory is a views directory, and that contains our views. For the initial release, I included templates for Latte, which is a very PHP-friendly rendering engine, that uses a basic syntax for displaying templates and also has automatic CSRF protection. But because everything in Marko is a module, you don't even need to use Latte if you don't want to. Let's go ahead and check out the package within Marko that is related to this, which is Vue Latte. And if we check out its composer.json file, we will see that it requires the Marko Vue package. This is a Marko package that defines some basic config for for the view layer without being too opinionated. This allows implementations of the view package to do their own thing without being handcuffed by the framework. Also, if you inspect the view_latte package, you'll see that the implementation is super minimal. This makes it easy to implement just about any template rendering engine in Marko just by adding a few classes. It's still shocking to me to see how simple this implementation is, but it just works. Relatedly, let's say we use Composer to remove the Marco Vue Latte package. Okay, Vue Latte is now completely gone. If we then try to visit a page in Marco, we will see that we get a loud error. You'll see that Marco complains loudly about not having a Vue implementation because Marco depends on interfaces rather than concrete classes. Removing a package gives you a clear error telling you what's missing. Rather than a silent failure buried in a stack trace. Also, having loud errors like this in place is a core premise of Marco. Not only will it tell you the error, but it will also tell you why the error occurred and how to fix it, along with the ability to copy the error to the clipboard for easy debugging. Speaking of loud errors, if you're intrigued by this framework so far, a like on this video is a great way to help other PHP developers to bind it to. Marco also makes it very easy to plug in the third-party code by using plugins and event observers. But rather than using XML or some other proprietary format, class and method modification is done directly with PHP attributes right where you write the code. Let's go ahead and check out the plugin related to Markdown. This Markdown plugin class has a save function which takes the message body and parses the Markdown into HTML. We use the plugin attribute on the class to define the target class that we're intercepting. Then the before attribute goes on the method to mark it as a before interceptor, which makes it run before the target method executes. Before plugins can do a few things depending on what you return. Return null to let the call proceed as normal, return an array to replace the arguments passed to the target method, or return any other non-null value to short circuit the entire call. Which makes the method never run and just return your value instead. Similarly, after works the same way for modifying the return value after method execution. You also have the option of passing in the method parameter on before or after plugins, which lets you target the specific method that you're plugging into. This gives you the flexibility to name your plugin whatever you wish. Let's now go ahead and check out the mention notification observer. We will see that the event observed is using the observer attribute and we are observing the mention detected event. The framework looks for the handle method, which then reacts to and has access to the related event. One common pattern in frameworks is defining data types in both a migration script and within a model. Marko replaces the need to do this by defining entities with PHP attributes. Let's head over to the entity directory and open up the message entity. Entities are representations of data objects, and the attribute defines the type, name, references, and more on that specific field. Entities are also treated as the single source of truth for object types. Once you install the database package and a related driver like MySQL or Postgres, the Marco tool automatically finds which packages are installed and exposes the related DB commands, including ones you're used to seeing in other frameworks such as migrate, rollback, and seed. The migrate command automatically creates and applies database migrations directly from your entity definitions, which always keeps your database tables in sync with your entities. This means that there is no reason to have hundreds of generated migration files that are hard to track over time, that deviate from the single source of truth being your entity model. And it's built around the same approach as everything else. It's all just PHP. There is so much more in this release that I just can't fit into a single video. Feel free to explore the codebase, mess around with Marko, and open issues on GitHub if you run into anything. Marko is at version 0.1 today, so it's in very early beta. I'm sure it'll have some bugs, but I absolutely want Marko to be a framework that PHP developers actually enjoy using. I know the core foundation is solid and I'm sure you'll love what you see once you dig into it. I want to hear what you think, so please drop some comments below. What stands out to you? What do you want covered in the next video? What interests you about Marko and where can this project go? I'm building this in the open and your feedback will directly help shape what Marko becomes. Be sure to visit marko.build to read the full documentation that's available on day one. And also join the Slack group to chat with me and all of the other developers that will start building on the Marko framework. See you in the next one.










