diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3d4b9b68 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +vendor +composer.lock +.phpunit.result.cache diff --git a/examples/php/behavioral/chain_of_responsibility/Account.php b/examples/php/behavioral/chain_of_responsibility/Account.php new file mode 100644 index 00000000..11a927c3 --- /dev/null +++ b/examples/php/behavioral/chain_of_responsibility/Account.php @@ -0,0 +1,50 @@ +next = $account; + } + + public function pay(float $amountToPay): string + { + if ($this->canPay($amountToPay)) { + return $this->payUsing($amountToPay); + } + + if ($this->next) { + return $this->cantPayUsing() . $this->next->pay($amountToPay); + } + + return 'Cannot pay, no more accounts in the chain'; + } + + private function canPay(float $amount): bool + { + return $this->balance >= $amount; + } + + private function payUsing(float $amountToPay): string + { + $this->balance -= $amountToPay; + + return sprintf('Paid %s using %s', $amountToPay, $this->getClassName()); + } + + private function cantPayUsing(): string + { + return sprintf('Cannot pay using %s. Proceeding ..' . PHP_EOL, $this->getClassName()); + } + + private function getClassName(): string + { + $className = explode('\\', static::class); + return array_pop($className); + } +} diff --git a/examples/php/behavioral/chain_of_responsibility/Bank.php b/examples/php/behavioral/chain_of_responsibility/Bank.php new file mode 100644 index 00000000..0d2ba2aa --- /dev/null +++ b/examples/php/behavioral/chain_of_responsibility/Bank.php @@ -0,0 +1,13 @@ +balance = $balance; + } +} diff --git a/examples/php/behavioral/chain_of_responsibility/Bitcoin.php b/examples/php/behavioral/chain_of_responsibility/Bitcoin.php new file mode 100644 index 00000000..e4c08c12 --- /dev/null +++ b/examples/php/behavioral/chain_of_responsibility/Bitcoin.php @@ -0,0 +1,13 @@ +balance = $balance; + } +} diff --git a/examples/php/behavioral/chain_of_responsibility/ChainOfResponsibility.png b/examples/php/behavioral/chain_of_responsibility/ChainOfResponsibility.png new file mode 100644 index 00000000..0f7c52aa Binary files /dev/null and b/examples/php/behavioral/chain_of_responsibility/ChainOfResponsibility.png differ diff --git a/examples/php/behavioral/chain_of_responsibility/ChainOfResponsibilityTest.php b/examples/php/behavioral/chain_of_responsibility/ChainOfResponsibilityTest.php new file mode 100644 index 00000000..74d89b22 --- /dev/null +++ b/examples/php/behavioral/chain_of_responsibility/ChainOfResponsibilityTest.php @@ -0,0 +1,58 @@ +$paypal->$bitcoin +// +// First priority bank +// If bank can't pay then paypal +// If paypal can't pay then bit coin + +$bank = new Bank(100); // Bank with balance 100 +$paypal = new Paypal(200); // Paypal with balance 200 +$bitcoin = new Bitcoin(300); // Bitcoin with balance 300 + +$bank->setNext($paypal); +$paypal->setNext($bitcoin); + +// Let's try to pay using the first priority i.e. bank +$bank->pay(259); + +// Output will be +// ============== +// Cannot pay using bank. Proceeding .. +// Cannot pay using paypal. Proceeding ..: +// Paid 259 using Bitcoin! +*/ + +class ChainOfResponsibilityTest extends TestCase +{ + public function test01(): void + { + $bank = new Bank(100); + $paypal = new Paypal(200); + $bitcoin = new Bitcoin(300); + + $bank->setNext($paypal); + $paypal->setNext($bitcoin); + + self::assertEquals("Cannot pay using Bank. Proceeding ..\nCannot pay using Paypal. Proceeding ..\nPaid 259 using Bitcoin", $bank->pay(259)); + } + + public function test02(): void + { + $bank = new Bank(100); + $paypal = new Paypal(200); + $bitcoin = new Bitcoin(100); + + $bank->setNext($paypal); + $paypal->setNext($bitcoin); + + self::assertEquals("Cannot pay using Bank. Proceeding ..\nCannot pay using Paypal. Proceeding ..\nCannot pay, no more accounts in the chain", $bank->pay(259)); + } +} diff --git a/examples/php/behavioral/chain_of_responsibility/Paypal.php b/examples/php/behavioral/chain_of_responsibility/Paypal.php new file mode 100644 index 00000000..21e03d36 --- /dev/null +++ b/examples/php/behavioral/chain_of_responsibility/Paypal.php @@ -0,0 +1,13 @@ +balance = $balance; + } +} diff --git a/examples/php/behavioral/command/Bulb.php b/examples/php/behavioral/command/Bulb.php new file mode 100644 index 00000000..8c9f9046 --- /dev/null +++ b/examples/php/behavioral/command/Bulb.php @@ -0,0 +1,16 @@ +submit($turnOn); // Bulb has been lit! +$remote->submit($turnOff); // Darkness! +*/ + +class CommandTest extends TestCase +{ + public function test01(): void + { + $bulb = new Bulb(); + + $turnOn = new TurnOn($bulb); + $turnOff = new TurnOff($bulb); + + $remote = new RemoteControl(); + self::assertEquals('Bulb has been lit!', $remote->submit($turnOn)); + self::assertEquals('Darkness!', $remote->submit($turnOff)); + } +} diff --git a/examples/php/behavioral/command/RemoteControl.php b/examples/php/behavioral/command/RemoteControl.php new file mode 100644 index 00000000..29b9c073 --- /dev/null +++ b/examples/php/behavioral/command/RemoteControl.php @@ -0,0 +1,11 @@ +execute(); + } +} diff --git a/examples/php/behavioral/command/TurnOff.php b/examples/php/behavioral/command/TurnOff.php new file mode 100644 index 00000000..526b8696 --- /dev/null +++ b/examples/php/behavioral/command/TurnOff.php @@ -0,0 +1,28 @@ +bulb = $bulb; + } + + public function execute(): string + { + return $this->bulb->turnOff(); + } + + public function undo(): string + { + return $this->bulb->turnOn(); + } + + public function redo(): string + { + return $this->execute(); + } +} diff --git a/examples/php/behavioral/command/TurnOn.php b/examples/php/behavioral/command/TurnOn.php new file mode 100644 index 00000000..e998e4d2 --- /dev/null +++ b/examples/php/behavioral/command/TurnOn.php @@ -0,0 +1,28 @@ +bulb = $bulb; + } + + public function execute(): string + { + return $this->bulb->turnOn(); + } + + public function undo(): string + { + return $this->bulb->turnOff(); + } + + public function redo(): string + { + return $this->execute(); + } +} diff --git a/examples/php/behavioral/interpreter/Interpreter.php b/examples/php/behavioral/interpreter/Interpreter.php new file mode 100644 index 00000000..a8143662 --- /dev/null +++ b/examples/php/behavioral/interpreter/Interpreter.php @@ -0,0 +1 @@ +addStation(new RadioStation(89)); +$stationList->addStation(new RadioStation(101)); +$stationList->addStation(new RadioStation(102)); +$stationList->addStation(new RadioStation(103.2)); + +foreach($stationList as $station) { + echo $station->getFrequency() . PHP_EOL; +} + +$stationList->removeStation(new RadioStation(89)); // Will remove station 89 +*/ + +class IteratorTest extends TestCase +{ + public function test01(): void + { + $stationList = new StationList(); + + $stationList->addStation(new RadioStation(89)); + $stationList->addStation(new RadioStation(101)); + $stationList->addStation(new RadioStation(102)); + $stationList->addStation(new RadioStation(103.2)); + + self::assertEquals(4, $stationList->count()); + + $expect = [89, 101, 102, 103.2]; + foreach($stationList as $key => $station) { + self::assertEquals($expect[$key], $station->getFrequency()); + } + + $stationList->removeStation(new RadioStation(89)); + self::assertEquals(3, $stationList->count()); + } +} diff --git a/examples/php/behavioral/iterator/RadioStation.php b/examples/php/behavioral/iterator/RadioStation.php new file mode 100644 index 00000000..8fa7971e --- /dev/null +++ b/examples/php/behavioral/iterator/RadioStation.php @@ -0,0 +1,18 @@ +frequency = $frequency; + } + + public function getFrequency(): float + { + return $this->frequency; + } +} diff --git a/examples/php/behavioral/iterator/StationList.php b/examples/php/behavioral/iterator/StationList.php new file mode 100644 index 00000000..5b89889b --- /dev/null +++ b/examples/php/behavioral/iterator/StationList.php @@ -0,0 +1,58 @@ +stations[] = $station; + } + + public function removeStation(RadioStation $toRemove): void + { + $toRemoveFrequency = $toRemove->getFrequency(); + $this->stations = array_filter($this->stations, function (RadioStation $station) use ($toRemoveFrequency) { + return $station->getFrequency() !== $toRemoveFrequency; + }); + } + + public function count(): int + { + return count($this->stations); + } + + public function current(): RadioStation + { + return $this->stations[$this->counter]; + } + + public function key(): int + { + return $this->counter; + } + + public function next(): void + { + $this->counter++; + } + + public function rewind(): void + { + $this->counter = 0; + } + + public function valid(): bool + { + return isset($this->stations[$this->counter]); + } +} diff --git a/examples/php/behavioral/mediator/ChatRoom.php b/examples/php/behavioral/mediator/ChatRoom.php new file mode 100644 index 00000000..2bb4f715 --- /dev/null +++ b/examples/php/behavioral/mediator/ChatRoom.php @@ -0,0 +1,26 @@ +getTime(); + $sender = $user->getName(); + + return $time . ' [' . $sender . ']: ' . $message; + } + + public function setTime(string $time): void + { + $this->time = $time; + } + + private function getTime(): string + { + return $this->time ? : date('M d, H:i'); + } +} diff --git a/examples/php/behavioral/mediator/ChatRoomMediator.php b/examples/php/behavioral/mediator/ChatRoomMediator.php new file mode 100644 index 00000000..473f87de --- /dev/null +++ b/examples/php/behavioral/mediator/ChatRoomMediator.php @@ -0,0 +1,8 @@ +send('Hi there!'); +$jane->send('Hey!'); + +// Output will be +// Feb 14, 10:58 [John]: Hi there! +// Feb 14, 10:58 [Jane]: Hey! +*/ + +class MediatorTest extends TestCase +{ + public function test01(): void + { + $mediator = new ChatRoom(); + $mediator->setTime('Feb 14, 10:58'); + + $john = new User('John', $mediator); + $jane = new User('Jane', $mediator); + + self::assertEquals('Feb 14, 10:58 [John]: Hi there!', $john->send('Hi there!')); + self::assertEquals('Feb 14, 10:58 [Jane]: Hey!',$jane ->send('Hey!')); + } +} diff --git a/examples/php/behavioral/mediator/User.php b/examples/php/behavioral/mediator/User.php new file mode 100644 index 00000000..ec3d82e0 --- /dev/null +++ b/examples/php/behavioral/mediator/User.php @@ -0,0 +1,25 @@ +name = $name; + $this->chatMediator = $chatMediator; + } + + public function getName(): string + { + return $this->name; + } + + public function send(string $message): string + { + return $this->chatMediator->showMessage($this, $message); + } +} diff --git a/examples/php/behavioral/memento/Editor.php b/examples/php/behavioral/memento/Editor.php new file mode 100644 index 00000000..f67f3c43 --- /dev/null +++ b/examples/php/behavioral/memento/Editor.php @@ -0,0 +1,28 @@ +content .= ' ' . $words; + } + + public function getContent(): string + { + return $this->content; + } + + public function save(): EditorMemento + { + return new EditorMemento($this->content); + } + + public function restore(EditorMemento $memento): void + { + $this->content = $memento->getContent(); + } +} diff --git a/examples/php/behavioral/memento/EditorMemento.php b/examples/php/behavioral/memento/EditorMemento.php new file mode 100644 index 00000000..2aea6c77 --- /dev/null +++ b/examples/php/behavioral/memento/EditorMemento.php @@ -0,0 +1,18 @@ +content = $content; + } + + public function getContent(): string + { + return $this->content; + } +} diff --git a/examples/php/behavioral/memento/Memento.png b/examples/php/behavioral/memento/Memento.png new file mode 100644 index 00000000..ea70f3da Binary files /dev/null and b/examples/php/behavioral/memento/Memento.png differ diff --git a/examples/php/behavioral/memento/MementoTest.php b/examples/php/behavioral/memento/MementoTest.php new file mode 100644 index 00000000..974716b8 --- /dev/null +++ b/examples/php/behavioral/memento/MementoTest.php @@ -0,0 +1,53 @@ +type('This is the first sentence.'); +$editor->type('This is second.'); + +// Save the state to restore to : This is the first sentence. This is second. +$saved = $editor->save(); + +// Type some more +$editor->type('And this is third.'); + +// Output: Content before Saving +echo $editor->getContent(); // This is the first sentence. This is second. And this is third. + +// Restoring to last saved state +$editor->restore($saved); + +$editor->getContent(); // This is the first sentence. This is second. +*/ + +class MementoTest extends TestCase +{ + public function test01(): void + { + $editor = new Editor(); + + // Type some stuff + $editor->type('This is the first sentence.'); + $editor->type('This is second.'); + + // Save the state to restore to : This is the first sentence. This is second. + $saved = $editor->save(); + + // Type some more + $editor->type('And this is third.'); + + // Output: Content before Saving + self::assertEquals(' This is the first sentence. This is second. And this is third.', $editor->getContent()); + + // Restoring to last saved state + $editor->restore($saved); + + self::assertEquals(' This is the first sentence. This is second.', $editor->getContent()); + } +} diff --git a/examples/php/behavioral/observer/EmploymentAgency.php b/examples/php/behavioral/observer/EmploymentAgency.php new file mode 100644 index 00000000..893d7164 --- /dev/null +++ b/examples/php/behavioral/observer/EmploymentAgency.php @@ -0,0 +1,28 @@ +observers as $observer) { + $observer->update($jobPost); + } + } + + public function attach(Observer $observer): void + { + $this->observers[] = $observer; + } + + public function detach(Observer $observer): void + { + $key = array_search($observer, $this->observers, true); + if ($key !== false) { + unset($this->observers[$key]); + } + } +} diff --git a/examples/php/behavioral/observer/JobPost.php b/examples/php/behavioral/observer/JobPost.php new file mode 100644 index 00000000..58cfebc5 --- /dev/null +++ b/examples/php/behavioral/observer/JobPost.php @@ -0,0 +1,18 @@ +title = $title; + } + + public function getTitle(): string + { + return $this->title; + } +} diff --git a/examples/php/behavioral/observer/JobSeeker.php b/examples/php/behavioral/observer/JobSeeker.php new file mode 100644 index 00000000..0bbf4226 --- /dev/null +++ b/examples/php/behavioral/observer/JobSeeker.php @@ -0,0 +1,25 @@ +name = $name; + } + + public function update(JobPost $jobPost): void + { + // Do something with the job posting + $this->lastNotice = 'Hi ' . $this->name . '! New job posted: ' . $jobPost->getTitle(); + } + + public function getLastNotice(): string + { + return $this->lastNotice; + } +} diff --git a/examples/php/behavioral/observer/Observable.php b/examples/php/behavioral/observer/Observable.php new file mode 100644 index 00000000..e407552a --- /dev/null +++ b/examples/php/behavioral/observer/Observable.php @@ -0,0 +1,10 @@ +attach($johnDoe); +$jobPostings->attach($janeDoe); + +// Add a new job and see if subscribers get notified +$jobPostings->addJob(new JobPost('Software Engineer')); + +// Output +// Hi John Doe! New job posted: Software Engineer +// Hi Jane Doe! New job posted: Software Engineer +*/ + +class ObserverTest extends TestCase +{ + public function test01(): void + { + // Create subscribers + $johnDoe = new JobSeeker('John Doe'); + $janeDoe = new JobSeeker('Jane Doe'); + + // Create publisher and attach subscribers + $jobPostings = new EmploymentAgency(); + $jobPostings->attach($johnDoe); + $jobPostings->attach($janeDoe); + + // Add a new job and see if subscribers get notified + $jobPostings->notify(new JobPost('Software Engineer')); + + // Output + self::assertEquals('Hi John Doe! New job posted: Software Engineer', $johnDoe->getLastNotice()); + self::assertEquals('Hi Jane Doe! New job posted: Software Engineer', $janeDoe->getLastNotice()); + + + // Detach a subscriber + $jobPostings->detach($janeDoe); + + // Add a new job and see if subscribers get notified + $jobPostings->notify(new JobPost('Engineering Manager')); + + self::assertEquals('Hi John Doe! New job posted: Engineering Manager', $johnDoe->getLastNotice()); + self::assertEquals('Hi Jane Doe! New job posted: Software Engineer', $janeDoe->getLastNotice()); + } +} diff --git a/examples/php/behavioral/state/DefaultText.php b/examples/php/behavioral/state/DefaultText.php new file mode 100644 index 00000000..024b853d --- /dev/null +++ b/examples/php/behavioral/state/DefaultText.php @@ -0,0 +1,11 @@ +type('First line'); + +$editor->setState(new UpperCase()); + +$editor->type('Second line'); +$editor->type('Third line'); + +$editor->setState(new LowerCase()); + +$editor->type('Fourth line'); +$editor->type('Fifth line'); + +// Output: +// First line +// SECOND LINE +// THIRD LINE +// fourth line +// fifth line +*/ +class StateTest extends TestCase +{ + protected function setUp(): void + { + parent::setUp(); + echo 1, "\n"; + } + + public function test01():void + { + $editor = new TextEditor(new DefaultText()); + + self::assertEquals('First line', $editor->type('First line')); + + $editor->setState(new UpperCase()); + + self::assertEquals('SECOND LINE', $editor->type('Second line')); + self::assertEquals('THIRD LINE', $editor->type('Third line')); + + $editor->setState(new LowerCase()); + + self::assertEquals('fourth line', $editor->type('Fourth line')); + self::assertEquals('fifth line', $editor->type('Fifth line')); + } + + public function test02():void + { + $editor = new TextEditor(new DefaultText()); + + self::assertEquals('First line', $editor->type('First line')); + + $editor->setState(new UpperCase()); + + self::assertEquals('SECOND LINE', $editor->type('Second line')); + self::assertEquals('THIRD LINE', $editor->type('Third line')); + + $editor->setState(new LowerCase()); + + self::assertEquals('fourth line', $editor->type('Fourth line')); + self::assertEquals('fifth line', $editor->type('Fifth line')); + } + + protected function tearDown(): void + { + parent::tearDown(); + echo 2, "\n"; + } +} diff --git a/examples/php/behavioral/state/TextEditor.php b/examples/php/behavioral/state/TextEditor.php new file mode 100644 index 00000000..1c839890 --- /dev/null +++ b/examples/php/behavioral/state/TextEditor.php @@ -0,0 +1,23 @@ +state = $state; + } + + public function setState(WritingState $state): void + { + $this->state = $state; + } + + public function type(string $words): string + { + return $this->state->write($words); + } +} diff --git a/examples/php/behavioral/state/UpperCase.php b/examples/php/behavioral/state/UpperCase.php new file mode 100644 index 00000000..f1a16c23 --- /dev/null +++ b/examples/php/behavioral/state/UpperCase.php @@ -0,0 +1,11 @@ +sorter = $sorter; + } + + public function sort(array $dataset): string + { + return $this->sorter->sort($dataset); + } +} diff --git a/examples/php/behavioral/strategy/Strategy.png b/examples/php/behavioral/strategy/Strategy.png new file mode 100644 index 00000000..aa9e89ed Binary files /dev/null and b/examples/php/behavioral/strategy/Strategy.png differ diff --git a/examples/php/behavioral/strategy/StrategyTest.php b/examples/php/behavioral/strategy/StrategyTest.php new file mode 100644 index 00000000..9b36249e --- /dev/null +++ b/examples/php/behavioral/strategy/StrategyTest.php @@ -0,0 +1,29 @@ +sort($dataset); // Output : Sorting using bubble sort + +$sorter = new Sorter(new QuickSortStrategy()); +$sorter->sort($dataset); // Output : Sorting using quick sort +*/ + +class StrategyTest extends TestCase +{ + public function test01(): void + { + $dataset = [1, 5, 4, 3, 2, 8]; + + $sorter = new Sorter(new BubbleSortStrategy()); + self::assertEquals('Sorting using bubble sort', $sorter->sort($dataset)); + + $sorter = new Sorter(new QuickSortStrategy()); + self::assertEquals('Sorting using quick sort', $sorter->sort($dataset)); + } +} diff --git a/examples/php/behavioral/template_method/AndroidBuilder.php b/examples/php/behavioral/template_method/AndroidBuilder.php new file mode 100644 index 00000000..5635f481 --- /dev/null +++ b/examples/php/behavioral/template_method/AndroidBuilder.php @@ -0,0 +1,26 @@ +test(), + $this->lint(), + $this->assemble(), + $this->deploy() + ); + } + + abstract public function test(): string; + abstract public function lint(): string; + abstract public function assemble(): string; + abstract public function deploy(): string; +} diff --git a/examples/php/behavioral/template_method/IosBuilder.php b/examples/php/behavioral/template_method/IosBuilder.php new file mode 100644 index 00000000..f451dfa5 --- /dev/null +++ b/examples/php/behavioral/template_method/IosBuilder.php @@ -0,0 +1,26 @@ +build(); + +// Output: +// Running android tests +// Linting the android code +// Assembling the android build +// Deploying android build to server + +$iosBuilder = new IosBuilder(); +$iosBuilder->build(); + +// Output: +// Running ios tests +// Linting the ios code +// Assembling the ios build +// Deploying ios build to server +*/ + +class TemplateMethodTest extends TestCase +{ + public function test01(): void + { + $androidBuilder = new AndroidBuilder(); + self::assertEquals("Running android tests\nLinting the android code\nAssembling the android build\nDeploying android build to server", $androidBuilder->build()); + } + + public function test02(): void + { + $iosBuilder = new IosBuilder(); + self::assertEquals("Running ios tests\nLinting the ios code\nAssembling the ios build\nDeploying ios build to server", $iosBuilder->build()); + } +} diff --git a/examples/php/behavioral/visitor/Animal.php b/examples/php/behavioral/visitor/Animal.php new file mode 100644 index 00000000..b39383d8 --- /dev/null +++ b/examples/php/behavioral/visitor/Animal.php @@ -0,0 +1,8 @@ +visitDolphin($this); + } +} diff --git a/examples/php/behavioral/visitor/Jump.php b/examples/php/behavioral/visitor/Jump.php new file mode 100644 index 00000000..39d4e429 --- /dev/null +++ b/examples/php/behavioral/visitor/Jump.php @@ -0,0 +1,21 @@ +visitLion($this); + } +} diff --git a/examples/php/behavioral/visitor/Monkey.php b/examples/php/behavioral/visitor/Monkey.php new file mode 100644 index 00000000..bc0fc876 --- /dev/null +++ b/examples/php/behavioral/visitor/Monkey.php @@ -0,0 +1,16 @@ +visitMonkey($this); + } +} diff --git a/examples/php/behavioral/visitor/Speak.php b/examples/php/behavioral/visitor/Speak.php new file mode 100644 index 00000000..855a97c1 --- /dev/null +++ b/examples/php/behavioral/visitor/Speak.php @@ -0,0 +1,21 @@ +shout(); + } + + public function visitLion(Lion $lion): string + { + return $lion->roar(); + } + + public function visitDolphin(Dolphin $dolphin): string + { + return $dolphin->speak(); + } +} diff --git a/examples/php/behavioral/visitor/Visitor.png b/examples/php/behavioral/visitor/Visitor.png new file mode 100644 index 00000000..8af614b4 Binary files /dev/null and b/examples/php/behavioral/visitor/Visitor.png differ diff --git a/examples/php/behavioral/visitor/VisitorTest.php b/examples/php/behavioral/visitor/VisitorTest.php new file mode 100644 index 00000000..ab46c617 --- /dev/null +++ b/examples/php/behavioral/visitor/VisitorTest.php @@ -0,0 +1,55 @@ +accept($speak); // Ooh oo aa aa! +$lion->accept($speak); // Roaaar! +$dolphin->accept($speak); // Tuut tutt tuutt! + +$jump = new Jump(); + +$monkey->accept($speak); // Ooh oo aa aa! +$monkey->accept($jump); // Jumped 20 feet high! on to the tree! + +$lion->accept($speak); // Roaaar! +$lion->accept($jump); // Jumped 7 feet! Back on the ground! + +$dolphin->accept($speak); // Tuut tutt tuutt! +$dolphin->accept($jump); // Walked on water a little and disappeared +*/ + +class VisitorTest extends TestCase +{ + public function test01(): void + { + $monkey = new Monkey(); + $lion = new Lion(); + $dolphin = new Dolphin(); + + $speak = new Speak(); + + self::assertEquals('Ooh oo aa aa!', $monkey->accept($speak)); + self::assertEquals('Roaaar!', $lion->accept($speak)); + self::assertEquals('Tuut tutt tuutt!', $dolphin->accept($speak)); + + $jump = new Jump(); + + self::assertEquals('Ooh oo aa aa!', $monkey->accept($speak)); + self::assertEquals('Jumped 20 feet high! on to the tree!', $monkey->accept($jump)); + + self::assertEquals('Roaaar!', $lion->accept($speak)); + self::assertEquals('Jumped 7 feet! Back on the ground!', $lion->accept($jump)); + + self::assertEquals('Tuut tutt tuutt!', $dolphin->accept($speak)); + self::assertEquals('Walked on water a little and disappeared', $dolphin->accept($jump)); + } +} diff --git a/examples/php/composer.json b/examples/php/composer.json new file mode 100644 index 00000000..a9d2ca51 --- /dev/null +++ b/examples/php/composer.json @@ -0,0 +1,16 @@ +{ + "name": "kamranahmedse/design-patterns-for-humans", + "description": "design-patterns-for-humans", + "type": "project", + "autoload": { + "psr-4": { + "design_patterns\\": "." + } + }, + "require": { + }, + "require-dev": { + "phpunit/phpunit": "~9.0", + "roave/security-advisories": "dev-latest" + } +} diff --git a/examples/php/creational/abstract_factory/AbstractFactory.png b/examples/php/creational/abstract_factory/AbstractFactory.png new file mode 100644 index 00000000..f8b1f397 Binary files /dev/null and b/examples/php/creational/abstract_factory/AbstractFactory.png differ diff --git a/examples/php/creational/abstract_factory/AbstractFactoryTest.php b/examples/php/creational/abstract_factory/AbstractFactoryTest.php new file mode 100644 index 00000000..87521878 --- /dev/null +++ b/examples/php/creational/abstract_factory/AbstractFactoryTest.php @@ -0,0 +1,53 @@ +makeDoor(); +$expert = $woodenFactory->makeFittingExpert(); + +$door->getDescription(); // Output: I am a wooden door +$expert->getDescription(); // Output: I can only fit wooden doors + +// Iron door factory to get iron door and the relevant fitting expert + +// Same for Iron Factory +$ironFactory = new IronDoorFactory(); + +$door = $ironFactory->makeDoor(); +$expert = $ironFactory->makeFittingExpert(); + +$door->getDescription(); // Output: I am an iron door +$expert->getDescription(); // Output: I can only fit iron doors +*/ + +class AbstractFactoryTest extends TestCase +{ + public function test01(): void + { + $woodenFactory = new WoodenDoorFactory(); + + $door = $woodenFactory->makeDoor(); + $expert = $woodenFactory->makeFittingExpert(); + + self::assertEquals("I am a wooden door", $door->getDescription()); + self::assertEquals("I can only fit wooden doors", $expert->getDescription()); + } + + public function test02(): void + { + $ironFactory = new IronDoorFactory(); + + $door = $ironFactory->makeDoor(); + $expert = $ironFactory->makeFittingExpert(); + + self::assertEquals("I am an iron door", $door->getDescription()); + self::assertEquals("I can only fit iron doors", $expert->getDescription()); + } +} diff --git a/examples/php/creational/abstract_factory/Carpenter.php b/examples/php/creational/abstract_factory/Carpenter.php new file mode 100644 index 00000000..246fc728 --- /dev/null +++ b/examples/php/creational/abstract_factory/Carpenter.php @@ -0,0 +1,11 @@ +size = $builder->size; + $this->cheese = $builder->cheese; + $this->pepperoni = $builder->pepperoni; + $this->lettuce = $builder->lettuce; + $this->tomato = $builder->tomato; + } +} diff --git a/examples/php/creational/builder/Builder.png b/examples/php/creational/builder/Builder.png new file mode 100644 index 00000000..525b5923 Binary files /dev/null and b/examples/php/creational/builder/Builder.png differ diff --git a/examples/php/creational/builder/BuilderTest.php b/examples/php/creational/builder/BuilderTest.php new file mode 100644 index 00000000..4e20f237 --- /dev/null +++ b/examples/php/creational/builder/BuilderTest.php @@ -0,0 +1,23 @@ +addPepperoni() + ->addLettuce() + ->addTomato() + ->build(); + + self::assertEquals(14, $burger->size); + self::assertFalse($burger->cheese); + self::assertTrue($burger->pepperoni); + self::assertTrue($burger->lettuce); + self::assertTrue($burger->tomato); + } +} diff --git a/examples/php/creational/builder/BurgerBuilder.php b/examples/php/creational/builder/BurgerBuilder.php new file mode 100644 index 00000000..14574347 --- /dev/null +++ b/examples/php/creational/builder/BurgerBuilder.php @@ -0,0 +1,47 @@ +size = $size; + } + + public function addPepperoni(): BurgerBuilder + { + $this->pepperoni = true; + return $this; + } + + public function addLettuce(): BurgerBuilder + { + $this->lettuce = true; + return $this; + } + + public function addCheese(): BurgerBuilder + { + $this->cheese = true; + return $this; + } + + public function addTomato(): BurgerBuilder + { + $this->tomato = true; + return $this; + } + + public function build(): Builder + { + return new Builder($this); + } +} diff --git a/examples/php/creational/factory_method/CommunityExecutive.php b/examples/php/creational/factory_method/CommunityExecutive.php new file mode 100644 index 00000000..2c127300 --- /dev/null +++ b/examples/php/creational/factory_method/CommunityExecutive.php @@ -0,0 +1,11 @@ +takeInterview(); // Output: Asking about design patterns. + +$marketingManager = new MarketingManager(); +$marketingManager->takeInterview(); // Output: Asking about community building. +*/ + +class FactoryMethodTest extends TestCase +{ + public function test01(): void + { + $devManager = new DevelopmentManager(); + self::assertEquals('Asking about design patterns.', $devManager->takeInterview()); + + $marketingManager = new MarketingManager(); + self::assertEquals('Asking about community building.', $marketingManager->takeInterview()); + } +} diff --git a/examples/php/creational/factory_method/HiringManager.php b/examples/php/creational/factory_method/HiringManager.php new file mode 100644 index 00000000..fa93065f --- /dev/null +++ b/examples/php/creational/factory_method/HiringManager.php @@ -0,0 +1,14 @@ +makeInterviewer()->askQuestions(); + } +} diff --git a/examples/php/creational/factory_method/Interviewer.php b/examples/php/creational/factory_method/Interviewer.php new file mode 100644 index 00000000..7ec993a1 --- /dev/null +++ b/examples/php/creational/factory_method/Interviewer.php @@ -0,0 +1,8 @@ +getName(); // Jolly +echo $original->getCategory(); // Mountain Sheep + +// Clone and modify what is required +$cloned = clone $original; +$cloned->setName('Dolly'); +echo $cloned->getName(); // Dolly +echo $cloned->getCategory(); // Mountain sheep +*/ + +class PrototypeTest extends TestCase +{ + public function test01(): void + { + $original = new Sheep('Jolly'); + self::assertEquals('Jolly', $original->getName()); + self::assertEquals('Mountain Sheep', $original->getCategory()); + + // Clone and modify what is required + $cloned = clone $original; + $cloned->setName('Dolly'); + self::assertEquals('Dolly', $cloned->getName()); + self::assertEquals('Mountain Sheep', $cloned->getCategory()); + } +} diff --git a/examples/php/creational/prototype/Sheep.php b/examples/php/creational/prototype/Sheep.php new file mode 100644 index 00000000..1a353f23 --- /dev/null +++ b/examples/php/creational/prototype/Sheep.php @@ -0,0 +1,35 @@ +name = $name; + $this->category = $category; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function setCategory(string $category): void + { + $this->category = $category; + } + + public function getCategory(): string + { + return $this->category; + } +} diff --git a/examples/php/creational/simple_factory/Door.php b/examples/php/creational/simple_factory/Door.php new file mode 100644 index 00000000..b019be4c --- /dev/null +++ b/examples/php/creational/simple_factory/Door.php @@ -0,0 +1,9 @@ +getWidth(); +//echo 'Height: ' . $door->getHeight(); +// +//// Make me a door of 50x100 +//$door2 = DoorFactory::makeDoor(50, 100); + +class SimpleFactoryTest extends TestCase +{ + public function test01(): void + { + $door = DoorFactory::makeDoor(100, 200); + self::assertEquals(100, $door->getWidth()); + self::assertEquals(200, $door->getHeight()); + } + + public function test02(): void + { + $door2 = DoorFactory::makeDoor(50, 100); + self::assertEquals(50, $door2->getWidth()); + self::assertEquals(100, $door2->getHeight()); + } +} diff --git a/examples/php/creational/simple_factory/WoodenDoor.php b/examples/php/creational/simple_factory/WoodenDoor.php new file mode 100644 index 00000000..abc64c85 --- /dev/null +++ b/examples/php/creational/simple_factory/WoodenDoor.php @@ -0,0 +1,25 @@ +width = $width; + $this->height = $height; + } + + public function getWidth(): float + { + return $this->width; + } + + public function getHeight(): float + { + return $this->height; + } +} diff --git a/examples/php/creational/singleton/President.php b/examples/php/creational/singleton/President.php new file mode 100644 index 00000000..63eafff3 --- /dev/null +++ b/examples/php/creational/singleton/President.php @@ -0,0 +1,32 @@ +hunt($wildDogAdapter); +*/ + +class AdapterTest extends TestCase +{ + public function test01(): void + { + $wildDog = new WildDog(); + $wildDogAdapter = new WildDogAdapter($wildDog); + + $hunter = new Hunter(); + + self::assertEquals("WildDog: Bark!", $hunter->hunt($wildDogAdapter)); + } +} diff --git a/examples/php/structural/adapter/AfricanLion.php b/examples/php/structural/adapter/AfricanLion.php new file mode 100644 index 00000000..8238653b --- /dev/null +++ b/examples/php/structural/adapter/AfricanLion.php @@ -0,0 +1,11 @@ +roar(); + } +} diff --git a/examples/php/structural/adapter/Lion.php b/examples/php/structural/adapter/Lion.php new file mode 100644 index 00000000..481ae868 --- /dev/null +++ b/examples/php/structural/adapter/Lion.php @@ -0,0 +1,8 @@ +dog = $dog; + } + + public function roar(): string + { + return $this->dog->bark(); + } +} diff --git a/examples/php/structural/bridge/About.php b/examples/php/structural/bridge/About.php new file mode 100644 index 00000000..60100286 --- /dev/null +++ b/examples/php/structural/bridge/About.php @@ -0,0 +1,18 @@ +theme = $theme; + } + + public function getContent(): string + { + return "About page in " . $this->theme->getColor(); + } +} diff --git a/examples/php/structural/bridge/AquaTheme.php b/examples/php/structural/bridge/AquaTheme.php new file mode 100644 index 00000000..50ff04c0 --- /dev/null +++ b/examples/php/structural/bridge/AquaTheme.php @@ -0,0 +1,11 @@ +getContent(); // "About page in Dark Black"; +echo $careers->getContent(); // "Careers page in Dark Black"; +*/ + +class BridgeTest extends TestCase +{ + public function test01(): void + { + $darkTheme = new DarkTheme(); + + $about = new About($darkTheme); + $careers = new Careers($darkTheme); + + $this->assertEquals('About page in Dark Black', $about->getContent()); + $this->assertEquals('Careers page in Dark Black', $careers->getContent()); + } + + public function test02(): void + { + $lightTheme = new LightTheme(); + + $about = new About($lightTheme); + $careers = new Careers($lightTheme); + + $this->assertEquals('About page in Off White', $about->getContent()); + $this->assertEquals('Careers page in Off White', $careers->getContent()); + } +} diff --git a/examples/php/structural/bridge/Careers.php b/examples/php/structural/bridge/Careers.php new file mode 100644 index 00000000..3aa94071 --- /dev/null +++ b/examples/php/structural/bridge/Careers.php @@ -0,0 +1,18 @@ +theme = $theme; + } + + public function getContent(): string + { + return "Careers page in " . $this->theme->getColor(); + } +} diff --git a/examples/php/structural/bridge/DarkTheme.php b/examples/php/structural/bridge/DarkTheme.php new file mode 100644 index 00000000..2bfb0287 --- /dev/null +++ b/examples/php/structural/bridge/DarkTheme.php @@ -0,0 +1,11 @@ +addEmployee($john); +$organization->addEmployee($jane); + +echo "Net salaries: " . $organization->getNetSalaries(); // Net Salaries: 27000 +*/ + +class CompositeTest extends TestCase +{ + public function test01(): void + { + $john = new Developer('John Doe', 12000); + $jane = new Designer('Jane Doe', 15000); + + $organization = new Organization(); + $organization->addEmployee($john); + $organization->addEmployee($jane); + + self::assertEquals(27000, $organization->getNetSalaries()); + } +} diff --git a/examples/php/structural/composite/Designer.php b/examples/php/structural/composite/Designer.php new file mode 100644 index 00000000..465c8d2c --- /dev/null +++ b/examples/php/structural/composite/Designer.php @@ -0,0 +1,36 @@ +name = $name; + $this->salary = $salary; + } + + public function getName(): string + { + return $this->name; + } + + public function setSalary(float $salary): void + { + $this->salary = $salary; + } + + public function getSalary(): float + { + return $this->salary; + } + + public function getRoles(): array + { + return $this->roles; + } +} diff --git a/examples/php/structural/composite/Developer.php b/examples/php/structural/composite/Developer.php new file mode 100644 index 00000000..faabac83 --- /dev/null +++ b/examples/php/structural/composite/Developer.php @@ -0,0 +1,36 @@ +name = $name; + $this->salary = $salary; + } + + public function getName(): string + { + return $this->name; + } + + public function setSalary(float $salary): void + { + $this->salary = $salary; + } + + public function getSalary(): float + { + return $this->salary; + } + + public function getRoles(): array + { + return $this->roles; + } +} diff --git a/examples/php/structural/composite/Employee.php b/examples/php/structural/composite/Employee.php new file mode 100644 index 00000000..adba3d71 --- /dev/null +++ b/examples/php/structural/composite/Employee.php @@ -0,0 +1,12 @@ +employees[] = $employee; + } + + public function getNetSalaries(): float + { + $netSalary = 0; + + foreach ($this->employees as $employee) { + $netSalary += $employee->getSalary(); + } + + return $netSalary; + } +} diff --git a/examples/php/structural/decorator/Coffee.php b/examples/php/structural/decorator/Coffee.php new file mode 100644 index 00000000..6a6fceba --- /dev/null +++ b/examples/php/structural/decorator/Coffee.php @@ -0,0 +1,9 @@ +getCost(); // 10 +echo $someCoffee->getDescription(); // Simple Coffee + +$someCoffee = new MilkCoffee($someCoffee); +echo $someCoffee->getCost(); // 12 +echo $someCoffee->getDescription(); // Simple Coffee, milk + +$someCoffee = new WhipCoffee($someCoffee); +echo $someCoffee->getCost(); // 17 +echo $someCoffee->getDescription(); // Simple Coffee, milk, whip + +$someCoffee = new VanillaCoffee($someCoffee); +echo $someCoffee->getCost(); // 20 +echo $someCoffee->getDescription(); // Simple Coffee, milk, whip, vanilla +*/ + +class DecoratorTest extends TestCase +{ + public function test01(): void + { + $someCoffee = new SimpleCoffee(); + self::assertEquals(10, $someCoffee->getCost()); + self::assertEquals('Simple Coffee', $someCoffee->getDescription()); + + $someCoffee = new MilkCoffee($someCoffee); + self::assertEquals(12, $someCoffee->getCost()); + self::assertEquals('Simple Coffee, milk', $someCoffee->getDescription()); + + $someCoffee = new WhipCoffee($someCoffee); + self::assertEquals(17, $someCoffee->getCost()); + self::assertEquals('Simple Coffee, milk, whip', $someCoffee->getDescription()); + + $someCoffee = new VanillaCoffee($someCoffee); + self::assertEquals(20, $someCoffee->getCost()); + self::assertEquals('Simple Coffee, milk, whip, vanilla', $someCoffee->getDescription()); + } +} diff --git a/examples/php/structural/decorator/MilkCoffee.php b/examples/php/structural/decorator/MilkCoffee.php new file mode 100644 index 00000000..66a0c2b2 --- /dev/null +++ b/examples/php/structural/decorator/MilkCoffee.php @@ -0,0 +1,23 @@ +coffee = $coffee; + } + + public function getCost(): float + { + return $this->coffee->getCost() + 2; + } + + public function getDescription(): string + { + return $this->coffee->getDescription() . ', milk'; + } +} diff --git a/examples/php/structural/decorator/SimpleCoffee.php b/examples/php/structural/decorator/SimpleCoffee.php new file mode 100644 index 00000000..09e56cd6 --- /dev/null +++ b/examples/php/structural/decorator/SimpleCoffee.php @@ -0,0 +1,16 @@ +coffee = $coffee; + } + + public function getCost(): float + { + return $this->coffee->getCost() + 3; + } + + public function getDescription(): string + { + return $this->coffee->getDescription() . ', vanilla'; + } +} diff --git a/examples/php/structural/decorator/WhipCoffee.php b/examples/php/structural/decorator/WhipCoffee.php new file mode 100644 index 00000000..ab3b9ebb --- /dev/null +++ b/examples/php/structural/decorator/WhipCoffee.php @@ -0,0 +1,23 @@ +coffee = $coffee; + } + + public function getCost(): float + { + return $this->coffee->getCost() + 5; + } + + public function getDescription(): string + { + return $this->coffee->getDescription() . ', whip'; + } +} diff --git a/examples/php/structural/facade/Computer.php b/examples/php/structural/facade/Computer.php new file mode 100644 index 00000000..2d1861a0 --- /dev/null +++ b/examples/php/structural/facade/Computer.php @@ -0,0 +1,41 @@ +computer = $computer; + } + + public function turnOn(): string + { + return sprintf( + '%s %s %s %s', + $this->computer->getElectricShock(), + $this->computer->makeSound(), + $this->computer->showLoadingScreen(), + $this->computer->bam() + ); + } + + public function turnOff(): string + { + return sprintf( + '%s %s %s', + $this->computer->closeEverything(), + $this->computer->pullCurrent(), + $this->computer->sooth() + ); + } +} diff --git a/examples/php/structural/facade/Facade.png b/examples/php/structural/facade/Facade.png new file mode 100644 index 00000000..a73846fe Binary files /dev/null and b/examples/php/structural/facade/Facade.png differ diff --git a/examples/php/structural/facade/FacadeTest.php b/examples/php/structural/facade/FacadeTest.php new file mode 100644 index 00000000..6613078a --- /dev/null +++ b/examples/php/structural/facade/FacadeTest.php @@ -0,0 +1,21 @@ +turnOn(); // Ouch! Beep beep! Loading.. Ready to be used! +$computer->turnOff(); // Bup bup buzzz! Haah! Zzzzz +*/ + +class FacadeTest extends TestCase +{ + public function test01(): void + { + $computer = new ComputerFacade(new Computer()); + self::assertEquals('Ouch! Beep beep! Loading.. Ready to be used!', $computer->turnOn()); + self::assertEquals('Bup bup bup buzzzz! Haaah! Zzzzz', $computer->turnOff()); + } +} diff --git a/examples/php/structural/flyweight/Flyweight.png b/examples/php/structural/flyweight/Flyweight.png new file mode 100644 index 00000000..fd0edf57 Binary files /dev/null and b/examples/php/structural/flyweight/Flyweight.png differ diff --git a/examples/php/structural/flyweight/FlyweightTest.php b/examples/php/structural/flyweight/FlyweightTest.php new file mode 100644 index 00000000..a2123104 --- /dev/null +++ b/examples/php/structural/flyweight/FlyweightTest.php @@ -0,0 +1,34 @@ +takeOrder('less sugar', 1); +$shop->takeOrder('more milk', 2); +$shop->takeOrder('without sugar', 5); + +$shop->serve(); +// Serving tea to table# 1 +// Serving tea to table# 2 +// Serving tea to table# 5 +*/ + +class FlyweightTest extends TestCase +{ + public function test01(): void + { + $teaMaker = new TeaMaker(); + $shop = new TeaShop($teaMaker); + + $shop->takeOrder('less sugar', 1); + $shop->takeOrder('more milk', 2); + $shop->takeOrder('without sugar', 5); + + self::assertEquals("Serving tea to table 1...\nServing tea to table 2...\nServing tea to table 5...\n", $shop->serve()); + } +} diff --git a/examples/php/structural/flyweight/KarakTea.php b/examples/php/structural/flyweight/KarakTea.php new file mode 100644 index 00000000..f81830f2 --- /dev/null +++ b/examples/php/structural/flyweight/KarakTea.php @@ -0,0 +1,9 @@ +availableTea[$preference])) { + $this->availableTea[$preference] = new KarakTea(); + } + + return $this->availableTea[$preference]; + } +} diff --git a/examples/php/structural/flyweight/TeaShop.php b/examples/php/structural/flyweight/TeaShop.php new file mode 100644 index 00000000..4a95fe29 --- /dev/null +++ b/examples/php/structural/flyweight/TeaShop.php @@ -0,0 +1,29 @@ +teaMaker = $teaMaker; + } + + public function takeOrder(string $teaType, int $table): void + { + $this->orders[$table] = $this->teaMaker->make($teaType); + } + + public function serve(): string + { + $output = ''; + foreach ($this->orders as $table => $tea) { + $output .= "Serving tea to table {$table}...\n"; + } + + return $output; + } +} diff --git a/examples/php/structural/proxy/Door.php b/examples/php/structural/proxy/Door.php new file mode 100644 index 00000000..c851f0a8 --- /dev/null +++ b/examples/php/structural/proxy/Door.php @@ -0,0 +1,9 @@ +open('invalid'); // Big no! It ain't possible. + +$door->open('$ecr@t'); // Opening lab door +$door->close(); // Closing lab door +*/ + +class ProxyTest extends TestCase +{ + public function test01(): void + { + $door = new SecuredDoor(new LabDoor()); + + self::assertEquals("Big no! It ain't possible.", $door->open('invalid')); + self::assertEquals('Opening lab door', $door->open('$ecr@t')); + self::assertEquals('Closing lab door', $door->close()); + } +} diff --git a/examples/php/structural/proxy/SecuredDoor.php b/examples/php/structural/proxy/SecuredDoor.php new file mode 100644 index 00000000..542f6018 --- /dev/null +++ b/examples/php/structural/proxy/SecuredDoor.php @@ -0,0 +1,32 @@ +door = $door; + } + + public function open(string $password): string + { + if (!$this->authenticate($password)) { + return "Big no! It ain't possible."; + } + + return $this->door->open(); + } + + public function authenticate(string $password): bool + { + return $password === '$ecr@t'; + } + + public function close(): string + { + return $this->door->close(); + } +}