escaping dependency hell v2
TRANSCRIPT
ESCAPINGDEPENDENCY HELL
Michael Haeuslmann - PHP UG Munich 2016Source: Escape from Hell Constantine by Rommeu
ESCAPING GERMANWINTER?
Michael Haeuslmann - PHP UG Munich 2016Source: Escape from Hell Constantine by Rommeu
PART I: DISCUSSION
WHAT IS DEPENDENCY HELL?
WHAT IS A DEPENDENCY?
... a dependency signifies a supplier/clientrelationship between model elements
where the modification of a supplier mayimpact the client model elements
- UML Specification v2.5
WHY SHOULD WE CARE?
MICHAEL HAEUSLMANN (@MICHAELHAEU)FREELANCER (PHPRAGMATIC.COM)
married, love to travel, board games, ...developing in PHP for ~8 yearsprofessional work in PHP (mostly legacy apps)open source for all the exciting stuff
Buuuut ...
I don't want to be a freelancer anymore ...
... sooooo ...
WE KNOW WE'RE INDEPENDENCY HELL.
HOW DO WE ESCAPE?
COMPLEX VS. COMPLICATEDBubble Sort?
COMPLEX VS. COMPLICATEDBubble Sort?
COMPLEX VS. COMPLICATEDCache Invalidation?
COMPLEX VS. COMPLICATEDCache Invalidation?
COMPLEX VS. COMPLICATEDCache Invalidation?
COMPLEX VS. COMPLICATEDCache Invalidation?
COMPLEX VS. COMPLICATEDCache Invalidation?
COMPLEX VS. COMPLICATEDWeb Applications?
COMPLEX VS. COMPLICATEDWeb Applications?
COMPLEX VS. COMPLICATEDIt doesn't have to be complicated
WHAT DO WE DO ABOUT IT?Read the code and take notes? We don't want to do everything ourselves
use a tool → Managing dependencies in the right way helps with: Complexity, maintenance and understandability
PART II: ANALYSE
WHAT SHOULD DEPENDENCY ANALYSIS TELLUS?
Where should we start refactoring? Why does [SomeClass] always break? What does our architecture actually look like? Is our architecture the way it should be?
TOOLSPHP DEPEND BY MANUEL PICHLER
many metrics many metrics hard to maintain too vague
TOOLSPHP DEPEND BY MANUEL PICHLER
TOOLS
detects even the sneakiest dependencies
generates dependency visualizations
hackable (grep, sed, awk, ...)
for the nerds: written using functional style
supports PHP 5.2 to 7.1
FEATURES Text For quick feedback, debugging, UNIX pipes etc. Visualisations (UML & DSM & dot) Detailed dependency & architectural analysis (Metrics)
DEPHPEND - TEXT OUTPUT$> wget http://phar.dephpend.com/dephpend.phar$> composer global require dephpend/dephpend:devmaster$> dephpend help text _ _____ _ _ _____ _ | | | __ \| | | | __ \ | | __| | ___| |__) | |__| | |__) |__ _ __ __| | / _` |/ _ \ ___/| __ | ___/ _ \ '_ \ / _` | | (_| | __/ | | | | | | | __/ | | | (_| | \__,_|\___|_| |_| |_|_| \___|_| |_|\__,_| version 0.1
Usage: text [options] [] ()...
$> dephpend text ~/workspace/dephpend/src
Mihaeu\PhpDependencies\Util\AbstractMap > Mihaeu\PhpDependencies\Util\CollectionMihaeu\PhpDependencies\Util\DI > Mihaeu\PhpDependencies\Analyser\Analyser...
(*) make sure XDebug is not enabled or use php -n
DEPHPEND - TEXT OUTPUT$> dephpend text ~/workspace/dephpend/src noclasses | sort
Mihaeu\PhpDependencies\Analyser > Mihaeu\PhpDependencies\DependenciesMihaeu\PhpDependencies\Analyser > Mihaeu\PhpDependencies\OSMihaeu\PhpDependencies\Analyser > Mihaeu\PhpDependencies\UtilMihaeu\PhpDependencies\Analyser > PhpParserMihaeu\PhpDependencies\Analyser > PhpParser\NodeMihaeu\PhpDependencies\Analyser > PhpParser\Node\ExprMihaeu\PhpDependencies\Analyser > PhpParser\Node\NameMihaeu\PhpDependencies\Analyser > PhpParser\Node\StmtMihaeu\PhpDependencies\Analyser > PhpParser\NodeVisitor...
$> dephpend text ~/workspace/dephpend/src noclasses \ | grep e 'Analyser > .*OS'
Mihaeu\PhpDependencies\Analyser > Mihaeu\PhpDependencies\OS
DEPHPEND - TEXT OUTPUTMake it yours!
#!/usr/bin/env sh
php build/dephpend.phar text ~/workspace/dephpend/src noclasses | grep \
e 'Analyser > .*OS' \
e 'OS > .*Analyser'
if [ "$?" eq 0 ]; then
echo 'Architecture violation!'
exit 1
fi
DEPHPEND - TEXT OUTPUT
<?php
$output = shell_exec('dephpend text ' .'~/workspace/myMVCFramework/src noclasses');$constraints = [ 'Model.* > .*View', 'View.* > .*Model',];
if (preg_match('/('.implode(')|(', $constraints).')/x', $output)) echo 'Architecture violation'.PHP_EOL; exit(1);
DEPHPEND - UML (SORT OF)dePHPend packages
$> dephpend uml ~/workspace/dephpend/src noclasses output=uml.png
DEPHPEND - UML (SORT OF)Symfony components
$> php d memory_limit=512M dephpend.phar uml \
~/workspace/symfony/src/Symfony/Component \
noclasses \
depth 3 \ # Symfony\Component\HttpKernel\Controller\ArgumentResolver \ # → Symfony\Component\HttpKernel
excluderegex='/Test/' \ # Symfony\Component\HttpFoundation\Tests
output=uml.png
DEPHPEND - UML (SORT OF)Symfony components
DEPHPEND - UML (SORT OF)Symfony HTTP Kernel
DEPENDENCY STRUCTURE MATRIX(DSM)
large graphs are unreadable same data as graph diagrams (e.g. UML class diagram) quick overview for large apps
DEPHPEND DSM
NDEPEND EXAMPLE
PART III: FIX
WHAT DO WE WANT?
We want code which is ... ... easier to understand ... easier to maintain ... easier to test
OBSCURE/NASTY DEPENDENCIES
Some dependencies cannot be detected by any tool (or developer):
Fake collections (array) Overuse of scalar values (int, string, ...) Temporal dependencies, etc.
BE EXPLICIT!
Implicit dependencies are hard to understand/manage!function sendNewsletter( array $customers, string $message);
function sendNewsletter( CustomerCollection $customers, Message $message);
WHICH ALLOCATES MORE RAM?ARRAY
$customer = [ 'id' => 123, 'name' => 'John Doe', 'city' => 'Example City',];
CUSTOM CLASSclass Customer /** @var int */ private $id;
/** @var string */ private $name;
/** @var string */ private $city;
Winner (less RAM): custom classProof: http://www.slideshare.net/patrick.allaert/php-data-structures-and-the-impact-of-php-7-on-
them-php-days-2015
DON'T MAKE ME LOOK IT UP/** * @param mixed $email * @param string|Email|array $email */function addEmail($email) if (is_array($email)) // pray everything inside the array actually is an email foreach ($email as $singleEmail) addEmail($singleEmail); else if (is_string($email)) addEmail(new Email($email)); else if ($email instanceof Email) this>emails[] = email; else throw new InvalidArgumentException('Bad argument type');
DON'T MAKE ME LOOK IT UPfunction addEmail(Email $email) $this>emails[] = $email;
function addEmailString(string $email) $this>addEmail(new Email($email));
function addEmailArray(array $emails) foreach ($emails as $email) /** @var Email $email */ if (is_string($email)) $this>addEmailstring($email); else if ($email instanceof Email) $this>addEmail($email); else throw new InvalidArgumentException('Bad argument type');
function addEmailCollection(EmailCollection $emails) $emails>each(function (Email $email) $this>addEmail($email); );
DON'T MAKE ME LOOK IT UPfunction addEmail(Email $email) $this>emails[] = $email;
function addEmailCollection(EmailCollection $emails) $emails>each(function (Email $email) $this>addEmail($email); );
function addEmailString(string $email) $this>addEmail(new Email($email));
function addEmailArray(array $emails) foreach ($emails as $email) /** @var Email $email */ if (is_string($email)) $this>addEmailstring($email); else if ($email instanceof Email) $this>addEmail($email); else throw new InvalidArgumentException('Bad argument type');
PRINCIPLES OF OO: SOLID
Single responsibility principle Open/closed principle Liskov substitution principle Interface segregation principle Dependency inversion principle
DEPENDENCY INVERSIONBAD:
class CustomerRepository public function __construct() $this>db = new MySQLDatabase(new DefaultConfig());
BETTER:class CustomerRepository public function __construct(MySQLDatabase $db) $this>db = $db;
GOOD:class CustomerRepository public function __construct(Database $db) $this>db = $db;
Easier to understand and test, less likely to break
DEPENDENCY INJECTIONCONTAINERS
$container = new Pimple\Container();
$container['cstmrrepo'] = function ($database) return new CustomerRepository($database);;
$cstmrRepo = $container['cstmrrepo'];
Too easy? obscure dependencies using YAMLadd another 3rd party libraryadd overhead by parsing meta format/sarcasm
AVOID IMPLICIT DEPENDENCIES IN FAVOR OFEXPLICIT ONES
// why not do it yourselves?class DependencyInjectionContainer
// eager load public function getCustomerRepository() : CustomerRepository return new CustomerRepository($this>otherDeps);
// OR: lazy load public function getCustomerRepository() : CustomerRepository if (null === $this>customerRepository) $this>customerRepository = new CustomerRepository($this>otherDeps); return $this>customerRepository;
$dependencyInjectionContainer>getCustomerRepository();
SERVICE LOCATORclass CustomerRepository public function __construct(ServiceLocator $serviceLocator) $this>db = $serviceLocator>getDb();
new CustomerRepository($serviceLocator);
Same or similar implementation, but different usage:
CHOOSE DEPENDENCY INJECTIONCONTAINERS OVER SERVICE LOCATORS
Service Locator provides access to everythingMight as well use globals...(but not really)Target class knows more than it should= more reasons to change (=break)Always choose REAL Dependency Injection
'MEMBER SYMONFY HTTPKERNEL?
WHERE TO GO FROM HERE?improve visualizations command for constraint checks caching better Test & CI integration
Contributions, ideas, feedback, bug reports are welcome!
QUESTIONS
???LINKS
http://www.slideshare.net/michael-haeuslmann/escaping-dependency-hell
https://dephpend.comhttps://github.com/mihaeu/dephpend
HOW DOES IT WORK?
STATIC ANALYSIS
Transform the code to make parsing easierInfer direct types (require, new, type hints, ...)Infer indirect types (DICs, ...)
DYNAMIC ANALYSIS
profile the applicationtrack function tracescollect all possible input values
STATIC ANALYSISEasy right?
use SomeNamespace\SomeClass;
class MyClass extends MyParent implements MyInterface
/**
* @return AnotherClass
*/
public function someFunction(SomeClass $someClass) : AnotherClass
StaticClass::staticFunction();
return new AnotherClass();
STATIC ANALYSISOr is it?
class MyClass
public function someMethod($dependency)
return call_user_func('globalFunction', $dependency);
or:
DYNAMIC ANALYSISXDebug to the rescue!
; php.ini
zend_extension=/path/to/xdebug.so
[xdebug]xdebug.profiler_enable = 1xdebug.auto_trace=1xdebug.collect_params=1xdebug.collect_return=3xdebug.collect_assignments=1xdebug.trace_format=1xdebug.trace_options=1
# https://github.com/mihaeu/dephpend/blob/develop/bin/dephpendphptrace dynamic=/path/to/tracefile.xt S localhost:8080
DYNAMIC ANALYSISTRACE START [20161019 16:59:03]1 0 0 0.000189 363984 main 1 /home/mike/workspace/dephpend/bin/dephpend 0 0
2 1 0 0.000209 363984 get_declared_classes 0 /home/mike/workspace/dephpend/bin/dephpend 80
...
11 211058 0 3.503452 4856528 strpos 0 /home/mike/workspace/dephpend/vendor/symfony/console/Formatter/OutputFormatter.php 177 2 string(15111) string(2)
...3 200813 R long 3.504303 238672TRACE END [20161019 16:59:07]
DYNAMIC ANALYSIS
Parse the trace file and merge with static results
$> dephpend text src \ dynamic=/path/to/tracefile.xt \ filterfrom=YourNamespace \ excluderegex='(Test)|(Mock)'
There are no secrets at runtime!
@ m i c h a e l h a e u - PHP UG Munich 2016 - h t t p s : / / d e p h p e n d . c o m
CREDITSNDepend
Slide Coder Deviant Art
PDepend Artifacts scaled DSM
http://www.ndepend.com/docs/dependency-structure-matrix-dsm
http://rommeu.deviantart.com/art/Escape-from-Hell-Constantine-204115701
pdepend.org
https://erik.doernenburg.com/2010/04/dependency-structure-matrix/