Object Calisthenics
Adapted for PHP
A little about me
●
●
●
●
●
●
●

Bedford, VA
Radford University
9+ Years PHP
8+ Years @ DE
Hampton Roads PHP
Comics
Every ...
Object Calisthenics
●
●
●
●
●
●

First introduced by Jeff Bay
Based on Java Development
Guidelines Not Rules
Difficulty va...
Object Calisthenics #1

“Only one level of indentation per method”
#1 Only one level of indentation
public function validate(array $products)
{
$requiredFields = ['price', 'name', 'descript...
#1 Only one level of indentation
public function validate(array $products)
{
$requiredFields = ['price', 'name', 'descript...
#1 Only one level of indentation
public function validate(array $products)
{
$requiredFields = ['price', 'name', 'descript...
#1 Only one level of indentation
Key Benefits
● Encourages Single Responsibility
● Increase Re-use
Object Calisthenics #2

“Do not use the ‘else’ keyword”
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exception...
#2 Do not use the ‘else’ keyword
Key Benefits
● Helps avoid code duplication
● Easier to read (single path)
● Reduces cycl...
Object Calisthenics #3

“Wrap all primitives and Strings”
*If there is behavior
#3 Wrap primitives and Strings
class Item
{
final public static function find($id)
{
if (!is_string($id) || trim($id) == '...
#3 Wrap primitives and Strings
class Item
{
final public static function find($id)
{

validation with
id

if (!is_string($...
#3 Wrap primitives and Strings
class Id
{
private $value;
final public function __construct($value)
{
if (!is_string($valu...
#3 Wrap primitives and Strings
class Id
{
private $value;
final public function __construct($value)

validation
encapsulat...
#3 Wrap primitives and Strings
class Item
{
final public static function find(Id $id)
{
// do find ...
}

final public sta...
#3 Wrap primitives and Strings
class Item
{
final public static function find(Id $id)
{
// do find ...
}

final public sta...
#3 Wrap primitives and Strings
Warning!!!
Using large amounts of objects will increase
the memory footprint of PHP.
#3 Wrap primitives and Strings
Key Benefits
● Type Hinting
● Encapsulation of operations
Object Calisthenics #4

“Use first class collections”
#4 Use first class collections
A class that contains a collection should
contain no other instance variables.
#4 Use first class collections
class Items implements Iterator
{
private $items = [];
final public function add(Item $item...
#4 Use first class collections
Key Benefits
● Implements collection operations
● Uses SPL interfaces
● Easier to merge col...
Object Calisthenics #5

“One object operator per line”
#5 One object operator per line

$this->manager->getConfig()->getSegment()->setName('foo');
#5 One object operator per line
Properties are harder
to mock

$this->manager->getConfig()->getSegment()->setName('foo');
#5 One object operator per line
Properties are harder
to mock

$this->manager->getConfig()->getSegment()->setName('foo');
...
#5 One object operator per line
Properties are harder
to mock

May indicate feature
envy

$this->manager->getConfig()->get...
#5 One object operator per line
Key Benefits
● Readability
● Easier Testing
● Easier to Debug
Object Calisthenics #6

“Do not abbreviate”
#6 Do not abbreviate
public function getPage() { ... }
public function startProcess() { ... }
$smp->process("index");
#6 Do not abbreviate
get what page
from where?
public function getPage() { ... }
public function startProcess() { ... }
$s...
#6 Do not abbreviate
get from where?

public function getPage() { ... }
public function startProcess() { ... }
$smp->proce...
#6 Do not abbreviate
get what page
from where?
public function getPage() { ... }

Use clearer names:
renderHomePage()
down...
#6 Do not abbreviate
get what page
from where?
public function getPage() { ... }

Use clearer names:
renderHomePage()
down...
#6 Do not abbreviate
get what page
from where?
public function getPage() { ... }

Use clearer names:
renderHomePage()
down...
#6 Do not abbreviate
Key Benefits
● Clearer communication and maintainability
● Indicates underlying problems
Object Calisthenics #7

“Keep classes small”
#7 Keep classes small
200 lines per class (including docblocks)
10 methods per class
15 classes per namespace (folder)
#7 Keep classes small
Key Benefits
● Single Responsibility
● Objective and clear methods
● Slimmer namespaces
Object Calisthenics #8

“Limit the number of instance variables in a
class”
#8 Limit instance variables in a class
class Client
{
private $_adapter;
private $_cache;
private $_logger;

// ...
}

Lim...
#8 Limit instance variables in a class
Key Benefits
● Shorter dependency list
● Easier Mocking for unit tests
Object Calisthenics #9

“Use getters/setters”
#9 Use Getters and Setters
class Tally
{
public $total = 0;

public function add($amount)
{
$this->total += $amount;
}
}

...
#9 Use Getters and Setters
class Tally
{
public $total = 0;

public function add($amount)
{
$this->total += $amount
}
}

$...
#9 Use Getters and Setters
class Tally
{
public $total = 0;

public function add($amount)
{
$this->total += $amount
}
}

$...
#9 Use Getters and Setters
class Tally

total cannot be “reset”

{
private $total = 0;
public function add($amount)
{
$thi...
#9 Use Getters and Setters
Warning!!!
Excessive setters and getters can be just as
bad as public properties
#9 Use Getters and Setters
Key Benefits
● Injector operations
● Encapsulation of transformations
● Encourages Open/Closed ...
Recap
1.
2.
3.
4.
5.
6.
7.
8.
9.

One level of indentation per method
Don’t use the else keyword
Wrap primitives that have...
Object Calisthenics
http://www.bennadel.com/resources/uploads/2012/ObjectCalisthenics.pdf

@chadicus78
@hrphpmeetup

Give ...
Upcoming SlideShare
Loading in...5
×

Object Calisthenics Adapted for PHP

400

Published on

The 9 "rules" of Object Calisthenics adapted for PHP development

0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
400
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
6
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Object Calisthenics Adapted for PHP

  1. 1. Object Calisthenics Adapted for PHP
  2. 2. A little about me ● ● ● ● ● ● ● Bedford, VA Radford University 9+ Years PHP 8+ Years @ DE Hampton Roads PHP Comics Every Hulk Issue
  3. 3. Object Calisthenics ● ● ● ● ● ● First introduced by Jeff Bay Based on Java Development Guidelines Not Rules Difficulty varies Write Better Code Adapted for PHP
  4. 4. Object Calisthenics #1 “Only one level of indentation per method”
  5. 5. #1 Only one level of indentation public function validate(array $products) { $requiredFields = ['price', 'name', 'description']; $valid = true; foreach ($products as $rawProduct) { $fields = array_keys($rawProduct); foreach ($requiredFields as $requiredField) { if (!in_array($requiredField, $fields)) { $valid = false; } } } return $valid; }
  6. 6. #1 Only one level of indentation public function validate(array $products) { $requiredFields = ['price', 'name', 'description']; $valid = true; 0 foreach ($products as $rawProduct) { 1 $fields = array_keys($rawProduct); foreach ($requiredFields as $requiredField) { 2 if (!in_array($requiredField, $fields)) { 3 $valid = false; } } } return $valid; }
  7. 7. #1 Only one level of indentation public function validate(array $products) { $requiredFields = ['price', 'name', 'description']; $valid = true; foreach ($products as $rawProduct) { $valid = $valid && $this->validateSingle($rawProduct, $requiredFields); } return $valid; } public function validateSingle(array $product, array $requiredFields) { $fields = array_keys($product); return count(array_diff($requiredFields, $fields)) == 0; }
  8. 8. #1 Only one level of indentation Key Benefits ● Encourages Single Responsibility ● Increase Re-use
  9. 9. Object Calisthenics #2 “Do not use the ‘else’ keyword”
  10. 10. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } elseif (is_string($exception)) { if ($exceptionArgs === null) { throw new Exception($exception); } else { if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); } } else { throw new InvalidArgumentException('$exception was not a string, Exception or null'); } }
  11. 11. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } elseif (is_string($exception)) { if ($exceptionArgs === null) { throw new Exception($exception); } else { if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); } } else { throw new InvalidArgumentException('$exception was not a string, Exception or null'); } }
  12. 12. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } elseif (is_string($exception)) { if ($exceptionArgs === null) { throw new Exception($exception); } else { if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); } } else { throw new InvalidArgumentException('$exception was not a string, Exception or null'); } }
  13. 13. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { return early throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } elseif (is_string($exception)) { if ($exceptionArgs === null) { throw new Exception($exception); } else { if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); } } else { throw new InvalidArgumentException('$exception was not a string, Exception or null'); } }
  14. 14. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } if (is_string($exception)) { if ($exceptionArgs === null) { throw new Exception($exception); } else { if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); } } else { throw new InvalidArgumentException('$exception was not a string, Exception or null'); }
  15. 15. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); reverse condition } if (is_string($exception)) { if ($exceptionArgs === null) { throw new Exception($exception); } else { if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); } } else { throw new InvalidArgumentException('$exception was not a string, Exception or null'); }
  16. 16. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } if (!is_string($exception)) { throw new InvalidArgumentException('$exception was not a string, Exception or null'); } if ($exceptionArgs === null) { throw new Exception($exception); } else { if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); } }
  17. 17. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } if (!is_string($exception)) { throw new InvalidArgumentException('$exception was not a string, Exception or null'); } if ($exceptionArgs === null) { throw new Exception($exception); } else { if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); } } return early
  18. 18. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } if (!is_string($exception)) { throw new InvalidArgumentException('$exception was not a string, Exception or null'); } if ($exceptionArgs === null) { throw new Exception($exception); } if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); }
  19. 19. #2 Do not use the ‘else’ keyword public function ensure($valueToEnsure, $valueToCheck, $exception = null, array $exceptionArgs = null) { if ($valueToEnsure === $valueToCheck) { return $valueToCheck; } if ($exception === null) { throw new Exception("'{$valueToEnsure}' did not equal '{$valueToCheck}'"); } if (!is_string($exception)) { throw new InvalidArgumentException('$exception was not a string, Exception or null'); } if ($exceptionArgs === null) { throw new Exception($exception); } if (array_key_exists($exception, self::$_exceptionAliases)) { $exception = self::$_exceptionAliases[$exception]; } $reflectionClass = new ReflectionClass($exception); throw $reflectionClass->newInstanceArgs($exceptionArgs); }
  20. 20. #2 Do not use the ‘else’ keyword Key Benefits ● Helps avoid code duplication ● Easier to read (single path) ● Reduces cyclomatic complexity
  21. 21. Object Calisthenics #3 “Wrap all primitives and Strings” *If there is behavior
  22. 22. #3 Wrap primitives and Strings class Item { final public static function find($id) { if (!is_string($id) || trim($id) == '') throw new InvalidArgumentException('$id must be a non-empty string'); // do find ... } final public static function create($id, array $data) { if (!is_string($id) || trim($id) == '') throw new InvalidArgumentException('$id must be a non-empty string'); // do create ... } }
  23. 23. #3 Wrap primitives and Strings class Item { final public static function find($id) { validation with id if (!is_string($id) || trim($id) == '') throw new InvalidArgumentException('$id must be a non-empty string'); // do find ... } final public static function create($id, array $data) validation with id { if (!is_string($id) || trim($id) == '') throw new InvalidArgumentException('$id must be a non-empty string'); // do create ... } }
  24. 24. #3 Wrap primitives and Strings class Id { private $value; final public function __construct($value) { if (!is_string($value) || trim($value) == '') throw new InvalidArgumentException('$value must be a non-empty string'); $this->value = $value; } final public function getValue() { return $this->value; } }
  25. 25. #3 Wrap primitives and Strings class Id { private $value; final public function __construct($value) validation encapsulated in constructor { if (!is_string($value) || trim($value) == '') throw new InvalidArgumentException('$value must be a non-empty string'); $this->value = $value; } final public function getValue() { return $this->value; } }
  26. 26. #3 Wrap primitives and Strings class Item { final public static function find(Id $id) { // do find ... } final public static function create(Id $id, array $data) { // do create ... } }
  27. 27. #3 Wrap primitives and Strings class Item { final public static function find(Id $id) { // do find ... } final public static function create(Id $id, array $data) { // do create ... } } $id now guaranteed to be valid.
  28. 28. #3 Wrap primitives and Strings Warning!!! Using large amounts of objects will increase the memory footprint of PHP.
  29. 29. #3 Wrap primitives and Strings Key Benefits ● Type Hinting ● Encapsulation of operations
  30. 30. Object Calisthenics #4 “Use first class collections”
  31. 31. #4 Use first class collections A class that contains a collection should contain no other instance variables.
  32. 32. #4 Use first class collections class Items implements Iterator { private $items = []; final public function add(Item $item) { // do add ... } final public function filter(array $filters) { // do filter ... } final public function merge(Items $items) { // do merge ... } }
  33. 33. #4 Use first class collections Key Benefits ● Implements collection operations ● Uses SPL interfaces ● Easier to merge collections and not worry about member behavior in them
  34. 34. Object Calisthenics #5 “One object operator per line”
  35. 35. #5 One object operator per line $this->manager->getConfig()->getSegment()->setName('foo');
  36. 36. #5 One object operator per line Properties are harder to mock $this->manager->getConfig()->getSegment()->setName('foo');
  37. 37. #5 One object operator per line Properties are harder to mock $this->manager->getConfig()->getSegment()->setName('foo'); Previous call could return null
  38. 38. #5 One object operator per line Properties are harder to mock May indicate feature envy $this->manager->getConfig()->getSegment()->setName('foo'); Previous call could return null
  39. 39. #5 One object operator per line Key Benefits ● Readability ● Easier Testing ● Easier to Debug
  40. 40. Object Calisthenics #6 “Do not abbreviate”
  41. 41. #6 Do not abbreviate public function getPage() { ... } public function startProcess() { ... } $smp->process("index");
  42. 42. #6 Do not abbreviate get what page from where? public function getPage() { ... } public function startProcess() { ... } $smp->process("index");
  43. 43. #6 Do not abbreviate get from where? public function getPage() { ... } public function startProcess() { ... } $smp->process("index"); ???
  44. 44. #6 Do not abbreviate get what page from where? public function getPage() { ... } Use clearer names: renderHomePage() downloadHomePage() public function startProcess() { ... } $smp->process("index"); ???
  45. 45. #6 Do not abbreviate get what page from where? public function getPage() { ... } Use clearer names: renderHomePage() downloadHomePage() public function startProcess() { ... } $smp->process("index"); ??? Use a thesaurus: fork, create, begin, open
  46. 46. #6 Do not abbreviate get what page from where? public function getPage() { ... } Use clearer names: renderHomePage() downloadHomePage() public function startProcess() { ... } $smp->process("index"); ??? Use a thesaurus: fork, create, begin, open Easy understanding, complete scope: $siteMapProcessor
  47. 47. #6 Do not abbreviate Key Benefits ● Clearer communication and maintainability ● Indicates underlying problems
  48. 48. Object Calisthenics #7 “Keep classes small”
  49. 49. #7 Keep classes small 200 lines per class (including docblocks) 10 methods per class 15 classes per namespace (folder)
  50. 50. #7 Keep classes small Key Benefits ● Single Responsibility ● Objective and clear methods ● Slimmer namespaces
  51. 51. Object Calisthenics #8 “Limit the number of instance variables in a class”
  52. 52. #8 Limit instance variables in a class class Client { private $_adapter; private $_cache; private $_logger; // ... } Limit: 2 - 5
  53. 53. #8 Limit instance variables in a class Key Benefits ● Shorter dependency list ● Easier Mocking for unit tests
  54. 54. Object Calisthenics #9 “Use getters/setters”
  55. 55. #9 Use Getters and Setters class Tally { public $total = 0; public function add($amount) { $this->total += $amount; } } $tally = new Tally(); $tally->add(1); // some other code ... $tally->total = -1; // some other code ... $tally->add(1); echo $tally->total . PHP_EOL;
  56. 56. #9 Use Getters and Setters class Tally { public $total = 0; public function add($amount) { $this->total += $amount } } $tally = new Tally(); $tally->add(1); // some other code ... $tally->total = -1 // some other code ... $tally->add(1); echo $tally->total . PHP_EOL; total can be changed without this instance knowing
  57. 57. #9 Use Getters and Setters class Tally { public $total = 0; public function add($amount) { $this->total += $amount } } $tally = new Tally(); total can be changed without this instance knowing $tally->add(1); // some other code ... $tally->total = -1 // some other code ... $tally->add(1); echo $tally->total . PHP_EOL; Causes unexpected results
  58. 58. #9 Use Getters and Setters class Tally total cannot be “reset” { private $total = 0; public function add($amount) { $this->total += $amount; } public function getTotal() { return $this->total; } } $tally = new Tally(); $tally->add(1); $tally->add(1); echo $tally->getTotal() . PHP_EOL; No unexpected results
  59. 59. #9 Use Getters and Setters Warning!!! Excessive setters and getters can be just as bad as public properties
  60. 60. #9 Use Getters and Setters Key Benefits ● Injector operations ● Encapsulation of transformations ● Encourages Open/Closed principle
  61. 61. Recap 1. 2. 3. 4. 5. 6. 7. 8. 9. One level of indentation per method Don’t use the else keyword Wrap primitives that have behavior Use first class collections One object operator per line Do not abbreviate Keep classes small Limit instance variables in a class Use Getters and setters
  62. 62. Object Calisthenics http://www.bennadel.com/resources/uploads/2012/ObjectCalisthenics.pdf @chadicus78 @hrphpmeetup Give us feedback at http://de12bcon.herokuapp.com/survey
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×