dutch php conference 2013: distilled
DESCRIPTION
A couple of our team members attended DPC13, and this is a subset of some of the interesting talks we wanted to share with the rest of the team.TRANSCRIPT
Dutch PHPConference:Distilled
Chris Saylor
Stephen Young
@cjsaylor
@young_steveo
Focus of this talkPHP by the NumbersEmergent DesignWhat's new in PHP 5.5Unbreakable Domain Models
PHP by the Numbers
Measuring ComplexityCyclomatic complexityN-path complexity
Cyclomatic complexityThe cyclomatic complexity of a method is the count of the number
of independent decision points in the method, plus one for themethod entry.
It's a fancy term for measuring the complexity of a method.Decision points in a method increase complexity.(e.g. if, else, foreach, etc.)Plugins will calculate it for you.(PHP Mess Detector, JSHint, Grunt!)
The lower, the better1—4 low complexity, easy to maintain5—7 moderate complexity8—10 high complexity, hard to test11+ ?
N-path complexityThe number of acyclic execution paths through a function; an
objective measure of complexity related to the ease with whichsoftware can be comprehensively tested
acyclic execution paths?
It's a lot like cyclomatic complexity.It measures how many straight lines you can draw through amethod.A method with a single IF statement has an N-path of 2.A method with 2 IF statements has an N-path of 4.3 IF statements would make the N-path 8.
Another way to look at itThe value of a method's N-path complexity is equal to the numberof unit tests needed to ensure that you have 100% code coverage.
A "quick estimate" for N-Path:
2̂(cyclomaticComplexity - 1)
You have probably seen thisRunning Grunt!
Running "jshint:source" (jshint) task
Linting app/webroot/js/views/doesEverythingView.js...
ERROR
[L31:C38] W074: This function's cyclomatic complexity is too high. (18)
Things to avoidViolating the Single Responsibility Principle
A Controller method with logic to perform CRUD operations.A Product Model with logic for formatting currency.
Seperation of Concerns
Too complex: A single method that executes four businessrules to perform a task.Better: Four methods that each execute a single business rule.
Plan AheadIt's common to be handed a user story that is simple to describe butcomplex to implement.
Take time to break the logic into the smallest possible units beforewriting code.
Emergent Design withPHPSpec
Topics covered1. PHPSpec overview2. emergent design3. simple design & smells4. designing composition with mocks
What is PHPSpec?
it started as a port for rspec
ProblemPHP !== Ruby
in ruby everything is an object
and all objects are openAlso, monkey patching code at runtime is a typical practice in the Ruby world.
blowling.score.should eq(0)
an early PHPSpec syntax example
Trying to emulate ruby in PHP looks ugly
$this->spec($bowling->getScore())->shouldEqual(0);
PHPSpec's new goalsfun to work withdevelopment toollet's not get in the wayenforce TDDdo it the PHP way
Test Driven Developmentyellow — Write the test first
red — Implement the class/method; test is failing
green — Test is passing
How PHPUnit handles the Yellow stepPHPUnit 3.7.14 by Sebastian Bergmann.
PHP Fatal error: Class 'Markdown' not found in /Users/stephenyoung/Documents/projects/Lab/phpunit/tests/MarkdownTest.php on line 8
Fatal error: Class 'Markdown' not found in /Users/stephenyoung/Documents/projects/Lab/phpunit/tests/MarkdownTest.php on line 8
How PHPSpec handles the Yellow step
it does this for missing methods too.
> spec\Customer ✘ it converts plain text to html paragraphs Class Markdown does not exist. Do you want me to create it for you? [Y/n]
MockingIt suffices to say that mocking is painful in PHPUnit.
PHPSpec has a very easy-to-use Mocking library.
Too much to go into here.
That's Enough about PHPSpecIt's a pretty awesome tool. Go check it out.
Emergent Design
What is software design?
Software designis
the art of describing how to to solve a problem.
First learn design, then learn emergentdesign
Alan Kay on Messaging
"The key in making great and growable systems ismuch more to design how its modules
communicate rather than what their internalproperties and behaviors should be."
$this->person->getCar()->getEngine()->ignite();
Focusing on messaging makes code moreflexible
$this->person->startCar();
Software designis
the art of describing how to to solve problems with roles,responsibilities and messages
Big Design Up FrontIt's hard to change later.We need to think about things before developing.We need to make sure we don't miss anything.This is just the way we do it.
Y A G N I
61%of all requested features are actually
delivered
27%of all requested features are actually used
5% to 10%are responsible for realising the
envisioned benefits
we should design for the high priority items and make it easy tochange later.
Agile Software designis
the art of describing how to to solve problems with roles,responsibilities and messages
in a change-friendly way
Easier said than done?
1. Test2. Code3. Refactor4. Repeat
Use simple design rules to refactor1. All tests run and pass
2. Remove duplication
3. Remove opacity
4. Remove complexity
What is the result of this method in yourcode?
It is testable.
It is modular.
It is expressive.
It is simple.
What is the alternative?Viscosity
Immobility, Rigidity, Fragility
Unreadable
Complex
Simple design enables smell detection
Simple Design1. All tests run and pass
2. Remove duplication
3. Remove opacity
4. Remove complexity
smells1. Test smells?
2. DRY smells?
3. Opacity smells?
4. Complexity smells?
Test smellsLack of testsSetup is too complexUnclear exerciseNo expectation
DRY SmellsCopy PastaLogic duplicationDuplicated constantsAlternative classes with different interfaces
Opacity smellsNaming not in the domainName does not express intentFeature envyMethod too longMethod does more than one thing
Complexity smellsUnnecessary elseUnnecessary ifUnnecessary switchToo many passed arguments
Use design patterns to refactor
What can happen in a method?return a valuethrow an exceptiondelegatemodify state not the final behavior
print something we should probably delegate that too
Design delegation with mocks
start by defining behavior
internally delegate to another method
FinallyDefine new roleExtract collaborators using mocksMove behavior definition to new collaborator test
What's new in PHP 5.5
PerformancePerformance boost compared to 5.3 -> 5.4 is not as great as 5.4 ->
5.5
APC replacementZend's OPCache is now packaged with PHP 5.5 and APC is now
depricated
array_column $keyValues = [ ['key1' => 'val1', 'key2' => 'val2'], ['key1' => 'val3', 'key2' => 'val4'] ]; var_dump(array_column($keyValues, 'key1')); // yields array('val1', 'val3')
Foreach with list $test = [ [1, 2], [3, 4] ]; foreach ($test as list($a, $b)) { echo "a: $a; b: $b"; } // displays: // a: 1; b: 2 // a: 3; b: 4
Finally a finally! try { doSomeWork(); return true; } finally { cleanUpSomeStuff(); echo "I am reachable!"; } echo "I am not reachable :( ";
Generators function lotsOfRecords() { while ($row = $this->db->getNext()) { yield $row['id'] => $row; } } foreach (lotsOfRecords() as $id => $row) { // do stuff }
Password hashing API echo password_hash('somepassword', \PASSWORD_BCRYPT); // $2y$12$QjSH496pcT5CEbzjD/vtVeH03tfHKFy36d4J0Ltp3lRtee9HDxY3K password_verify( 'somepassword', '$2y$12$QjSH496pcT5CEbzjD/vtVeH03tfHKFy36d4J0Ltp3lRtee9HDxY3K' ); // true
Unbreakable Domain Models
Use objects as consistencyboundaries
class Customer { public function __construct($email) { if( /* ugly regex here */) { throw new \InvalidArgumentException(); } $this->email = $email; } }
Single Responsibility Principle class Email { private $email; public function __construct($email) { if( /* ugly regex here */) { throw new \InvalidArgumentException(); } $this->email = $email; } public function __toString() { return $this->email; } }
Customer class is now tighter class Customer { /** @var Email */ private $email; public function __construct(Email $email) { $this->email = $email; } }
Encapsulate state andbehavior with Value Objects
A user story may be:
"A customer orders products and pays for them."
Procedural $order = new Order; $order->setCustomer($customer); $order->setProducts($products); $order->setStatus(Order::UNPAID); // ... $order->setPaidAmount(500); $order->setPaidCurrency(‘EUR’); $order->setStatus(Order::PAID);
Improve it with object forconsistency
$order = new Order; $order->setCustomer($customer); $order->setProducts($products); $order->setStatus( new PaymentStatus(PaymentStatus::UNPAID) ); $order->setPaidAmount(500); $order->setPaidCurrency(‘EUR’); $order->setStatus( new PaymentStatus(PaymentStatus::PAID) );
Improve it with moreconsistency
$order = new Order; $order->setCustomer($customer); $order->setProducts($products); $order->setStatus( new PaymentStatus(PaymentStatus::UNPAID) ); $order->setPaidMonetary( new Money(500, new Currency(‘EUR’)) ); $order->setStatus( new PaymentStatus(PaymentStatus::PAID) );
Even more $order = new Order($customer, $products); // set PaymentStatus in Order::__construct() $order->setPaidMonetary( new Money(500, new Currency(‘EUR’)) ); $order->setStatus( new PaymentStatus(PaymentStatus::PAID) );
Getting ridiculous now $order = new Order($customer, $products); $order->pay( new Money(500, new Currency(‘EUR’)) ); // set PaymentStatus in Order#pay()
Encapsulation throughspecification
"I want to give a discount to a customer that has at least 3 orders."
interface CustomerSpecification { /** @return bool */ public function isSatisfiedBy(Customer $customer); }
class CustomerIsPremium implements CustomerSpecification { private $orderRepository; public function __construct( OrderRepository $orderRepository ) {...} /** @return bool */ public function isSatisfiedBy(Customer $customer) { $count = $this->orderRepository->countFor($customer); return $count >= 3; } } $customerIsPremium = new CustomerIsPremium($orderRepository) if($customerIsPremium->isSatisfiedBy($customer)) { // send special offer }
CreditsPHP By the numbers - Anthony FerraraEmergent Design with phpspec - Marcello DuarteUnbreakable Domain Models - Mathias VerraesLet's have a look at PHP 5.5 - Julien Pauli