SlideShare uma empresa Scribd logo
1 de 92
Baixar para ler offline
PHP 4 ADULTS
OBJECT CALISTHENICS AND CLEAN CODE
GUILHERMEBLANCO
GUILHERMEBLANCO
Guilherme Blanco
MOTIVATION
▸ Readability
▸ Maintainability
▸ Reusability
▸ Testability
SUMMING UP…
CLEAN CODE
S T U P I D
SINGLETON
TIGHT COUPLING
UNTESTABILITY
PREMATURE OPTIMIZATION
INDESCRIPTIVE NAMING
DUPLICATION
S O L I D
SINGLE RESPONSIBILITY
OPEN/CLOSED PRINCIPLE
LISKOV SUBSTITUTION PRINCIPLE
INTERFACE SEGREGATION
DEPENDENCY INVERSION
LISKOV SUBSTITUTION
PRINCIPLE
interface Bird
{
public function setLocation($longitude, $latitude);
public function setHeight($height);
public function draw();
}
class Penguin implements Bird
{
public function setHeight($height)
{
// Do nothing
}
}
interface Bird
{
public function setLocation($longitude, $latitude);
public function draw();
}
interface FlightfulBird extends Bird 

{ 

public function setHeight($height);
}
DEPENDENCY
INVERSION PRINCIPLE
namespace DatingUserBundleEntity
{
class User
{
/** @var DatingUserBundleEntityImage */
protected $avatar;
}
}
namespace DatingMediaBundleEntity
{
class Image
{
/** @var DatingUserBundleEntityUser */
protected $owner;
} 

}
namespace DatingUserBundleEntity
{
class User
{
/** @var AvatarInterface */
protected $avatar;
}
interface AvatarInterface
{
// ...
}
}
namespace DatingMediaBundleEntity
{
use DatingUserBundleEntityAvatarInterface;
class Image implements AvatarInterface
{
/** @var DatingUserBundleEntityUser */
protected $owner;
} 

}
OBJECT
CALISTHENICS
RULE #1
ONLY ONE INDENTATION LEVEL PER
METHOD
public function validateForm($filters='', $validators='', $options='')
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if ($input->hasInvalid() || $input->hasMissing()) {
foreach ($input->getMessages() as $field => $messageList) {
foreach ($messageList as $message) {
if (strpos($message, "empty")) {
throw new Tss_FormException(
"The field {$field} cannot be empty!",
3,
"javascript:history.back();"
); 

} else {
throw new Tss_FormException(
"{$message}",
3,
"javascript:history.back();"
);
}
}
}
}
return $input;
}
1
2
3
4
public function validateForm($filters='', $validators='', $options='')
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if ($input->hasInvalid() || $input->hasMissing()) {
foreach ($input->getMessages() as $field => $messageList) {
foreach ($messageList as $message) {
if (strpos($message, "empty")) {
throw new Tss_FormException(
"The field {$field} cannot be empty!",
3,
"javascript:history.back();"
); 

} else {
throw new Tss_FormException(
"{$message}",
3,
"javascript:history.back();"
);
}
}
}
}
return $input;
}
Class prototype
EARLY RETURNS
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
foreach ($messageList as $message) {
if (strpos($message, "empty")) {
throw new Tss_FormException(
"The field {$field} cannot be empty!",
3,
"javascript:history.back();"
); 

} else {
throw new Tss_FormException(
"{$message}",
3,
"javascript:history.back();"
);
}
}
}
return $input;
}
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
foreach ($messageList as $message) {
if (strpos($message, "empty")) {
throw new Tss_FormException(
"The field {$field} cannot be empty!",
3,
"javascript:history.back();"
); 

} else {
throw new Tss_FormException(
"{$message}",
3,
"javascript:history.back();"
);
}
}
}
return $input;
}
1
2
3
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
foreach ($messageList as $message) {
if (strpos($message, "empty")) {
throw new Tss_FormException(
"The field {$field} cannot be empty!",
3,
"javascript:history.back();"
); 

} else {
throw new Tss_FormException(
"{$message}",
3,
"javascript:history.back();"
);
}
}
}
return $input;
}
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
foreach ($messageList as $message) {
$errorMessage = (strpos($message, "empty") === false)
? "The field {$field} cannot be empty!"
: $message;
throw new Tss_FormException(
$errorMessage,
3,
"javascript:history.back();"
);
}
}
return $input;
}
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
foreach ($messageList as $message) {
$errorMessage = (strpos($message, "empty") === false)
? "The field {$field} cannot be empty!"
: $message;
throw new Tss_FormException(
$errorMessage,
3,
"javascript:history.back();"
);
}
}
return $input;
}
1
2
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
foreach ($messageList as $message) {
$errorMessage = (strpos($message, "empty") === false)
? "The field {$field} cannot be empty!"
: $message;
throw new Tss_FormException(
$errorMessage,
3,
"javascript:history.back();"
);
}
}
return $input;
}
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
$message = array_shift($messageList);
$jsAction = "javascript:history.back();";
$errorMessage = (strpos($message, "empty") === false)
? "The field {$field} cannot be empty!"
: $message;
throw new Tss_FormException($errorMessage, 3, $jsAction);
}
return $input;
}
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
$message = array_shift($messageList);
$jsAction = "javascript:history.back();";
$errorMessage = (strpos($message, "empty") === false)
? "The field {$field} cannot be empty!"
: $message;
throw new Tss_FormException($errorMessage, 3, $jsAction);
}
return $input;
}
Logical groups
Variable interpolation
public function validateForm($filters=array(), $validators=array(), $options=array())
{
$data = $_POST;
$input = new Zend_Filter_Input($filters, $validators, $data, $options);
$input->setDefaultEscapeFilter(new Zend_Filter_StringTrim());
if (! ($input->hasInvalid() || $input->hasMissing())) {
return $input;
}
foreach ($input->getMessages() as $field => $messageList) {
$message = array_shift($messageList);
$jsAction = "javascript:history.back();";
$errorMessage = (strpos($message, "empty") === false)
? sprintf("The field %s cannot be empty!", $field)
: $message;
throw new Tss_FormException($errorMessage, 3, $jsAction);
}
return $input;
}
BENEFITS
▸ Single Responsibility Principle ("S" in SOLID)
▸ Reusability
RULE #2
NO "ELSE" KEYWORD
public function createPost($request) {
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if ($form->isValid()){
$repository = $this->getRepository('MyBundle:Post');
if (!$repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
} 

} else {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
}
public function createPost($request) {
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if ($form->isValid()){
$repository = $this->getRepository('MyBundle:Post');
if (!$repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
} 

} else {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
}
Type-casting
Coding standards
Separate into logical
groups.
Consider as paragraphs!
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if ($form->isValid()) {
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
} 

} else {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if ($form->isValid()) {
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
} 

} else {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if ($form->isValid()) {
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
} 

} else {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
}
UML
NORMAL VS. ALTERNATIVE FLOWS
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if ($form->isValid()) {
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
} 

} else {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if ($form->isValid()) {
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
} 

} else {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if ($form->isValid()) {
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
} 

} else {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if (! $form->isValid()) {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
}
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if (! $form->isValid()) {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
}
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if (! $form->isValid()) {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
if (! $repository->exists($entity)) {
$repository->save($entity);
return $this->redirect('create_ok');
} else {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
}
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if (! $form->isValid()) {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
if ($repository->exists($entity)) {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
}
$repository->save($entity);
return $this->redirect('create_ok');
}
public function createPost(Request $request)
{
$repository = $this->getRepository(‘MyBundle:Post');
$entity = new Post();
$form = new MyForm($entity);
$form->bind($request);
if (! $form->isValid()) {
$error = "Invalid fields";
return array('form' => $form, 'error' => $error);
}
if ($repository->exists($entity)) {
$error = "Post Title already exists";
return array('form' => $form, 'error' => $error);
}
$repository->save($entity);
return $this->redirect('create_ok');
}
BENEFITS
▸ Prevents code duplication
▸ Increases legibility
▸ Reduce cyclomatic complexity
RULE #3
ENCAPSULATE ALL PRIMITIVE TYPES
AND STRINGS
RULE #3
ENCAPSULATE ALL PRIMITIVE TYPES
AND STRINGS
IF THEY HAVE BEHAVIOR
BUT… WHY?
EXCESSIVE USAGE OF OBJECTS IN
PHP (IF PHP <7!) DRASTICALLY
INCREASES MEMORY FOOTPRINT!
Guilherme Blanco
class Item
{
final public static function find($id)
{
if (is_string($id) && trim($id) != '') {
// do find ...
}
throw new InvalidArgumentException('$id must be a non-empty string');
}
final public static function create($id, array $data)
{
if ( ! is_string($id)) {
throw new InvalidArgumentException('$id must be a string');
}
if (empty(trim($id))) {
throw new InvalidArgumentException('$id must be a non-empty string');
}
// do create ...
}
}
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 ...
}
}
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 ...
}
}
final class Id
{
/** @var string */
public $value;
public function __construct($value)
{
if (! is_string($id) || trim($id) === '') {
$message = sprintf('%s must be a non-empty string', $value);
throw new InvalidArgumentException($message);
}
$this->value = $value;
}
public function getValue()
{
return $this->value;
}
}
class Item
{
final public static function find(Id $id)
{
// do find ...
}
final public static function create(Id $id, array $data)
{
// do create ...
}
}
BENEFITS
▸ Type hinting
▸ Encapsulation
▸ Prevents code duplication
RULE #4
ONE OBJECT OPERATOR (->) PER LINE
$this->manager->getConfig()->getSegment()->setName("foo");
Properties are hard to mock What if previous call returned NULL?
JUST USE A NULL OBJECT!
Someone watching this talk, one day
final class NullObject
{
public function __get($property)
{
return new self;
}
public function __set($property, $value)
{
return new self;
}
public function __call($method, array $arguments)
{
return new self;
}
public function __callStatic($method, array $arguments)
{
return new self;
}
public function__toString()
{
return 'null';
}
}
WHY IS IT BAD?
▸ Hide encapsulation problem
▸ Hard to debug and handle exceptions
▸ Codebase must be structured to use NullObject
▸ Hard to read and understand
EXCEPTION TO RULE
FLUENT INTERFACE UNDER SAME
METHOD
$filterChain 

