Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Escaping Dependency Hell

290 views

Published on

Discover, analyse and fix dependency and architectural problems in PHP applications.

Published in: Technology
  • Be the first to comment

Escaping Dependency Hell

  1. 1. ESCAPING DEPENDENCY HELL Michael Haeuslmann - IPC Munich 2016 Source: Escape from Hell Constantine by Rommeu
  2. 2. AGENDA 1. Discuss What is dependency hell? Why should we care? Are all dependencies created equal? 2. Analyse ( ) Dependencies Architecture 3. Fix Untestable code Dependency (mis-)management
  3. 3. MICHAEL HAEUSLMANN INDEPENDENT FREELANCER (PHPRAGMATIC.COM) married, love to travel, board games, arch-nerd, ... developing in PHP for ~8 years mostly legacy applications or open source professional work in PHP private projects in Java, JavaScript, Swi , ...
  4. 4. PART I: DISCUSS
  5. 5. WHAT IS DEPENDENCY HELL?
  6. 6. WHY SHOULD WE CARE? » If you can’t understand it, you can’t change it. « - Eric Evans
  7. 7. WHY SHOULD WE CARE? COMPLEX VS. COMPLICATED Big so ware systems are complex (many moving parts) Not all parts are complicated BUT mismanaging complexity leads to more complicated problems
  8. 8. WHAT ARE DEPENDENCIES? Cross-Team dependencies Team A requires results from Team B which requires results from Team C ... 3rd-party dependencies Composer packages etc. So ware dependencies Internal or external dependencies on class or package level
  9. 9. WHY DEPENDENCY MANAGEMENT TOOLS MATTER? Difficulty of any given task depends on its dependencies We don't want to do everything ourselves Managing dependencies in the right way helps with: Understanding Maintenance
  10. 10. PART II: ANALYSE
  11. 11. WHAT SHOULD DEPENDENCY ANALYSIS TELL US? 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?
  12. 12. TOOLS PHP DEPEND BY MANUEL PICHLER many metrics many metrics hard to maintain too vague
  13. 13. TOOLS PHP DEPEND BY MANUEL PICHLER
  14. 14. TOOLS made with ♥ for OSS @ University of Rosenheim generates UML and other dependency visualizations detects even the sneakiest dependencies hackable (grep, sed, awk, ...) for the nerds: written using functional style a lot more to come
  15. 15. FEATURES Text For quick feedback, debugging, UNIX pipes etc. UML & DSM Detailed dependency & architectural analysis (Metrics)
  16. 16. DEPHPEND - TEXT OUTPUT $> php dephpend.phar help text       _      _____  _    _ _____               _      | |    |  __ | |  | |  __              | |    __| | ___| |__) | |__| | |__) |__ _ __   __| |   / _` |/ _   ___/|  __  |  ___/ _  '_  / _` |  | (_| |  __/ |    | |  | | |  |  __/ | | | (_| |   __,_|___|_|    |_|  |_|_|   ___|_| |_|__,_| version 0.1   Usage:   text [options] [­­]  ()... $> php dephpend.phar text ~/workspace/dephpend/src MihaeuPhpDependenciesUtilAbstractMap ­­> MihaeuPhpDependenciesUtilCollection MihaeuPhpDependenciesUtilDI ­­> MihaeuPhpDependenciesAnalyserAnalyser ... (*) make sure XDebug is not enabled or use php -n
  17. 17. DEPHPEND - TEXT OUTPUT $> php dephpend.phar text ~/workspace/dephpend/src ­­no­classes | sort MihaeuPhpDependenciesAnalyser ­­> MihaeuPhpDependenciesDependencies MihaeuPhpDependenciesAnalyser ­­> MihaeuPhpDependenciesOS MihaeuPhpDependenciesAnalyser ­­> MihaeuPhpDependenciesUtil MihaeuPhpDependenciesAnalyser ­­> PhpParser MihaeuPhpDependenciesAnalyser ­­> PhpParserNode MihaeuPhpDependenciesAnalyser ­­> PhpParserNodeExpr MihaeuPhpDependenciesAnalyser ­­> PhpParserNodeName MihaeuPhpDependenciesAnalyser ­­> PhpParserNodeStmt MihaeuPhpDependenciesAnalyser ­­> PhpParserNodeVisitor ... $> php dephpend.phar text ~/workspace/dephpend/src ­­no­classes      | grep ­e 'Analyser ­­> .*OS' MihaeuPhpDependenciesAnalyser ­­> MihaeuPhpDependenciesOS
  18. 18. DEPHPEND - TEXT OUTPUT Make it yours! #!/usr/bin/env sh php build/dephpend.phar text ~/workspace/dephpend/src ­­no­classes | grep      ­e 'Analyser ­­> .*OS'      ­e 'OS ­­> .*Analyser' if [ "$?" ­eq 0 ]; then     echo 'Architecture violation!'     exit 1 fi
  19. 19. DEPHPEND - TEXT OUTPUT <?php $output = shell_exec('php dephpend.phar text '     .'~/workspace/myMVCFramework/src ­­no­classes'); $constraints = [     'Model.* ­­> .*View',     'View.*  ­­> .*Model', ]; if (preg_match('/('.implode(')|(', $constraints).')/x', $output)) {     echo 'Architecture violation'.PHP_EOL;     exit(1); }
  20. 20. DEPHPEND - UML (SORT OF) dePHPend packages $> php dephpend.phar uml ~/workspace/dephpend/src ­­no­classes ­­output=uml.png
  21. 21. DEPHPEND - UML (SORT OF) Symfony components $> php ­d memory_limit=512M dephpend.phar uml          ~/workspace/symfony/src/Symfony/Component          ­­no­classes                         ­­depth 3                            ­­exclude­regex='/Test/'             ­­output=uml.png
  22. 22. DEPHPEND - UML (SORT OF) Symfony HTTP Kernel
  23. 23. DEPENDENCY STRUCTURE MATRIX (DSM) same data as graph diagrams (e.g. UML class diagram) quick overview for large apps
  24. 24. NDEPEND EXAMPLE
  25. 25. DEPHPEND DSM:
  26. 26. PART III: FIX
  27. 27. OBSCURE/NASTY DEPENDENCIES Some dependencies cannot be detected by any tool (or developer): Fake collections Overuse of scalar values (int, string, ...) Temporal dependencies ...
  28. 28. WHY DO WE CARE? We want code which is ... ... easier to understand ... easier to maintain ... easier to test
  29. 29. BE EXPLICIT! What is explicit/implicit? Can your IDE provide assistance for it? (Ctrl + Le click or mouse over) Can you be sure it is what it says it is? function sendNewsletter(      array $customers,      string $message  );    function sendNewsletter(      CustomerCollection $customers,      Message $message  );
  30. 30. DON'T MAKE ME LOOK IT UP /**  * @var mixed $email  * @var 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');     } }
  31. 31. DON'T MAKE ME LOOK IT UP function 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);   }); }
  32. 32. PRINCIPLES OF OO: SOLID Single responsibility principle Open/closed principle Liskov substitution principle Interface segregation principle Dependency inversion principle
  33. 33. DEPENDENCY INVERSION BAD: 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;     } }
  34. 34. Easier to understand and test, less likely to break
  35. 35. DEPENDENCY INJECTION CONTAINERS $container = new PimpleContainer(); $container['cstmrrepo'] = function ($database) {     return new CustomerRepository($database); }; $cstmrRepo = $container['cstmrrepo']; Too easy? obscure dependencies using YAML add another 3rd party library add overhead by parsing meta format /sarcasm
  36. 36. AVOID IMPLICIT DEPENDENCIES IN FAVOR OF EXPLICIT 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();
  37. 37. SERVICE LOCATOR class CustomerRepository {     public function __construct(ServiceLocator $serviceLocator) {         $this­>db = $serviceLocator­>getDb();     } } new CustomerRepository($serviceLocator);
  38. 38. Same or similar implementation, but different usage: CHOOSE DEPENDENCY INJECTION CONTAINERS OVER SERVICE LOCATORS Service Locator provides access to everything Might as well use globals... (but not really) Target class knows more than it should = more reasons to change (=break) Always choose REAL Dependency Injection
  39. 39. WHERE TO GO FROM HERE? support more types of dependencies improve visualization caching CI integration php dephpend.phar test­features    [✓]  creating objects  [✓]  using traits  ...    [✗]  known variable passed into method without type hints ...                               Contributions, ideas, feedback, bug reports are welcome!
  40. 40. QUESTIONS ??? LINKS https://dephpend.com https://github.com/mihaeu/github https://pdepend.org
  41. 41. HOW DOES IT WORK? STATIC ANALYSIS Transform the code to make parsing easier Infer direct types (require, new, type hints, ...) Infer indirect types (DICs, ...) DYNAMIC ANALYSIS profile the application track function traces collect all possible input values
  42. 42. STATIC ANALYSIS Easy right? use SomeNamespaceSomeClass; class MyClass extends MyParent implements MyInterface {     /**      * @return AnotherClass      */     public function someFunction(SomeClass $someClass) : AnotherClass {         StaticClass::staticFunction();         return new AnotherClass();     } }
  43. 43. STATIC ANALYSIS Or is it? class MyClass {     public function someMethod($dependency) {         return call_user_func('globalFunction', $dependency);     } }
  44. 44. or: DYNAMIC ANALYSIS XDebug to the rescue! ; php.ini zend_extension=/path/to/xdebug.so [xdebug] xdebug.profiler_enable = 1 xdebug.auto_trace=1 xdebug.collect_params=1 xdebug.collect_return=3 xdebug.collect_assignments=1 xdebug.trace_format=1 xdebug.trace_options=1 # https://github.com/mihaeu/dephpend/blob/develop/bin/dephpend php­trace ­­dynamic=/path/to/trace­file.xt ­S localhost:8080
  45. 45. DYNAMIC ANALYSIS TRACE START [2016­10­19 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  238672 TRACE END   [2016­10­19 16:59:07]                     
  46. 46. DYNAMIC ANALYSIS Parse the trace file and merge with static results $> php dephpend.phar text src                        ­­dynamic=/path/to/trace­file.xt             ­­filter­from=YourNamespace                  ­­exclude­regex='(Test)|(Mock)' There are no secrets at runtime!

×