Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Magento 2: PHP Development Best Practices

Gabriel Somoza
September 27, 2017

Magento 2: PHP Development Best Practices

A talk about some of the most relevant features of the Magento 2 architecture and how they relate to PHP development best-practices, with a focus on do's and dont's for those who are looking to get a jump-start on Magento2 development.

Gabriel Somoza

September 27, 2017
Tweet

More Decks by Gabriel Somoza

Other Decks in Programming

Transcript

  1. https://somoza.be 1. Understand how Magento 2 (M2) meets mainstream PHP


    2. Get up to speed with some of the jargon around M2 development
 3. Share 8 quick tips about how to build an open-source M2 component Goals for This Talk
  2. https://somoza.be 1. Magento Developers or modern PHP Developers
 2. Magento

    2 Developers
 1. Distributing M2 components
 2. Contributing to M2
 3. Building internal components Who is this talk (primarily) for?
  3. https://somoza.be 1 Coding Standards M2 DOCS: Best Practices for Extension

    Developers http://bit.ly/m2-best-practices SOLID Do not modify core Use the M2 framework Security Observers Reusable
  4. https://somoza.be 1 Coding Standards PSRs 1 through 4 Code Demarcation

    Standard DocBlock Standard JavaScript Coding Standard JavaScript DocBlock Standard jQuery Widget Standard LESS Coding Standard HTML Style Guide
  5. https://somoza.be 1 Coding Standards Must for core contributions Just a

    suggestion for everyone else,
 but please take it Kind-of-a-must for Magento Marketplace (see Extension Quality Program)
  6. https://somoza.be 9 Understand the OM Dependency Injection/Inversion Lifecycle Management Compilation

    Scopes Special Patterns Translations Magento’s Container: the Object Manager (OM)
  7. https://somoza.be 3 Build Bridges Sample Component Structure • Api •

    Block • Controller • etc • frontend • events.xml • backend • di.xml • module.xml • config.xml • Helper • view • composer.json • README.md • registration.php PSR-4 Namespace: Vendor\Package
  8. https://somoza.be 3 Build Bridges { "name": “stockbase/magento2-module", "type": "magento2-module", "license":

    [ "OSL-3.0", "AFL-3.0" ], "autoload": { "files": [ "registration.php" ], "psr-4": { “Stockbase\\Module\\": “." } } "require": { "php": “~7.0.0|~7.1.0|~7.2.0”, “magento/module-catalog": “~100.1.0”, // snip, ship ... "dividebv/phpdivideiq": “^1.5” } } Example composer.json ✓ Package name
  9. https://somoza.be 3 Build Bridges { "name": “stockbase/magento2-module", "type": "magento2-module", "license":

    [ "OSL-3.0", "AFL-3.0" ], "autoload": { "files": [ "registration.php" ], "psr-4": { “Stockbase\\Module\\": “." } } "require": { "php": “~7.0.0|~7.1.0|~7.2.0”, “magento/module-catalog": “~100.1.0”, // snip, ship ... "dividebv/phpdivideiq": “^1.5” } } Example composer.json ✓ Package name ✓ Component Type
 (module, theme, language)
  10. https://somoza.be 3 Build Bridges { "name": “stockbase/magento2-module", "type": "magento2-module", "license":

    [ "OSL-3.0", "AFL-3.0" ], "autoload": { "files": [ "registration.php" ], "psr-4": { “Stockbase\\Module\\": “." } } "require": { "php": “~7.0.0|~7.1.0|~7.2.0”, “magento/module-catalog": “~100.1.0”, // snip, ship ... "dividebv/phpdivideiq": “^1.5” } } Example composer.json ✓ Package name ✓ Component Type
 (module, theme, language)
  11. https://somoza.be 3 Build Bridges { "name": “stockbase/magento2-module", "type": "magento2-module", "license":

    [ "OSL-3.0", "AFL-3.0" ], "autoload": { "files": [ "registration.php" ], "psr-4": { “Stockbase\\Module\\": “." } } "require": { "php": “~7.0.0|~7.1.0|~7.2.0”, “magento/module-catalog": “~100.1.0”, // snip, ship ... "dividebv/phpdivideiq": “^1.5” } } Example composer.json ✓ Package name ✓ Magento 2 Dependencies ✓ Component Type
 (module, theme, language)
  12. https://somoza.be 3 Build Bridges { "name": “stockbase/magento2-module", "type": "magento2-module", "license":

    [ "OSL-3.0", "AFL-3.0" ], "autoload": { "files": [ "registration.php" ], "psr-4": { “Stockbase\\Module\\": “." } } "require": { "php": “~7.0.0|~7.1.0|~7.2.0”, “magento/module-catalog": “~100.1.0”, // snip, ship ... "dividebv/phpdivideiq": “^1.5” } } Example composer.json ✓ External libraries ✓ Package name ✓ Magento 2 Dependencies ✓ Component Type
 (module, theme, language)
  13. https://somoza.be 3 Build Bridges Domain Layer Bridge Layer Magento 2

    Your M2 Component Your Generic Domain or
 Web API Library Output Input XSDs APIs Events Provide APIs Events Consume 3rd Party Components Core Components Preferences Extend
  14. https://somoza.be 3 Build Bridges Domain Layer Bridge Layer Magento 2

    dividebv/phpdivideiq stockbase/magento2- module Response Request Events Provide APIs Events Consume 3rd Party Components Core Components Preferences Extend
  15. https://somoza.be 4 Defensive Components Hide Information Default to “Private” Visibility

    / Access Modifiers Photo by Dale Gillard / CC BY public $bar; protected $foo; private $baz; public function foo() {} protected function bar() {} private function baz() {}
  16. https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC

    BY Avoid Getter & Setter Swarm Private Access Modifiers
  17. https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC

    BY “Setters don’t mean anything, anyway Marco Pivetta (@ocramius) “Getter and setter methods are evil Allen Hollub, 2003 Private Access Modifiers
  18. https://somoza.be 4 Defensive Components class MyAbandonedCartEmailSender { /** * Gathers

    data and sends email * @return void */ public function process(CartInterface $cart) {} // Returns the email transport public function getEmailTransport(): EmailTransportInterface {} // Sets the email transport public function setEmailTransport( EmailTransportInterface $transport ) {} protected function calculateCartPriceForEmail(CartInterface $cart) {} } Photo by Dale Gillard / CC BY EVIL Private Access Modifiers Example
  19. https://somoza.be 4 Defensive Components # etc/di.xml <preference for=“Vendor\Package\Api\AbandonedCartEmailSenderInterface” type=“Vendor\Package\Service\MyEmailSender” />

    interface Api/AbandonedCartEmailSenderInterface { /** * Gathers data and sends email * @return void */ public function process(CartInterface $cart); } Photo by Dale Gillard / CC BY Private Access Modifiers ✓ Tip for OSS: abstract to a meaningful interface
  20. https://somoza.be 4 Defensive Components # etc/di.xml <preference for=“Vendor\Package\Api\AbandonedCartEmailSenderInterface” type=“Vendor\Package\Service\MyEmailSender” />

    interface Api/AbandonedCartEmailSenderInterface { /** * Gathers data and sends email * @return void */ public function process(CartInterface $cart); } Photo by Dale Gillard / CC BY Private Access Modifiers ✓ Convention: public component interfaces go in an “Api” folder
  21. https://somoza.be 4 Defensive Components interface Api/AbandonedCartEmailSenderInterface { /** * Gathers

    data and sends email * @return void */ public function process(CartInterface $cart); } Photo by Dale Gillard / CC BY Private Access Modifiers ✓ We also got rid of getters / setters with little meaning or value
  22. https://somoza.be 4 Defensive Components interface AbandonedCartEmailSenderInterface { /** * Gathers

    data and sends email * @return void */ public function process(CartInterface $cart); } Photo by Dale Gillard / CC BY Private Access Modifiers ✓ Rename public methods to something meaningful
  23. https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC

    BY Private Access Modifiers interface AbandonedCartEmailSenderInterface { /** * Gathers data and sends email * @return void */ public function sendForCart(CartInterface $cart); } ✓ Rename public methods to something meaningful
  24. https://somoza.be 4 Defensive Components interface AbandonedCartEmailSenderInterface { /** * Gathers

    data and sends email * @return void */ public function sendForCart(CartInterface $cart); } Photo by Dale Gillard / CC BY ✓ Suggestion: rename interfaces so they reflect their value, meaning or role to their users Private Access Modifiers
  25. https://somoza.be 4 Defensive Components interface SendsAbandonedCartEmails { /** * Gathers

    data and sends email * @return void */ public function sendForCart(CartInterface $cart); } Photo by Dale Gillard / CC BY ✓ Suggestion: rename interfaces so they reflect their value, meaning or role to their users Private Access Modifiers
  26. https://somoza.be 4 Defensive Components class MyEmailSender implements Api\SendsAbandonedCartEmails { private

    $transport; public function __construct( EmailTransportInterface $transport ) { $this->transport = $transport; } public function sendForCart(CartInterface $cart) { // ... $this->transport->send(/* ... */); } private function calculateCartPriceForEmail( CartInterface $cart ) { // ... } } Photo by Dale Gillard / CC BY Private Access Modifiers
  27. https://somoza.be 4 Defensive Components class MyEmailSender implements Api\SendsAbandonedCartEmails { private

    $transport; public function __construct( EmailTransportInterface $transport ) { $this->transport = $transport; } public function sendForCart(CartInterface $cart) { // ... $this->transport->send(/* ... */); } private function calculateCartPriceForEmail( CartInterface $cart ) { // ... } } Photo by Dale Gillard / CC BY ✓ Interface in 
 “Api” folder Private Access Modifiers
  28. https://somoza.be 4 Defensive Components class MyEmailSender implements Api\SendsAbandonedCartEmails { private

    $transport; public function __construct( EmailTransportInterface $transport ) { $this->transport = $transport; } public function sendForCart(CartInterface $cart) { // ... $this->transport->send(/* ... */); } private function calculateCartPriceForEmail( CartInterface $cart ) { // ... } } Photo by Dale Gillard / CC BY ✓ Default to 
 private visibility Private Access Modifiers ✓ Interface in 
 “Api” folder
  29. https://somoza.be 4 Defensive Components class MyEmailSender implements Api\SendsAbandonedCartEmails { private

    $transport; public function __construct( EmailTransportInterface $transport ) { $this->transport = $transport; } public function sendForCart(CartInterface $cart) { // ... $this->transport->send(/* ... */); } private function calculateCartPriceForEmail( CartInterface $cart ) { // ... } } Photo by Dale Gillard / CC BY ✓ Dependencies injected during construction Private Access Modifiers ✓ Interface in 
 “Api” folder ✓ Default to 
 private visibility
  30. https://somoza.be 4 Defensive Components class MyEmailSender implements Api\SendsAbandonedCartEmails { private

    $transport; public function __construct( EmailTransportInterface $transport ) { $this->transport = $transport; } public function sendForCart(CartInterface $cart) { // ... $this->transport->send(/* ... */); } private function calculateCartPriceForEmail( CartInterface $cart ) { // ... } } Photo by Dale Gillard / CC BY ✓ Public API now is meaningful
 and concise Private Access Modifiers ✓ Interface in 
 “Api” folder ✓ Default to 
 private visibility ✓ Dependencies injected during construction
  31. https://somoza.be 4 Defensive Components class MyEmailSender implements Api\SendsAbandonedCartEmails { private

    $transport; public function __construct( EmailTransportInterface $transport ) { $this->transport = $transport; } public function sendForCart(CartInterface $cart) { // ... $this->transport->send(/* ... */); } private function calculateCartPriceForEmail( CartInterface $cart ) { // ... } } Photo by Dale Gillard / CC BY ✓ We don’t yet know if this would be useful publicly Private Access Modifiers ✓ Interface in 
 “Api” folder ✓ Default to 
 private visibility ✓ Dependencies injected during construction ✓ Public API now is meaningful
 and concise
  32. https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC

    BY ✓ Prevent unnecessary BC breaks ✓ Encourage more expressive APIs ✓ Design better abstractions ✓ More simple tests Some benefits: Private Access Modifiers
  33. https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC

    BY ๏ Will not play nice with several OM features like Proxies / Plugins
 ๏ Might not be compatible with some Magento-specific debugging / profiling tools WARNING: “final” classes and functions The OM uses inheritance for many of its features. Private Access Modifiers
  34. https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC

    BY Photo by Dale Gillard / CC BY Fail Fast(er) composer require webmozart/assert
  35. https://somoza.be 4 Defensive Components use Webmozart\Assert\Assert; class Employee { public

    function __construct($id) { Assert::integer($id); Assert::greaterThan($id, 0); } } Fail Fast(er) composer require webmozart/assert Fail Faster
  36. https://somoza.be 4 Defensive Components use Webmozart\Assert\Assert; class Employee { public

    function __construct($id) { Assert::integer($id); Assert::greaterThan($id, 0); } } new Employee('foobar'); // => InvalidArgumentException: // Expected an integer. Got: string new Employee(-10); // => InvalidArgumentException: // Expected a value greater than 0. Got: -10 Fail Fast(er) composer require webmozart/assert Fail Faster
  37. https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC

    BY …and don’t forget to CATCH exceptions /** * Sends a test email */ public function execute() { try { $emailAddress = $this->getEmailParam(); $orders = $this->getOrders(); $this->mailSender->send($emailAddress, $orders); } catch (\InvalidArgumentException $e) { $this->logger->logException($e); $this->messageManager->addError($e->getMessage()); } } Fail Faster
  38. https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC

    BY Extremely Defensive PHP - Marko Pivetta Defensive Programming - Wikipedia More on Defensive Programming
  39. https://somoza.be 5 Automated Tests 1. Unit test: generously, or use

    TDD 2. Integration test: interactions (where unit tests are not enough)
 and data flows 3. Functional / UI test: 
 only “happy” / critical paths 4. Follow core testing standards Unit Functional UI by Mike Cohn
  40. https://somoza.be 5 Automated Tests ✓ Exclude them from “production” releases:


    
 # .gitattributes
 /Test export-ignore ✓ Distribute tests in your component’s source (free extra docs!)
  41. https://somoza.be 5 Automated Tests Awesome FREE Tools ๏ Travis CI

    ๏ ContinuousPHP ๏ Jenkins ๏ A few other Builds ๏ Scrutinizer ๏ Code Climate ๏ Codacy ๏ Your own Code Quality ๏ GrumPHP ๏ CodeSniffer ๏ Mage EQP ๏ PHP MD ๏ Many more Standards Next up - Automatically deploying magento2 without losing sleep (Ike Devolder)
  42. https://somoza.be 6 Documentation What goes in README.md? ✓ Build Status

    ✓ Description & Features ✓ Link to external docs ✓ Installation ✓ Requirements ✓ License ✓ Contributing
 Guidelines
  43. https://somoza.be 6 Documentation ✓ Build Status ✓ Description & Features

    ✓ Link to external docs ✓ Installation ✓ Requirements ✓ License ✓ Contributing
 Guidelines What goes in README.md?
  44. https://somoza.be 6 Documentation ✓ Build Status ✓ Description & Features

    ✓ Link to external docs ✓ Installation ✓ Requirements ✓ License ✓ Contributing
 Guidelines What goes in README.md?
  45. https://somoza.be 7 packagist.org $where .= "id IN ("; foreach($diff as

    $id) { $where .= "'$id'"; if($id != end($diff)) { $where .= ','; } } $where .= “)”; $where = 'id IN ('.implode(',', $diff).')'; ≈
  46. https://somoza.be 7 packagist.org if (in_array($inputParam, $allowedValues, true)) { // do

    something } else { throw new \InvalidArgumentException(sprintf( 'Value "%s" is not allowed.', $value )); } Assert::oneOf($inputParam, $allowedValues); // do something here ≈
  47. https://somoza.be 7 packagist.org Photo by Dale Gillard / CC BY

    Photo by Dale Gillard / CC BY webmozart/assert
 ramsey/uuid
 bookdown/bookdown
 mockery/mockery
 league/factory-muffin
 league/oauth2-client guzzlehttp/guzzle
 monolog/monolog
 facebook/graph-sdk
 jms/serializer
 phpmd/phpmd
 phpseclib/phpseclib