->addFilter(new Zend_Filter_Alpha())
->addFilter(new Zend_Filter_StringToLower())
;
BENEFITS
▸ Law of Demeter
▸ Readability
▸ Increases testability (easier to mock)
▸ Simplifies debugging
RULE #5
DO NOT ABBREVIATE
THERE ARE 2 HARD PROBLEMS IN
COMPUTER SCIENCE: CACHE
INVALIDATION, NAMING THINGS AND
OFF BY 1 ERRORS.
Tim Bray (mentioning Phil Karlton)
WHY DO YOU
ABBREVIATE?
CODE DUPLICATION PROBLEM!
WRITE SAME NAME REPEATEDLY
MULTIPLE RESPONSIBILITY
PROBLEM!
LONG NAMES
public function getPage($data) { ... }
"Get" from where?
public function startProcess() { ... }
$trx->process('site.login');
How?
WTF is that?
renderHomePage
forkIntoChildProcess
extendedTranslator
BENEFITS
▸ Readability
▸ Better exposing method’s intent
▸ Improved maintainability
▸ Good indicator of code duplication and encapsulation
RULE #6
KEEP YOUR CLASSES SMALL
OBJECTIVE
▸ Maximum 200 lines per class

(including docblock/documentation)
▸ 10 methods per class
▸ Up to 20 lines per method
▸ 15 classes/interfaces/traits per namespace
BENEFITS
▸ Single Responsibility Principle
▸ Clear and objective methods
▸ Better code segregation
▸ Cleaner namespaces
RULE #7
LIMIT CLASS INSTANCE VARIABLES IN A
CLASS (BETWEEN 2 TO 5)
class MyRegistrationService
{
protected $userService;
protected $passwordService;
protected $logger;
protected $translator;
protected $entityManager;
protected $imageCropper;
// ...
}
Database interactions
should be on UserService
Rely on an Event system
and move this to a listener
Cross-cutting concerns. Should be auto-
injected by your DI through an interface hint
class MyRegistrationService
implements LoggerAwareInterface, TranslatorAwareInterface
{
use LoggerAwareTrait;
use TranslatorAwareTrait;
protected $userService;
protected $passwordService;
protected $eventDispatcher;
// ...
}
BENEFITS
▸ Single Responsibility Principle
▸ Loose coupling
▸ Better encapsulation
▸ Testability
RULE #8
USE FIRST CLASS COLLECTIONS
OR IN OTHER
TERMS…
ANY CLASS THAT CONTAINS AN
ARRAY MUST NOT HAVE ANY
OTHER PROPERTY.
Guilherme Blanco
TEXT
class User
{
private $name;
// ...
private $albumList = array();
public function getPublicAlbumList()
{
$filteredAlbumList = array();
foreach ($this->albumList as $album) {
if ($album->getPrivacy() === AlbumPrivacy::PUBLIC) {
$filteredAlbumList[] = $album;
}
}
return $filteredAlbumList;
}
// ...
}
$publicAlbumList = $user->getPublicAlbumList();
class AlbumList extends Collection
{
public function getPublic()
{
$filteredAlbumList = array();
foreach ($this->value as $album) {
if ($album->getPrivacy() === AlbumPrivacy::PUBLIC) {
$filteredAlbumList[] = $album;
}
}
return $filteredAlbumList;
}
}
class User
{
private $name;
private $albumList = new AlbumList();
// ...
}
$publicAlbumList = $user->getAlbumList()->getPublic();
class AlbumList extends Collection
{
public function getPublic()
{
return new ArrayCollection(
array_filter(
$this->value,
function (Album $album) {
return $album->isPublic();
}
)
);
}
}
class User
{
private $name;
private $albumList = new AlbumList();
// ...
}
$publicAlbumList = $user->getAlbumList()->getPublic();
BENEFITS
▸ Single Responsibility Principle
▸ Collection operations implemented inside of Collection
▸ Usage of SPL classes
▸ Easy to group collections without concerns over their
members’ behavior
▸ Filtering, ordering, mapping, combining are good
example methods
RULE #9
USE GETTERS AND SETTERS
class BankAccount
{
public $balance = 0;
public function deposit($amount)
{
$this->balance += $amount;
}
public function withdraw($amount)
{
$this->balance -= $amount;
}
}
// Example:
$account = new BankAccount();


