Whether you write custom modules for individual clients or sell extensions on the Magento Marketplace - Magento 2 (M2) extension development is a big change from M1.
I started writing Fooman’s M2 extensions almost two years ago, and have clocked up countless hours of wins, fails (sometimes painful), trial and error earning my M2 developer stripes. Many nights were spent coding until the wee hours of the morning, and I’d fall into bed only to be woken by my newborn baby.
Learning the ins and outs of M2 has been a labour of love. But these hours have taught me a lot that I want to share with the #realmagento community. New M2 features have added a couple of shortcuts to writing smart code, but this does require a shift in thinking from Magento 1.
Dear #realmagento, what are your #Magento2 development best practices? Select answers added w/credit to my preso at #zendcon2016— Ben Marks (@benmarks) October 18, 2016
Triggered by the above tweet I started writing down what I had learned so far. Here are my six take-aways of how you can write better Magento 2 extensions.
1. Work with the framework
While Magento 1 and 2 share a number of similarities, a lot has moved on with Magento 2. Familiarise yourself with the new platform and its capabilities. The system architecture has been overhauled and it’s now easier than ever to use current design patterns and tools with Magento.
Early on I looked closely at how things are implemented in the Magento_Customer module. This module has generally been mentioned by Magento as reflecting the new way of doing things in M2. However, don't be surprised if other Magento modules look different - Magento hasn't had a chance to refactor all modules yet and things are still changing.
One example I learned from the Customer module is to prefer Repository classes (their interfaces to be exact) for retrieving entities. While you can still use Collections like in M1, I expect their use to be discouraged over time.
When working with the M2 framework, keep track of the individual Magento Core modules that you use in your extension’s
composer.json require section. Add them to the
module.xml’s sequence node to make sure your extension’s config files are loaded after the core module.
Doing this will allow you to use Composer - something that will make your life much easier when dealing with future Magento upgrades.
2. Use service contracts
Magento has put a lot of effort into creating service contracts (implemented via php interfaces) for their code. The service contract approach brings flexibility to the system (you can replace implementations with your own) while also defining boundaries (the new implementations need to follow the same expectations as the original implementation, enforced via the interface). Use Magento's existing service contracts as often as you can. For example, use
Magento\Sales\Api\Data\OrderInterface in your code, rather than
You can create service contracts for your own code too. Simply create an interface (by convention in the Api folder of your extension) then instruct M2 to use your actual implementation of the interface via a preference in your di.xml file.
I created service contracts for the supply of a pdf ready to be attached to an email in our free Fooman Email Attachments M2 extension. If the free Fooman Print Order Pdf M2 extension is also installed (since many people install these together), it will then supply the actual implementation for the order pdf to be sent attached to the email:
<virtualType name="fooman_emailattachments_order_pdf_renderer" type="\Fooman\EmailAttachments\Model\PdfRenderer"> <arguments> <argument name="pdfRenderer" xsi:type="object">\Fooman\PrintOrderPdf\Model\Pdf\Order</argument> </arguments> </virtualType> <type name="\Fooman\EmailAttachments\Observer\AbstractSendOrderObserver"> <arguments> <argument name="pdfRenderer" xsi:type="object">fooman_emailattachments_order_pdf_renderer</argument> </arguments> </type>
Fooman Pdf Customiser on M2 uses the same mechanism to swap out the default pdfs with our customised pdfs instead.
Why did I do this? Email Attachments does not know of the existence of either the Print Order Pdf or Pdf Customiser extensions. Keeping the responsibility decoupled like this increases the maintainability of the code.
Generally, you want to aim for your constructor to only be using service contracts from any module (including your own) or classes from your own namespace. At the current stage of M2 this isn’t always possible - but it’s where things are heading. Don’t be afraid to let Magento know where there are gaps that are currently not covered by service contracts like Vinai did here.
3. Use plugins
Wherever possible, use plugins (preferably on methods that have an
@api annotation) to implement your functionality. This will reduce the scope for possible conflicts with other extensions, and lessen support issues later down the track (something we can all appreciate).
M2 plugins are a lot more targeted than what we have in M1, which only had the crude method of overwriting complete classes. M2 plugins allow you to target changing/adding/removing functionality on the method level.
I decided to use the plugin approach to replace the output of one core method that we need to change in our free Google Analytics + extension.
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\GoogleAnalytics\Block\Ga"> <plugin name="fooman_googleanalytics_plus_ga" type="\Fooman\GoogleAnalyticsPlus\Plugin\Ga"/> </type> </config>
Using a plugin here allows us to surgically replace the output of only one method. All other core functionality is retained and means fewer chances things will break on future Magento upgrades.
4. Write tests
Compared to Magento 1, M2 makes automated testing a whole lot easier. Out of the box, M2 comes with unit tests, integration and functional tests - plus frameworks to use them. Aim to use all types for your own code. Tests will not only help you to write better code, but they’ll save you time when testing compatibility with new versions of Magento later down the track. If you’re planning to sell your extension to multiple end users, tests will also assist people in evaluating the quality of your code.
If you do need to compromise on what tests to write, I’m a firm believer in prioritising functional tests. This is for the simple reason that functional tests are aligned with what the end customer uses, i.e. using the site via a browser. A functional test provides you with more certainty than a unit test that your code (as experienced by the end user) does what it is designed to do.
When I started M2 development at Fooman, I knew that Magento’s code base would continuously change. At the time of writing, there have been ten 2.0.x releases and three 2.1.x releases so far. Investing time upfront in automated installs and running the various testing frameworks (unit tests, integration tests and functional tests) is already allowing us to move faster. I’m confident that all those hours we invested setting this is up already starting to pay off.
5. Use modular code
Consider creating separate modules or extensions with highly targeted functionality, rather than bundling together loosely related features which are not relevant to the majority of your users. Pick the specific features which will be most helpful to your target customer, then spend your energy making those features outstanding.
Splitting code into logical units of functionality is a great way to increase code reusability. Start by extracting framework independent code into your own libraries. Magento 2 supports modularisation by using composer for dependency management which allows you to join packages easily. You can always decide later to create a metapackage that brings together your targeted modules to form a broader product.
Keeping modules smaller allows more fine-tuned control to install only the features which are needed by the store, without burdening the performance of the store with features that aren’t required. It also makes the extension itself easier to maintain in the future, as less code is involved.
For M2, I decided to de-bundle one of the most popular features of our Email Attachments extension and keep it free. Fooman Print Order Pdf was born - it’s a simple module which lets you print an Order Confirmation pdf document for single or multiple orders. This extension has literally one feature - but this feature is powerful and in demand.
Even if it wasn’t free, I can see that this single-feature extension would fill a basic need for a lot of people who don’t need the advanced features provided by a specific pdf customisation/creation tool.
6. Seek customer feedback to improve your code
This one’s not a specific M2 tip but is as relevant as ever. I always advocate taking a lean approach when developing extensions. It makes sense to show your work early on to confirm that you’re on the right track, before investing precious resources in developing a 100% complete version and later realising there just wasn’t demand for it.
Seek feedback as early as possible from the target user - whether this is your client or early adopters of a community extension. Is the basic functionality going to solve their problem? Are any logical core features missing? Was anything confusing to set up or use? This could save hours of wasted time developing features people aren’t interested in, and make your product stronger. Plus, if you’re selling extensions, seeking early feedback can also help to create a good first impression for those all-important first reviews.
Development priorities at Fooman have always been influenced by customer requests. Moving to M2, all extensions had to be rewritten from the ground up, and every single M1 file was changed. To show we’re serious about Magento 2 it was essential to release our popular products early. Fooman Pdf Customiser for M2 was released with core pdf customisation functionality, but fewer features than our bestselling M1 version. Since then, we’ve added plenty of new features based on customer feedback. If we’d waited to develop our full list of features before releasing the extension, we would have missed out on those customer insights which led to those features being prioritised over others.
Look for trends in feedback and be open-minded - early feedback could lead you to your best code or product tweaks. But also trust your instinct and don't allow yourself to get distracted from the core extension purpose and target customer.
Writing code which takes advantage of new M2 tools will initially take longer as you learn the new platform - there’s no getting around this. But once you’ve worked with M2 for a while, going back to developing with M1 will feel like a step back in time. Hopefully these tips will give you a few shortcuts to take advantage of the new framework when writing M2 extensions.