Date post: | 19-Jul-2015 |
Category: |
Spiritual |
Upload: | manuel-noirfalise |
View: | 207 times |
Download: | 1 times |
Why Your Code SuxxAnd why you should care about it ?
www.blubird.eu
Started mid 2013
Audit & consultancy
Project staffing
Project management
ABOUT BLUBIRD
Web & Mobile apps
Virtualization and cloud
Performance & optimization
Security
SMAC the pictureSecurity, Mobility, Apps & Cloud
Nicolas De Boose
➢ Freelancer
➢ PHP, Symfony2, (Node) Js, Css
➢ 10 yrs pro
➢ A wonderful fiancee <3
➢@NicoDeBoose
➢ Blog: mechantblog.com
ABOUT ME
WHY THIS SUBJECT?
1. Laravel vs Symfony2 vs Zend Framework2 ? Google it!
2. “Clean code” is a subject I like– Pull request– Gave lots of reviews– Read/watch Clean coders (book & website)
3. Every year, new coders must learn best practices, solid principles, patterns, ... And it’s a good booster shot
HOW ?
1. Presentation of code that smells
2. Your opinion
3. Refactoring
//Let's go to the bar
interface Human{public function getAge();public function getFullName();
}
interface Drink{public function getPrice();
}
interface Bar{public function removeFromStock(Drink $drink);public function hasInStock(Drink $drink);
}
class BarMan{private $bar;
public function command(Human $human, Drink $drink){if ($this->bar->hasInStock($drink) == true) {
if ($drink->doesNotContainAlcohol()) {$this->prepare($human, $drink);
} else {if ($human->getAge() >= 18){
$this->prepare($human, $drink);} else {
$this->errorMessage = 'Too young :(';return false;
}}
} else {$this->errorMessage = 'Not in stock :(';
}return true;
}}
class BarMan{private $bar;
public function command(Human $human, Drink $drink){if ($this->bar->hasInStock($drink) == true) {
if ($drink->doesNotContainAlcohol()) {$this->prepare($human, $drink);
} else {if ($human->getAge() >= 18){
$this->prepare($human, $drink);} else {
$this->errorMessage = 'Too young :(';return false;
}}
} else {$this->errorMessage = 'Not in stock :(';
}return true;
}}
Focus on this code
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink) == true) {if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);} else {
if ($human->getAge() >= 18){$this->prepare($human, $drink);
} else {$this->errorMessage = 'Too young :(';return false;
}}
} else {$this->errorMessage = 'Not in stock :(';return false;
}return true;
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink) == true) {if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);} else {
if ($human->getAge() >= 18){$this->prepare($human, $drink);
} else {$this->errorMessage = 'Too young :(';return false;
}}
} else {$this->errorMessage = 'Not in stock :(';return false;
}return true;
== true) == true) ...
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink)) {if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);} else {
if ($human->getAge() >= 18){$this->prepare($human, $drink);
} else {$this->errorMessage = 'Too young :(';return false;
}}
} else {$this->errorMessage = 'Not in stock :(';return false;
}return true;
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink)) {if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);} else {
if ($human->getAge() >= 18){$this->prepare($human, $drink);
} else {$this->errorMessage = 'Too young :(';return false;
}}
} else {$this->errorMessage = 'Not in stock :(';return false;
}return true;
Nested if’s
//In Barman::command(Human $human, Drink $drink)
if ($this->bar->hasInStock($drink)) {if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);} else {
if ($human->getAge() >= 18){$this->prepare($human, $drink);
} else {$this->errorMessage = 'Too young :(';return false;
}}
} else {$this->errorMessage = 'Not in stock :(';return false;
}return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {$this->errorMessage = 'Not in stock :(';return false;
}if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);} else {
if ($human->getAge() >= 18){$this->prepare($human, $drink);
} else {$this->errorMessage = 'Too young :(';return false;
}}return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {$this->errorMessage = 'Not in stock :(';return false;
}if ($drink->doesNotContainAlcohol()) {
$this->prepare($human, $drink);} else {
if ($human->getAge() >= 18){$this->prepare($human, $drink);
} else {$this->errorMessage = 'Too young :(';return false;
}}return true;
call to “prepare()” here
And here + nested if
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {$this->errorMessage = 'Not in stock :(';return false;
}if (!$drink->doesNotContainAlcohol()
&& $human->getAge() < 18) {
$this->errorMessage = 'Too young :(';return false;
}
$this->prepare($human, $drink);return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {$this->errorMessage = 'Not in stock :(';return false;
}if (!$drink->doesNotContainAlcohol()
&& $human->getAge() < 18) {
$this->errorMessage = 'Too young :(';return false;
}
$this->prepare($human, $drink);return true;
NOT doesNot… My head hurts!
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {$this->errorMessage = 'Not in stock :(';return false;
}if ($drink->containsAlcohol()
&& $human->getAge() < 18) {
$this->errorMessage = 'Too young :(';return false;
}
$this->prepare($human, $drink);return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {$this->errorMessage = 'Not in stock :(';return false;
}if ($drink->containsAlcohol()
&& $human->getAge() < 18) {
$this->errorMessage = 'Too young :(';return false;
}
$this->prepare($human, $drink);return true;
can we be more explicit?
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {$this->errorMessage = 'Not in stock :(';return false;
}if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)) {
$this->errorMessage = 'Too young :(';return false;
}
$this->prepare($human, $drink);return true;
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {$this->errorMessage = 'Not in stock :(';return false;
}if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)) {
$this->errorMessage = 'Too young :(';return false;
}
$this->prepare($human, $drink);return true;
It’s not my job to keep the error message
It’s not my job to keep the error message
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {throw new DrinkNotInStockException($drink);
}if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)) {
return new CommandResult(false,'Too young :(');}
$this->prepare($human, $drink);return true;
Solution 1: Throw an exception
Solution 2: Return a “Result” object
Solution 1: Return nothingSolution 2: Return a “Result” object
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {throw new DrinkNotInStockException($drink);
}if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)) {
throw new TooYoungToDrinkAlcoholException();}
$this->prepare($human, $drink);
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {throw new DrinkNotInStockException($drink);
}if ($drink->containsAlcohol()
&& $human->isYoungerThan(18)) {
throw new TooYoungToDrinkAlcoholException();}
$this->prepare($human, $drink);
What is “18”? It’s a magic number!
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {throw new DrinkNotInStockException($drink);
}if ($drink->containsAlcohol()
&& $human->isYoungerThan(self::MINIMUM_AGE_FOR_ALCOHOL)
) {throw new TooYoungToDrinkAlcoholException();
}
$this->prepare($human, $drink);
Better but not the best place to store the constant
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink)) {throw new DrinkNotInStockException($drink);
}if ($drink->containsAlcohol()
&& $human->isYoungerThan(BarLegislation::MINIMUM_AGE_FOR_ALCOHOL)
) {throw new TooYoungToDrinkAlcoholException();
}
$this->prepare($human, $drink);
There, it’s good enough for the moment
//In Barman::command(Human $human, Drink $drink)
if (!$this->bar->hasInStock($drink))throw new DrinkNotInStockException($drink);
if ($drink->containsAlcohol() && $human->isYoungerThan
(BarLegislation::MINIMUM_AGE_FOR_ALCOHOL))
throw new TooYoungToDrinkAlcoholException();
$this->prepare($human, $drink);
interface Human{// public function getAge(); => deleted
public function isYoungerThan($age); //New public function getFullName();
}
interface Drink{public function getPrice();public function containsAlcohol(); //Rename
}
interface Bar{public function removeFromStock(Drink $drink);public function hasInStock(Drink $drink);
}
//In Barman::prepare(Human $human, Drink $drink)
$tmp = explode(" ", $human->getFullName());$ac = "";foreach ($tmp as $w) {
$ac .= $w[0];}$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($ac);SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready', $ac)
);
//In Barman::prepare(Human $human, Drink $drink)
$tmp = explode(" ", $human->getFullName());$ac = "";foreach ($tmp as $w) {
$ac .= $w[0];}$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($ac);SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready', $ac)
);
What does “tmp” mean?
What does “ac” mean?
//In Barman::prepare(Human $human, Drink $drink)
$nameParts = explode(" ",$human->getFullName());$acronym = "";foreach ($nameParts as $w) {
$acronym .= $w[0];}$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready', $acronym)
);
//In Barman::prepare(Human $human, Drink $drink)
$nameParts = explode(" ",$human->getFullName());$acronym = "";foreach ($nameParts as $w) {
$acronym .= $w[0];}$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready', $acronym)
);
Can we compute the acronym elsewhere?
//In Barman::prepare(Human $human, Drink $drink)
$acronym = $this->getHumanAcronym($human);$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready', $acronym)
);
That’s better, but ...
//In Barman::prepare(Human $human, Drink $drink)
$acronym = $human->getAcronym();$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready', $acronym)
);
//In Barman::prepare(Human $human, Drink $drink)
$acronym = $human->getAcronym();$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($acronym);SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready', $acronym)
);
Now let’s remove $acronym var
//In Barman::prepare(Human $human, Drink $drink)
$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($human->getAcronym());SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready',$human->getAcronym())
);
//In Barman::prepare(Human $human, Drink $drink)
$human->addToBill($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($human->getAcronym());SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready',$human->getAcronym())
);
A bill for a “simple” human?
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($human->getAcronym());SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready',$human->getAcronym())
);
Create a “Client” class: He knows what he’s drinking
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$drink->setAcronym($human->getAcronym());SmsSender::send(
$human->getMobilePhoneNumber(),sprintf('Drink %s ready',$human->getAcronym())
);
A drink should not be more than a drink.
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$labeledDrink = new LabeledDrink($drink,$human->getAcronym());
SmsSender::send($human->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
Create a class that symbolizes the drink with an acronym
Take the label from the new object
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$labeledDrink = new LabeledDrink($drink,$human->getAcronym());
SmsSender::send($human->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
Is there a better place to instantiate the object?
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$labeledDrink = new LabeledDrink($drink,$human->getAcronym());
SmsSender::send($human->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
Is there a better place to instantiate the object?
Maybe here?
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send($human->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send($human->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
Not all human have a phone number
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send($client->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
Our clients have a phone number!
//In Barman::prepare(Human $human, Drink $drink)
$client = new Client($human);$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send($client->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
Human becomes useless here
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send($client->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send($client->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
Hello ugly hard coded content!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send($client->getMobilePhoneNumber(),sprintf('Drink %s ready',
$labeledDrink->getLabel()));
Hello ugly hard coded content!
We actually send a message here...
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(new DrinkIsReadyMessage($labeledDrink, $client););
//FYI
class DrinkIsReadyMessage implements Message{public function getPhoneNumber(){
return $this->client->getMobilePhoneNumber();}public function getMessage(){/* … */}
}
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(new DrinkIsReadyMessage($labeledDrink, $client););
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(new DrinkIsReadyMessage($labeledDrink, $client););
Static is not testable!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(new DrinkIsReadyMessage($labeledDrink, $client););
/* Should the bar manage the sms messaging system? */
Static is not testable!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(new DrinkIsReadyMessage($labeledDrink, $client););
/* Should the bar manage the sms messaging system? Why not, but it already manages the drinks stock.*/
Static is not testable!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
SmsSender::send(new DrinkIsReadyMessage($labeledDrink, $client););
/* Should the bar manage the sms messaging system? Why not, but it already manages the drinks stock.Maybe the bar should be a facade?*/
Static is not testable!
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$this->bar->sendMessage(new DrinkIsReadyMessage($labeledDrink, $client););
Simplest solution...
//In Barman::prepare(Client $client, Drink $drink)
$labeledDrink = $client->addDrink($drink);$this->bar->removeFromStock($drink);
/* ... Prepare the drink ... */
$this->bar->sendMessage(new DrinkIsReadyMessage($labeledDrink, $client););
Simplest solution...
Voilà! Let's not over engineering for the moment :)
interface Bar{public function removeFromStock(Drink $drink);public function hasInStock(Drink $drink);public function sendMessage(Message $message);//New
}
interface Client{public function addDrink(Drink $drink);public function getMobilePhoneNumber();
}interface labeledDrink extends Drink{ //New class
public function getLabel();}
interface Message{ //New interfacepublic function getPhoneNumber();public function getMessage();
}interface DrinkIsReadyMessage extends Message{} //New
class BillPrinter{ //Let's print the bill!public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {$this->renderAsXml($client);
} elseif ($this->type === 'html') {$this->renderAsHtml($client);
} else {throw new \Exception('Invalid input');
}}
public function renderAsHtml(Client $client){}public function renderAsXml(Client $client){}
}
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {$this->renderAsXml($client);
} elseif ($this->type === 'html') {$this->renderAsHtml($client);
} else {throw new \Exception('Invalid input');
}}
public function renderAsHtml(Client $client){}public function renderAsXml(Client $client){}
}
Generic exception
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {$this->renderAsXml($client);
} elseif ($this->type === 'html') {$this->renderAsHtml($client);
} else {throw new \InvalidTypeException();
}}
public function renderAsHtml(Client $client){}public function renderAsXml(Client $client){}
}
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {$this->renderAsXml($client);
} elseif ($this->type === 'html') {$this->renderAsHtml($client);
} else {throw new \InvalidTypeException();
}}
public function renderAsHtml(Client $client){}public function renderAsXml(Client $client){}
}
Render an xml
Render an html
2 responsabilities
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {$this->renderAsHtml($client);
} else {throw new \InvalidTypeException();
}}
public function renderAsHtml(Client $client){}}
Render an html
Delegate xml
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {(new HtmlBillPrinter())->render($client);
} else {throw new \InvalidTypeException();
}}
}
Delegate html
Delegate xml
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {(new HtmlBillPrinter())->render($client);
} else {throw new \InvalidTypeException();
}}
}
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {(new HtmlBillPrinter())->render($client);
} else {throw new \InvalidTypeException();
}}
}interface BillPrinterInterface{
public function render(Client $client);}
NB: The same interface is used
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {(new HtmlBillPrinter())->render($client);
} else {throw new \InvalidTypeException();
}}
} It’s not my job to find what instantiate!I just want to render a bill!
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
if ($this->type === 'xml') {(new XmlBillPrinter())->render($client);
} elseif ($this->type === 'html') {(new HtmlBillPrinter())->render($client);
} else {throw new \InvalidTypeException();
}}
} It’s not my job to find what instantiate!I just want to render a bill!
Delegate to a private method first...
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
$this->getPrinterInstance()->render($client);}public function getPrinterInstance(){
if ($this->type === 'xml') {return new XmlBillPrinter();
} elseif ($this->type === 'html') {return new HtmlBillPrinter();
} else {throw new \InvalidTypeException();
}}
}
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
$this->getPrinterInstance()->render($client);}public function getPrinterInstance(){
if ($this->type === 'xml') {return new XmlBillPrinter();
} elseif ($this->type === 'html') {return new HtmlBillPrinter();
} else {throw new \InvalidTypeException();
}}
}
It’s really not my job to find what instantiate!
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
$this->getPrinterInstance()->render($client);}public function getPrinterInstance(){
if ($this->type === 'xml') {return new XmlBillPrinter();
} elseif ($this->type === 'html') {return new HtmlBillPrinter();
} else {throw new \InvalidTypeException();
}}
}
It’s really not my job to find what instantiate!
It’s a factory job!
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
BillPrinterFactory::create($this->type)->render($client);
}}class BillPrinterFactory{
public static function create(string $type){if ($type === 'xml') {
return new XmlBillPrinter();} else //...
}}
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
BillPrinterFactory::create($this->type)->render($client);
}}class BillPrinterFactory{
public static function create(string $type){if ($type === 'xml') {
return new XmlBillPrinter();} else //...
}}
What is the purpose of this class again?
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
BillPrinterFactory::create($this->type)->render($client);
}}class BillPrinterFactory{
public static function create(string $type){if ($type === 'xml') {
return new XmlBillPrinter();} else //...
}}
What is the purpose of this class again?
class BillPrinter{public function __construct(string $type) {
$this->type = $type;}public function renderClientBill(Client $client){
BillPrinterFactory::create($this->type)->render($client);
}}class BillPrinterFactory{
public static function create(string $type){if ($type === 'xml') {
return new XmlBillPrinter();} else //...
}}
Remove the unnecessary class :)
//What about the render of the bill in HTML?class HtmlBillPrinter
implements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';}return $render;
}}
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';}return $render;
}}
It renders lines
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';}return $render;
}}
It manages HTML and its attributes
It renders lines
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';}return $render;
}}
It manages HTML and its attributes
And who’s gonna escape string if necessary?
It renders lines
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$render .= '<div class="drink-line"';
if ($drink->containsAlcohol()) {$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';}return $render;
}}
It manages HTML and its attributes
And who’s gonna escape string if necessary?
It renders lines
It’s bibi!
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
return '<div class="drink-line"';$div = new HtmlTag('div');$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';}return $render;
}}
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {$render .= ' data-alcohol="1"';
}
$render.= '>'. $drink->getPrice() .'</div>';}return $render;
}}
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {return ' data-alcohol="1"'; $div->addAttribute('data-alcohol', '1');
}$render.= '>'. $drink->getPrice() .'</div>';
}return $render;
}}
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {$div->addAttribute('data-alcohol', '1');
}$render.= '>'. $drink->getPrice() .'</div>';
}return $render;
}}
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {$div->addAttribute('data-alcohol', '1');
}$render.= '>'. $drink->getPrice() .'</div>';$div->setHtml($drink->getPrice());return $div;
}}
}
class HtmlBillPrinterimplements BillPrinterInterface{
public function render(Client $client){foreach($client->getDrinks() as $drink){
$div = new HtmlTag('div');$div->addAttribute('class', 'drink-line');
if ($drink->containsAlcohol()) {$div->addAttribute('data-alcohol', '1');
}$div->setHtml($drink->getPrice());return $div;
}}
}
It’s all about making
Your code comprehensible
WRAPPING UP
Books
Thank you ☺Questions?