$account->deposit(100.00);
// ...
$account->balance = 0; 

// ...
$account->withdraw(10.00);
Balance can be modified without class being
notified, leading to unexpected errors.
BENEFITS
▸ Operations injection
▸ Transformations encapsulation
▸ Promotes Open/Closed Principle ("O" in SOLID)
QUESTIONS?
THANKS! =)
GUILHERMEBLANCO
GUILHERMEBLANCO

Mais conteúdo relacionado

Mais procurados

Class 3 - PHP Functions
Class 3 - PHP FunctionsClass 3 - PHP Functions
Class 3 - PHP FunctionsAhmed Swilam
 
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшeQA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшeQAFest
 
PHP unserialization vulnerabilities: What are we missing?
PHP unserialization vulnerabilities: What are we missing?PHP unserialization vulnerabilities: What are we missing?
PHP unserialization vulnerabilities: What are we missing?Sam Thomas
 
Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)Scott Wlaschin
 
Component lifecycle hooks in Angular 2.0
Component lifecycle hooks in Angular 2.0Component lifecycle hooks in Angular 2.0
Component lifecycle hooks in Angular 2.0Eyal Vardi
 
Java 8 Default Methods
Java 8 Default MethodsJava 8 Default Methods
Java 8 Default MethodsHaim Michael
 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaJorge Vásquez
 
