Slide 1

Slide 1 text

Magento 2: PHP Development Best Practices Gabriel Somoza @gabriel_somoza https://somoza.be

Slide 2

Slide 2 text

https://somoza.be Gabriel Somoza Consultant Ecommerce Architect

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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?

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

https://somoza.be Follow Magento’s “Core Coding Standards” 1

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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)

Slide 9

Slide 9 text

https://somoza.be Understand the Object Manager 2

Slide 10

Slide 10 text

https://somoza.be 9 Understand the OM Dependency Injection/Inversion Lifecycle Management Compilation Scopes Special Patterns Translations Magento’s Container: the Object Manager (OM)

Slide 11

Slide 11 text

https://somoza.be 9 Understand the OM 2pm - Manipulating Magento: make it do what you want (Yoke Puts)

Slide 12

Slide 12 text

https://somoza.be Build Bridges (integrations) 3

Slide 13

Slide 13 text

https://somoza.be 3 Build Bridges The Stockbase M2 Module not the topic only for examples

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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)

Slide 17

Slide 17 text

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)

Slide 18

Slide 18 text

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)

Slide 19

Slide 19 text

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)

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

https://somoza.be 3 Build Bridges ✓ Use Private Packagist (commercial) or Satis for private packages

Slide 23

Slide 23 text

https://somoza.be Write Defensive Components 4

Slide 24

Slide 24 text

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() {}

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

https://somoza.be 4 Defensive Components # etc/di.xml 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

Slide 29

Slide 29 text

https://somoza.be 4 Defensive Components # etc/di.xml 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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

https://somoza.be 4 Defensive Components Fail Faster use Webmozart\Assert\Assert; Fail Fast(er) composer require webmozart/assert

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

https://somoza.be 4 Defensive Components Photo by Dale Gillard / CC BY Extremely Defensive PHP - Marko Pivetta Defensive Programming - Wikipedia More on Defensive Programming

Slide 49

Slide 49 text

https://somoza.be Write Tests 5

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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!)

Slide 52

Slide 52 text

https://somoza.be 5 Automated Tests Automated Builds

Slide 53

Slide 53 text

https://somoza.be 5 Automated Tests Code Quality

Slide 54

Slide 54 text

https://somoza.be 5 Automated Tests ✓ Show badges for build status and code quality shields.io

Slide 55

Slide 55 text

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)

Slide 56

Slide 56 text

https://somoza.be Don’t Forget to Document! 6

Slide 57

Slide 57 text

https://somoza.be 6 Documentation

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

https://somoza.be Leverage Packagist 7

Slide 62

Slide 62 text

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).')'; ≈

Slide 63

Slide 63 text

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 ≈

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

https://somoza.be Stay Up to Date 8

Slide 66

Slide 66 text

https://somoza.be phptherightway.com alanstorm.com devdocs.magento.com inchoo.net/category/magento-2 u.magento.com/magento-2 alankent.me/category/magento

Slide 67

Slide 67 text

https://somoza.be @gabriel_somoza gsomoza [email protected] https://somoza.be Questions?

Slide 68

Slide 68 text

https://somoza.be @gabriel_somoza gsomoza [email protected] https://somoza.be Thank You!