Added Decorator and observer Patterns along with UnitTest for Observer

This commit is contained in:
Yevhen Odynets 2025-07-06 14:36:53 +03:00
parent dabbf541dc
commit f76eb08fe0
20 changed files with 1887 additions and 126 deletions

8
.idea/laravel-idea.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="InertiaPackage">
<option name="directoryPaths">
<list />
</option>
</component>
</project>

View File

@ -3,6 +3,7 @@
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/ndjson-react" /> <excludeFolder url="file://$MODULE_DIR$/vendor/clue/ndjson-react" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" /> <excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/evenement/evenement" /> <excludeFolder url="file://$MODULE_DIR$/vendor/evenement/evenement" />
@ -36,6 +37,30 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stopwatch" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stopwatch" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" /> <excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/lines-of-code" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/staabm/side-effects-detector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />

View File

@ -71,6 +71,30 @@
<path value="$PROJECT_DIR$/vendor/friendsofphp/php-cs-fixer" /> <path value="$PROJECT_DIR$/vendor/friendsofphp/php-cs-fixer" />
<path value="$PROJECT_DIR$/vendor/react/stream" /> <path value="$PROJECT_DIR$/vendor/react/stream" />
<path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" /> <path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/staabm/side-effects-detector" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
</include_path> </include_path>
</component> </component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.3" /> <component name="PhpProjectSharedConfiguration" php_language_level="8.3" />
@ -82,6 +106,11 @@
<component name="PhpStanOptionsConfiguration"> <component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" /> <option name="transferred" value="true" />
</component> </component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
</phpunit_settings>
</component>
<component name="Psalm"> <component name="Psalm">
<Psalm_settings> <Psalm_settings>
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="6adcaaf8-6d8b-41cc-8523-d95f7e166ea1" timeout="60000" /> <psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="6adcaaf8-6d8b-41cc-8523-d95f7e166ea1" timeout="60000" />

10
.idea/phpspec.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPSpec">
<suites>
<PhpSpecSuiteConfiguration>
<option name="myPath" value="$PROJECT_DIR$" />
</PhpSpecSuiteConfiguration>
</suites>
</component>
</project>

View File

@ -9,4 +9,9 @@
declare(strict_types = 1); declare(strict_types = 1);
dump('Decorator'); use Pattern\Structural\Decorator\{BasicInspection, OilChange, TireRotation};
$service = new TireRotation(new OilChange(new BasicInspection()));
echo $service->getDescription() . ': $';
echo $service->getCost();

View File

@ -5,6 +5,11 @@
"": "src/" "": "src/"
} }
}, },
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"authors": [ "authors": [
{ {
"name": "Yevhen Odynets", "name": "Yevhen Odynets",
@ -16,7 +21,8 @@
"ext-simplexml": "*" "ext-simplexml": "*"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^3.76" "friendsofphp/php-cs-fixer": "^3.76",
"phpunit/phpunit": "^12.2"
}, },
"scripts": { "scripts": {
"cs": "php-cs-fixer --config=./.php-cs-fixer.php check -v", "cs": "php-cs-fixer --config=./.php-cs-fixer.php check -v",

1549
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 13:55
*/
declare(strict_types = 1);
namespace Pattern\Behavioral\Observer;
use SplObjectStorage;
use SplObserver;
use SplSubject;
class User implements SplSubject
{
/**
* @var string
*/
private string $name;
/**
* @var SplObjectStorage
*/
private SplObjectStorage $observers;
public function __construct()
{
$this->observers = new SplObjectStorage();
}
/**
* Attach an observer
*/
public function attach(SplObserver $observer): void
{
$this->observers->attach($observer);
}
/**
* Detach an observer
*/
public function detach(SplObserver $observer): void
{
$this->observers->detach($observer);
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
*
* @return void
*/
public function setName(string $name): void
{
$this->name = $name;
$this->notify();
}
/**
* Notify an observer
*/
public function notify(): void
{
/** @var SplObserver $observer */
foreach ($this->observers as $observer) {
var_dump($observer);
$observer->update($this);
}
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 14:06
*/
declare(strict_types = 1);
namespace Pattern\Behavioral\Observer;
use SplObserver;
use SplSubject;
class UserObserver implements SplObserver
{
private array $namesLog = [];
public function update(SplSubject $subject): void
{
/** @var User $subject */
$this->namesLog[] = $subject->getName();
}
public function getNamesLog(): array
{
return $this->namesLog;
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 11:38
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
class BasicInspection implements CarServiceInterface
{
public function getCost(): float
{
return 25.14;
}
public function getDescription(): string
{
return 'Basic inspection';
}
}

View File

@ -1,22 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 10:42
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
class BorderDecorator implements ShapeInterface
{
public function __construct(private ShapeInterface $shape) {}
public function draw(): void
{
echo '<div style="width: 8rem; height: 8rem; border: .5rem solid orange"></div>';
}
}

View File

@ -0,0 +1,19 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 11:53
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
interface CarServiceInterface
{
public function getCost(): float;
public function getDescription(): string;
}

View File

@ -1,20 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 10:41
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
class Circle implements ShapeInterface
{
public function draw(): void
{
// TODO: Implement draw() method.
}
}

View File

@ -1,22 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 10:43
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
class EmailDecorator implements ShapeInterface
{
public function __construct(private ShapeInterface $shape) {}
public function draw(): void
{
// TODO: Implement draw() method.
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 12:08
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
class OilChange implements CarServiceInterface
{
public function __construct(protected CarServiceInterface $carService) {}
public function getCost(): float
{
return 29.89 + $this->carService->getCost();
}
public function getDescription(): string
{
return $this->carService->getDescription() . ', and oil change';
}
}

View File

@ -1,17 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 10:38
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
interface ShapeInterface
{
public function draw(): void;
}

View File

@ -1,22 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 10:47
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
class SmileDecorator implements ShapeInterface
{
public function __construct(private ShapeInterface $shape) {}
public function draw(): void
{
// TODO: Implement draw() method.
}
}

View File

@ -1,20 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 10:41
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
class Square implements ShapeInterface
{
public function draw(): void
{
// TODO: Implement draw() method.
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 12:41
*/
declare(strict_types = 1);
namespace Pattern\Structural\Decorator;
class TireRotation implements CarServiceInterface
{
public function __construct(protected CarServiceInterface $carService) {}
public function getCost(): float
{
return 25.14 + $this->carService->getCost();
}
public function getDescription(): string
{
return $this->carService->getDescription() . ', and tire rotation';
}
}

43
tests/ObserverTest.php Normal file
View File

@ -0,0 +1,43 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 13:31
*/
declare(strict_types = 1);
namespace App\Tests;
use Pattern\Behavioral\Observer\User;
use Pattern\Behavioral\Observer\UserObserver;
use PHPUnit\Framework\TestCase;
class ObserverTest extends TestCase
{
public function testObserver(): void
{
$user = new User();
/** These names will be skipped and not be added to the log of names */
$user->setName("Just a name");
$user->setName("Some new name");
$userObserver = new UserObserver();
$user->attach($userObserver);
/** After creating a new instance of UserObserver::class
* and attached it to User::class instance,
* these names will be added to the log of names */
$user->setName("Another new name");
$user->setName("Once again new name");
$user->setName("Possibly other new name");
$this->assertEquals(
["Another new name", "Once again new name", "Possibly other new name"],
$userObserver->getNamesLog()
);
}
}