DNS exfiltration using sqlmap
DNS exfiltration using sqlmapDNS exfiltration using sqlmap
DNS exfiltration using sqlmapMiroslav Stampar
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsRoss Tuck
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code SmellsMario Sangiorgio
 
The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)Scott Wlaschin
 
Oops concepts in php
Oops concepts in phpOops concepts in php
Oops concepts in phpCPD INDIA
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome TownRoss Tuck
 
Introduction to Javascript
Introduction to JavascriptIntroduction to Javascript
Introduction to JavascriptAmit Tyagi
 

Mais procurados (20)

Clean coding-practices
Clean coding-practicesClean coding-practices
Clean coding-practices
 
Class 3 - PHP Functions
Class 3 - PHP FunctionsClass 3 - PHP Functions
Class 3 - PHP Functions
 
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшeQA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
QA Fes 2016. Алексей Виноградов. Page Objects: лучше проще, да лучшe
 
PHP unserialization vulnerabilities: What are we missing?
PHP unserialization vulnerabilities: What are we missing?PHP unserialization vulnerabilities: What are we missing?
PHP unserialization vulnerabilities: What are we missing?
 
Clean code slide
Clean code slideClean code slide
Clean code slide
 
Optional in Java 8
Optional in Java 8Optional in Java 8
Optional in Java 8
 
Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)
 
Clean Code
Clean CodeClean Code
Clean Code
 
Component lifecycle hooks in Angular 2.0
Component lifecycle hooks in Angular 2.0Component lifecycle hooks in Angular 2.0
Component lifecycle hooks in Angular 2.0
 
Java 8 Default Methods
Java 8 Default MethodsJava 8 Default Methods
Java 8 Default Methods
 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in Scala
 
DNS exfiltration using sqlmap
DNS exfiltration using sqlmapDNS exfiltration using sqlmap
DNS exfiltration using sqlmap
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code Smells
 
JUnit 4
JUnit 4JUnit 4
JUnit 4
 
HTML5 DRAG AND DROP
HTML5 DRAG AND DROPHTML5 DRAG AND DROP
HTML5 DRAG AND DROP
 
The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)The Functional Programming Toolkit (NDC Oslo 2019)
The Functional Programming Toolkit (NDC Oslo 2019)
 
Oops concepts in php
Oops concepts in phpOops concepts in php
Oops concepts in php
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
 
Introduction to Javascript
Introduction to JavascriptIntroduction to Javascript
Introduction to Javascript
 

Destaque

“Writing code that lasts” … or writing code you won’t hate tomorrow.
“Writing code that lasts” … or writing code you won’t hate tomorrow.“Writing code that lasts” … or writing code you won’t hate tomorrow.
“Writing code that lasts” … or writing code you won’t hate tomorrow.Rafael Dohms
 
ORM dont kill your DB, developers do
ORM dont kill your DB, developers doORM dont kill your DB, developers do
ORM dont kill your DB, developers doGuilherme Blanco
 
Thinking Object-Oriented
Thinking Object-OrientedThinking Object-Oriented
Thinking Object-Orientedadil raja
 
PHPubSP Object Calisthenics aplicado ao PHP
PHPubSP Object Calisthenics aplicado ao PHPPHPubSP Object Calisthenics aplicado ao PHP
PHPubSP Object Calisthenics aplicado ao PHPGuilherme Blanco
 
Your code sucks, let's fix it
Your code sucks, let's fix itYour code sucks, let's fix it
Your code sucks, let's fix itRafael Dohms
 
Your code sucks, let's fix it! - php|tek13
Your code sucks, let's fix it! - php|tek13Your code sucks, let's fix it! - php|tek13
Your code sucks, let's fix it! - php|tek13Rafael Dohms
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix itRafael Dohms
 
Granny Was a Hacker (CampJS Version)
Granny Was a Hacker (CampJS Version)Granny Was a Hacker (CampJS Version)
Granny Was a Hacker (CampJS Version)Kristine Howard
 
Introduction to Go programming
Introduction to Go programmingIntroduction to Go programming
Introduction to Go programmingExotel
 

Destaque (12)

“Writing code that lasts” … or writing code you won’t hate tomorrow.
“Writing code that lasts” … or writing code you won’t hate tomorrow.“Writing code that lasts” … or writing code you won’t hate tomorrow.
“Writing code that lasts” … or writing code you won’t hate tomorrow.
 
