object calisthenics adapted for php
DESCRIPTION
The 9 "rules" of Object Calisthenics adapted for PHP developmentTRANSCRIPT
Object CalisthenicsAdapted for PHP
A little about me
● Bedford, VA● Radford University● 9+ Years PHP● 8+ Years @ DE● Hampton Roads PHP● Comics● Every Hulk Issue
Object Calisthenics
● First introduced by Jeff Bay● Based on Java Development● Guidelines Not Rules● Difficulty varies● Write Better Code● Adapted for PHP
Object Calisthenics #1
“Only one level of indentation per method”
#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;
}
#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;
}
01
23
#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;
}
#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 $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');
}
}
#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');
}
}
#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');
}
}
#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');
}
}
return early
#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');
}
}
#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');
}
}
reverse condition
#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);
}
}
#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
#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);
}
#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);
}
#2 Do not use the ‘else’ keyword
Key Benefits
● Helps avoid code duplication● Easier to read (single path)● Reduces cyclomatic complexity
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) == '')
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 ...
}
}
#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 ...
}
}
validation with id
validation with id
#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;
}
}
#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;
}
}
validation encapsulated in constructor
#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 ...
}
}
#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.
#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 collectionsclass 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 ...
}
}
#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
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
$this->manager->getConfig()->getSegment()->setName('foo');
Properties are harder to mock
#5 One object operator per line
$this->manager->getConfig()->getSegment()->setName('foo');
Properties are harder to mock
Previous call could return null
#5 One object operator per line
$this->manager->getConfig()->getSegment()->setName('foo');
Properties are harder to mock
Previous call could return null
May indicate feature envy
#5 One object operator per line
Key Benefits
● Readability● Easier Testing● Easier to Debug
Object Calisthenics #6
“Do not abbreviate”
public function getPage() { ... }
public function startProcess() { ... }
$smp->process("index");
#6 Do not abbreviate
public function getPage() { ... }
public function startProcess() { ... }
$smp->process("index");
#6 Do not abbreviateget what page from where?
#6 Do not abbreviate
public function getPage() { ... }
public function startProcess() { ... }
$smp->process("index");
get from where?
???
#6 Do not abbreviate
public function getPage() { ... }
public function startProcess() { ... }
$smp->process("index");
get what page from where?
???
Use clearer names:renderHomePage()downloadHomePage()
#6 Do not abbreviate
public function getPage() { ... }
public function startProcess() { ... }
$smp->process("index");
get what page from where?
???
Use clearer names:renderHomePage()downloadHomePage()
Use a thesaurus:fork, create, begin, open
#6 Do not abbreviate
public function getPage() { ... }
public function startProcess() { ... }
$smp->process("index");
get what page from where?
???
Use clearer names:renderHomePage()downloadHomePage()
Use a thesaurus:fork, create, begin, open
Easy understanding, complete scope:$siteMapProcessor
#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 class15 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 classclass Client
{
private $_adapter;
private $_cache;
private $_logger;
// ...
}
Limit: 2 - 5
#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 Settersclass 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;
#9 Use Getters and Settersclass 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
#9 Use Getters and Settersclass 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
Causes unexpected results
#9 Use Getters and Settersclass Tally
{
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;
total cannot be “reset”
No unexpected results
#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 principle
Recap
1. One level of indentation per method2. Don’t use the else keyword3. Wrap primitives that have behavior4. Use first class collections5. One object operator per line6. Do not abbreviate7. Keep classes small8. Limit instance variables in a class9. Use Getters and setters
Object Calisthenics
http://www.bennadel.com/resources/uploads/2012/ObjectCalisthenics.pdf
@chadicus78@hrphpmeetup
Give us feedback athttp://de12bcon.herokuapp.com/survey