<<

RETHINKING NAMESPACING IN PHP ARNE BLANKERTS SEBASTIAN HEUER Principal Consultant & Co-Founder Developer Advocate thePHP.cc die kartenmacherei NAMESPACING TODAY PSR-4 FOLLOW STRUCTURE COMPOSER AUTOLOADING BACK IN THE DAYS class Foo {} class Vendor_Package_Foo {} AUTOLOADING? include './Vendor/Package/Foo.';

$foo = new Vendor_Package_Foo(); set_include_path(dirname(__FILE__) . '/src'); function __autoload($classname) { $filename = str_replace('_', '/', $classname) . '.php'; include $filename; }

$foo = new Vendor_Package_Foo(); set_include_path(dirname(__FILE__) . '/src'); spl_autoload_register(function($classname) { $filename = str_replace('_', '/', $classname) . '.php'; include $filename; });

$foo = new Vendor_Package_Foo(); class Vendor_Package_Foo {}

Vendor/Package/Foo.php Vendor/Package/Subfolder/Foo.php class Vendor_Package_Subfolder_Foo {} THE INTRODUCTION OF NAMESPACES ADDED CONVENIENCE Vendor\Package; class Foo {} namespace Vendor\Package; use OtherVendor\Lib\Bar; class Foo {} NAME COLLISIONS STILL POSSIBLE namespace Vendor\Package; use Vendor\Lib\Bar; use OtherVendor\Lib\Bar; class Foo {} namespace Vendor\Package; use Vendor\Lib\Bar; use OtherVendor\Lib\Bar as Baz; class Foo {} PSR-0 namespace Vendor\Package; class Foo_Loader {}

Vendor/Package/Foo/Loader.php Vendor/Package/Subfolder/Foo/Loader.php namespace Vendor\Package\Subfolder; class Foo_Loader {} PSR-4 namespace Vendor\Package; class Foo_Loader {} src/Foo_Loader.php src/Subfolder/Foo_Loader.php namespace Vendor\Package\Subfolder; class Foo_Loader {} NOTHING REALLY CHANGED NAMESPACES DICTATED BY AUTOLOADING https://www.youtube.com/watch?v=Nsjsiz2A9mg WHY DOES IT TELL US HOW IT WAS MADE? WHY DOESN'T IT TELL US WHAT IT DOES? NAMESPACE VS. PACKAGE PACKAGES IN import vendor.package; import vendor.package.subpackage; import vendor.package.*; namespace Vendor\Package\Controllers; use Vendor\Package\Views\FooView; use Vendor\Package\Repositories\FooRepository; class FooController {

} NAMESPACE = DOMAIN? namespace Vendor\Package\MyDomain; class FeatureController {

} namespace Vendor\Package\MyDomain; use Vendor\Package\MyDomain\Views\FeatureView; use Vendor\Package\MyDomain\Repositories\FeatureRepository; class FeatureController {

} namespace Vendor\Package\MyDomain\Feature; use Vendor\Package\MyDomain\Feature\View; use Vendor\Package\MyDomain\Feature\Repository; class Controller {

} namespace Vendor\Package\MyDomain\Feature; use Vendor\Package\MyDomain\Feature\View; use Vendor\Package\MyDomain\Feature\Repository; class Controller {

} src/MyDomain/Feature/controllers/Controller.php src/MyDomain/Feature/views/View.php src/MyDomain/Feature/repositories/Repository.php BUT WHAT ABOUT PSR-4?

2010 2012 COMPOSER CLASSMAP $ composer install --optimize-autoloader $ composer install --classmap-authoritative

$ composer dump-autoload --optimize $ composer dump-autoload --classmap-authoritative { "autoload": { "classmap": ["src/"] } } PHPAB $ phpab -o src/autoload src phpab 1.24.1 - Copyright (tag">C) 2009 - 2018 by Arne Blankerts and Contributors

Scanning directory src

Autoload file src/autoload generated. '/DomainA/foo.php', 'vendor\\domainb\\barbar' => '/DomainB/BarBar.php', 'vendor\\domainb\\foo' => '/DomainB/Foo.php' ); } $cn = strtolower($class); if (isset($classes[$cn])) { require __DIR__ . $classes[$cn]; } }, true, false ); // @codeCoverageIgnoreEnd NAMESPACE DEPENDENCY CHECKS https://github.com/mihaeu/dephpend $ tools/dephpend text src Vendor\Shop\Basket\BasketService --> Vendor\Shop\Catalog\Product Vendor\Shop\Basket\BasketService --> Vendor\Shop\Basket\Basket Vendor\Shop\Http\Basket\AddToBasketCommand --> Vendor\Shop\Basket\BasketService Vendor\Shop\Http\Basket\AddToBasketCommand --> Vendor\Shop\Catalog\Product

$ tools/dephpend text --no-classes src Vendor\Shop\Basket --> Vendor\Shop\Catalog Vendor\Shop\Http\Basket --> Vendor\Shop\Basket Vendor\Shop\Http\Basket --> Vendor\Shop\Catalog

$output = shell_exec('php -n tools/dephpend text src'); $constraints = [ 'Basket' => ['Catalog'] ];

$violations = [];

$lines = explode("\n", $output); foreach ($lines as $line) { if (strpos($line, '-->') === false) { continue; } list($left, $right) = explode('-->', $line); $left = trim($left); $right = trim($right);

foreach ($constraints as $key => $namespaces) { if (strpos($left, $key) === false) { continue; } foreach ($namespaces as $namespace) { if (strpos($right, $namespace) !== false) { if (!isset($violations[$left])) { $violations[$left] = []; } $violations[$left][] = $right; } } } } if (count($violations) > 0) { echo "Architecture violations found! \n\n"; } foreach ($violations as $class => $usedClasses) { echo "Class $class depends on: \n"; foreach ($usedClasses as $usedClass) { echo " $usedClass \n"; } echo "\n"; } $ php ci/namespace-check.php Architecture violations found!

Class Vendor\Shop\Basket\BasketService depends on: Vendor\Shop\Catalog\Product

Class Vendor\Shop\Http\Basket\AddToBasketCommand depends on: Vendor\Shop\Catalog\Product

THANK YOU!

ARNE BLANKERTS SEBASTIAN HEUER @arneblankerts @belanur @thePHPcc @inside.kartenmacherei