ORM dont kill your DB, developers do
ORM dont kill your DB, developers doORM dont kill your DB, developers do
ORM dont kill your DB, developers do
 
Realtime com node.js e socket.io
Realtime com node.js e socket.ioRealtime com node.js e socket.io
Realtime com node.js e socket.io
 
Thinking Object-Oriented
Thinking Object-OrientedThinking Object-Oriented
Thinking Object-Oriented
 
PHPubSP Object Calisthenics aplicado ao PHP
PHPubSP Object Calisthenics aplicado ao PHPPHPubSP Object Calisthenics aplicado ao PHP
PHPubSP Object Calisthenics aplicado ao PHP
 
Your code sucks, let's fix it
Your code sucks, let's fix itYour code sucks, let's fix it
Your code sucks, let's fix it
 
Your code sucks, let's fix it! - php|tek13
Your code sucks, let's fix it! - php|tek13Your code sucks, let's fix it! - php|tek13
Your code sucks, let's fix it! - php|tek13
 
SPL Datastructures
SPL DatastructuresSPL Datastructures
SPL Datastructures
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix it
 
RESTful API Design, Second Edition
RESTful API Design, Second EditionRESTful API Design, Second Edition
RESTful API Design, Second Edition
 
Granny Was a Hacker (CampJS Version)
Granny Was a Hacker (CampJS Version)Granny Was a Hacker (CampJS Version)
Granny Was a Hacker (CampJS Version)
 
Introduction to Go programming
Introduction to Go programmingIntroduction to Go programming
Introduction to Go programming
 

Semelhante a PHP for Adults: Clean Code and Object Calisthenics

Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsSam Hennessy
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにYuya Takeyama
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolveXSolve
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency InjectionRifat Nabi
 
How to write code you won't hate tomorrow
How to write code you won't hate tomorrowHow to write code you won't hate tomorrow
How to write code you won't hate tomorrowPete McFarlane
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleHugo Hamon
 
Symfony2 - extending the console component
Symfony2 - extending the console componentSymfony2 - extending the console component
Symfony2 - extending the console componentHugo Hamon
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design PatternsHugo Hamon
 
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConRafael Dohms
 
Anonymous classes
Anonymous classesAnonymous classes
Anonymous classesDarkmira
 
1st CI&T Lightning Talks: Writing better code with Object Calisthenics
1st CI&T Lightning Talks: Writing better code with Object Calisthenics1st CI&T Lightning Talks: Writing better code with Object Calisthenics
1st CI&T Lightning Talks: Writing better code with Object CalisthenicsLucas Arruda
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your CodeAbbas Ali
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistenceHugo Hamon
 
Crazy things done on PHP
Crazy things done on PHPCrazy things done on PHP
Crazy things done on PHPTaras Kalapun
 
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...DevClub_lv
 
Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Jeff Carouth
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in actionJace Ju
 

Semelhante a PHP for Adults: Clean Code and Object Calisthenics (20)

Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
PHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
 
Oops in php
Oops in phpOops in php
Oops in php
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
How to write code you won't hate tomorrow
How to write code you won't hate tomorrowHow to write code you won't hate tomorrow
How to write code you won't hate tomorrow
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Symfony2 - extending the console component
Symfony2 - extending the console componentSymfony2 - extending the console component
Symfony2 - extending the console component
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnCon
 
Functional programming with php7
Functional programming with php7Functional programming with php7
Functional programming with php7
 
Anonymous classes
Anonymous classesAnonymous classes
Anonymous classes
 
1st CI&T Lightning Talks: Writing better code with Object Calisthenics
1st CI&T Lightning Talks: Writing better code with Object Calisthenics1st CI&T Lightning Talks: Writing better code with Object Calisthenics
1st CI&T Lightning Talks: Writing better code with Object Calisthenics
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
Crazy things done on PHP
Crazy things done on PHPCrazy things done on PHP
Crazy things done on PHP
 
Shell.php
Shell.phpShell.php
Shell.php
 
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
“SOLID principles in PHP – how to apply them in PHP and why should we care“ b...
 
Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4Can't Miss Features of PHP 5.3 and 5.4
Can't Miss Features of PHP 5.3 and 5.4
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 

Mais de Guilherme Blanco

IPC2010SE Doctrine2 Enterprise Persistence Layer for PHP
IPC2010SE Doctrine2 Enterprise Persistence Layer for PHPIPC2010SE Doctrine2 Enterprise Persistence Layer for PHP
IPC2010SE Doctrine2 Enterprise Persistence Layer for PHPGuilherme Blanco
 
Doctrine 2.0: A evolução da persistência em PHP
Doctrine 2.0: A evolução da persistência em PHPDoctrine 2.0: A evolução da persistência em PHP
Doctrine 2.0: A evolução da persistência em PHPGuilherme Blanco
 
Doctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHPDoctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHPGuilherme Blanco
 
Desenvolvimento Agil Com Doctrine Orm
Desenvolvimento Agil Com Doctrine OrmDesenvolvimento Agil Com Doctrine Orm
Desenvolvimento Agil Com Doctrine OrmGuilherme Blanco
 

Mais de Guilherme Blanco (10)

Enterprise php
Enterprise phpEnterprise php
Enterprise php
 
PHP 7
PHP 7PHP 7
PHP 7
 
Javascript para adultos
Javascript para adultosJavascript para adultos
Javascript para adultos
 
Dependency injection
Dependency injectionDependency injection
Dependency injection
 
Doctrine2 Seminário PHP
Doctrine2 Seminário PHPDoctrine2 Seminário PHP
Doctrine2 Seminário PHP
 
IPC2010SE Doctrine2 Enterprise Persistence Layer for PHP
IPC2010SE Doctrine2 Enterprise Persistence Layer for PHPIPC2010SE Doctrine2 Enterprise Persistence Layer for PHP
IPC2010SE Doctrine2 Enterprise Persistence Layer for PHP
 
Doctrine 2.0: A evolução da persistência em PHP
Doctrine 2.0: A evolução da persistência em PHPDoctrine 2.0: A evolução da persistência em PHP
Doctrine 2.0: A evolução da persistência em PHP
 
PHP, Daemons e Multimedia
PHP, Daemons e MultimediaPHP, Daemons e Multimedia
PHP, Daemons e Multimedia
 
Doctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHPDoctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHP
 
Desenvolvimento Agil Com Doctrine Orm
Desenvolvimento Agil Com Doctrine OrmDesenvolvimento Agil Com Doctrine Orm
Desenvolvimento Agil Com Doctrine Orm
 

Último

From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...Neo4j
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 

Último (20)

From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 

PHP for Adults: Clean Code and Object Calisthenics

  • 1. PHP 4 ADULTS OBJECT CALISTHENICS AND CLEAN CODE
  • 5.
  • 7. S T U P I D
  • 9. S O L I D
  • 10. SINGLE RESPONSIBILITY OPEN/CLOSED PRINCIPLE LISKOV SUBSTITUTION PRINCIPLE INTERFACE SEGREGATION DEPENDENCY INVERSION
  • 12. interface Bird { public function setLocation($longitude, $latitude); public function setHeight($height); public function draw(); }
  • 13. class Penguin implements Bird { public function setHeight($height) { // Do nothing } }
  • 14. interface Bird { public function setLocation($longitude, $latitude); public function draw(); } interface FlightfulBird extends Bird 
 { 
 public function setHeight($height); }
  • 16. namespace DatingUserBundleEntity { class User { /** @var DatingUserBundleEntityImage */ protected $avatar; } } namespace DatingMediaBundleEntity { class Image { /** @var DatingUserBundleEntityUser */ protected $owner; } 
 }
  • 17. namespace DatingUserBundleEntity { class User { /** @var AvatarInterface */ protected $avatar; } interface AvatarInterface { // ... } } namespace DatingMediaBundleEntity { use DatingUserBundleEntityAvatarInterface; class Image implements AvatarInterface { /** @var DatingUserBundleEntityUser */ protected $owner; } 
 }
  • 19. RULE #1 ONLY ONE INDENTATION LEVEL PER METHOD
  • 20. public function validateForm($filters='', $validators='', $options='') { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if ($input->hasInvalid() || $input->hasMissing()) { foreach ($input->getMessages() as $field => $messageList) { foreach ($messageList as $message) { if (strpos($message, "empty")) { throw new Tss_FormException( "The field {$field} cannot be empty!", 3, "javascript:history.back();" ); 
 } else { throw new Tss_FormException( "{$message}", 3, "javascript:history.back();" ); } } } } return $input; }
  • 21. 1 2 3 4 public function validateForm($filters='', $validators='', $options='') { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if ($input->hasInvalid() || $input->hasMissing()) { foreach ($input->getMessages() as $field => $messageList) { foreach ($messageList as $message) { if (strpos($message, "empty")) { throw new Tss_FormException( "The field {$field} cannot be empty!", 3, "javascript:history.back();" ); 
 } else { throw new Tss_FormException( "{$message}", 3, "javascript:history.back();" ); } } } } return $input; } Class prototype
  • 23. public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { foreach ($messageList as $message) { if (strpos($message, "empty")) { throw new Tss_FormException( "The field {$field} cannot be empty!", 3, "javascript:history.back();" ); 
 } else { throw new Tss_FormException( "{$message}", 3, "javascript:history.back();" ); } } } return $input; }
  • 24. public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { foreach ($messageList as $message) { if (strpos($message, "empty")) { throw new Tss_FormException( "The field {$field} cannot be empty!", 3, "javascript:history.back();" ); 
 } else { throw new Tss_FormException( "{$message}", 3, "javascript:history.back();" ); } } } return $input; }
  • 25. 1 2 3 public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { foreach ($messageList as $message) { if (strpos($message, "empty")) { throw new Tss_FormException( "The field {$field} cannot be empty!", 3, "javascript:history.back();" ); 
 } else { throw new Tss_FormException( "{$message}", 3, "javascript:history.back();" ); } } } return $input; }
  • 26. public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { foreach ($messageList as $message) { $errorMessage = (strpos($message, "empty") === false) ? "The field {$field} cannot be empty!" : $message; throw new Tss_FormException( $errorMessage, 3, "javascript:history.back();" ); } } return $input; }
  • 27. public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { foreach ($messageList as $message) { $errorMessage = (strpos($message, "empty") === false) ? "The field {$field} cannot be empty!" : $message; throw new Tss_FormException( $errorMessage, 3, "javascript:history.back();" ); } } return $input; }
  • 28. 1 2 public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { foreach ($messageList as $message) { $errorMessage = (strpos($message, "empty") === false) ? "The field {$field} cannot be empty!" : $message; throw new Tss_FormException( $errorMessage, 3, "javascript:history.back();" ); } } return $input; }
  • 29. public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { $message = array_shift($messageList); $jsAction = "javascript:history.back();"; $errorMessage = (strpos($message, "empty") === false) ? "The field {$field} cannot be empty!" : $message; throw new Tss_FormException($errorMessage, 3, $jsAction); } return $input; }
  • 30. public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { $message = array_shift($messageList); $jsAction = "javascript:history.back();"; $errorMessage = (strpos($message, "empty") === false) ? "The field {$field} cannot be empty!" : $message; throw new Tss_FormException($errorMessage, 3, $jsAction); } return $input; } Logical groups Variable interpolation
  • 31. public function validateForm($filters=array(), $validators=array(), $options=array()) { $data = $_POST; $input = new Zend_Filter_Input($filters, $validators, $data, $options); $input->setDefaultEscapeFilter(new Zend_Filter_StringTrim()); if (! ($input->hasInvalid() || $input->hasMissing())) { return $input; } foreach ($input->getMessages() as $field => $messageList) { $message = array_shift($messageList); $jsAction = "javascript:history.back();"; $errorMessage = (strpos($message, "empty") === false) ? sprintf("The field %s cannot be empty!", $field) : $message; throw new Tss_FormException($errorMessage, 3, $jsAction); } return $input; }
  • 32. BENEFITS ▸ Single Responsibility Principle ("S" in SOLID) ▸ Reusability
  • 33. RULE #2 NO "ELSE" KEYWORD
  • 34. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } 
 } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 35. public function createPost($request) { $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()){ $repository = $this->getRepository('MyBundle:Post'); if (!$repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } 
 } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } } Type-casting Coding standards Separate into logical groups. Consider as paragraphs!
  • 36. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()) { if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } 
 } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 37. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()) { if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } 
 } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 38. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()) { if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } 
 } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 40. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()) { if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } 
 } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 41. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()) { if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } 
 } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 42. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if ($form->isValid()) { if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } 
 } else { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } }
  • 43. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if (! $form->isValid()) { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } }
  • 44. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if (! $form->isValid()) { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } }
  • 45. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if (! $form->isValid()) { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } if (! $repository->exists($entity)) { $repository->save($entity); return $this->redirect('create_ok'); } else { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } }
  • 46. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if (! $form->isValid()) { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } if ($repository->exists($entity)) { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } $repository->save($entity); return $this->redirect('create_ok'); }
  • 47. public function createPost(Request $request) { $repository = $this->getRepository(‘MyBundle:Post'); $entity = new Post(); $form = new MyForm($entity); $form->bind($request); if (! $form->isValid()) { $error = "Invalid fields"; return array('form' => $form, 'error' => $error); } if ($repository->exists($entity)) { $error = "Post Title already exists"; return array('form' => $form, 'error' => $error); } $repository->save($entity); return $this->redirect('create_ok'); }
  • 48. BENEFITS ▸ Prevents code duplication ▸ Increases legibility ▸ Reduce cyclomatic complexity
  • 49. RULE #3 ENCAPSULATE ALL PRIMITIVE TYPES AND STRINGS
  • 50. RULE #3 ENCAPSULATE ALL PRIMITIVE TYPES AND STRINGS IF THEY HAVE BEHAVIOR
  • 52. EXCESSIVE USAGE OF OBJECTS IN PHP (IF PHP <7!) DRASTICALLY INCREASES MEMORY FOOTPRINT! Guilherme Blanco
  • 53. class Item { final public static function find($id) { if (is_string($id) && trim($id) != '') { // do find ... } throw new InvalidArgumentException('$id must be a non-empty string'); } final public static function create($id, array $data) { if ( ! is_string($id)) { throw new InvalidArgumentException('$id must be a string'); } if (empty(trim($id))) { throw new InvalidArgumentException('$id must be a non-empty string'); } // do create ... } }
  • 54. 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 ... } }
  • 55. 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 ... } }
  • 56. final class Id { /** @var string */ public $value; public function __construct($value) { if (! is_string($id) || trim($id) === '') { $message = sprintf('%s must be a non-empty string', $value); throw new InvalidArgumentException($message); } $this->value = $value; } public function getValue() { return $this->value; } }
  • 57. class Item { final public static function find(Id $id) { // do find ... } final public static function create(Id $id, array $data) { // do create ... } }
  • 58. BENEFITS ▸ Type hinting ▸ Encapsulation ▸ Prevents code duplication
  • 59. RULE #4 ONE OBJECT OPERATOR (->) PER LINE
  • 61. JUST USE A NULL OBJECT! Someone watching this talk, one day
  • 62. final class NullObject { public function __get($property) { return new self; } public function __set($property, $value) { return new self; } public function __call($method, array $arguments) { return new self; } public function __callStatic($method, array $arguments) { return new self; } public function__toString() { return 'null'; } }
  • 63. WHY IS IT BAD? ▸ Hide encapsulation problem ▸ Hard to debug and handle exceptions ▸ Codebase must be structured to use NullObject ▸ Hard to read and understand
  • 64. EXCEPTION TO RULE FLUENT INTERFACE UNDER SAME METHOD
  • 66. BENEFITS ▸ Law of Demeter ▸ Readability ▸ Increases testability (easier to mock) ▸ Simplifies debugging
  • 67. RULE #5 DO NOT ABBREVIATE
  • 68. THERE ARE 2 HARD PROBLEMS IN COMPUTER SCIENCE: CACHE INVALIDATION, NAMING THINGS AND OFF BY 1 ERRORS. Tim Bray (mentioning Phil Karlton)
  • 70. CODE DUPLICATION PROBLEM! WRITE SAME NAME REPEATEDLY
  • 72. public function getPage($data) { ... } "Get" from where? public function startProcess() { ... } $trx->process('site.login'); How? WTF is that? renderHomePage forkIntoChildProcess extendedTranslator
  • 73. BENEFITS ▸ Readability ▸ Better exposing method’s intent ▸ Improved maintainability ▸ Good indicator of code duplication and encapsulation
  • 74. RULE #6 KEEP YOUR CLASSES SMALL
  • 75. OBJECTIVE ▸ Maximum 200 lines per class
 (including docblock/documentation) ▸ 10 methods per class ▸ Up to 20 lines per method ▸ 15 classes/interfaces/traits per namespace
  • 76. BENEFITS ▸ Single Responsibility Principle ▸ Clear and objective methods ▸ Better code segregation ▸ Cleaner namespaces
  • 77. RULE #7 LIMIT CLASS INSTANCE VARIABLES IN A CLASS (BETWEEN 2 TO 5)
  • 78. class MyRegistrationService { protected $userService; protected $passwordService; protected $logger; protected $translator; protected $entityManager; protected $imageCropper; // ... } Database interactions should be on UserService Rely on an Event system and move this to a listener Cross-cutting concerns. Should be auto- injected by your DI through an interface hint
  • 79. class MyRegistrationService implements LoggerAwareInterface, TranslatorAwareInterface { use LoggerAwareTrait; use TranslatorAwareTrait; protected $userService; protected $passwordService; protected $eventDispatcher; // ... }
  • 80. BENEFITS ▸ Single Responsibility Principle ▸ Loose coupling ▸ Better encapsulation ▸ Testability
  • 81. RULE #8 USE FIRST CLASS COLLECTIONS
  • 83. ANY CLASS THAT CONTAINS AN ARRAY MUST NOT HAVE ANY OTHER PROPERTY. Guilherme Blanco TEXT
  • 84. class User { private $name; // ... private $albumList = array(); public function getPublicAlbumList() { $filteredAlbumList = array(); foreach ($this->albumList as $album) { if ($album->getPrivacy() === AlbumPrivacy::PUBLIC) { $filteredAlbumList[] = $album; } } return $filteredAlbumList; } // ... } $publicAlbumList = $user->getPublicAlbumList();
  • 85. class AlbumList extends Collection { public function getPublic() { $filteredAlbumList = array(); foreach ($this->value as $album) { if ($album->getPrivacy() === AlbumPrivacy::PUBLIC) { $filteredAlbumList[] = $album; } } return $filteredAlbumList; } } class User { private $name; private $albumList = new AlbumList(); // ... } $publicAlbumList = $user->getAlbumList()->getPublic();
  • 86. class AlbumList extends Collection { public function getPublic() { return new ArrayCollection( array_filter( $this->value, function (Album $album) { return $album->isPublic(); } ) ); } } class User { private $name; private $albumList = new AlbumList(); // ... } $publicAlbumList = $user->getAlbumList()->getPublic();
  • 87. BENEFITS ▸ Single Responsibility Principle ▸ Collection operations implemented inside of Collection ▸ Usage of SPL classes ▸ Easy to group collections without concerns over their members’ behavior ▸ Filtering, ordering, mapping, combining are good example methods
  • 88. RULE #9 USE GETTERS AND SETTERS
  • 89. class BankAccount { public $balance = 0; public function deposit($amount) { $this->balance += $amount; } public function withdraw($amount) { $this->balance -= $amount; } } // Example: $account = new BankAccount(); 
 $account->deposit(100.00); // ... $account->balance = 0; 
 // ... $account->withdraw(10.00); Balance can be modified without class being notified, leading to unexpected errors.
  • 90. BENEFITS ▸ Operations injection ▸ Transformations encapsulation ▸ Promotes Open/Closed Principle ("O" in SOLID)