made some refactoring

This commit is contained in:
Yevhen Odynets 2025-07-09 05:28:39 +03:00
parent f76eb08fe0
commit 38676bc9fd
69 changed files with 3075 additions and 355 deletions

9
.env Normal file
View File

@ -0,0 +1,9 @@
APP_NAME=Project
APP_ENV=local
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laraspace
DB_USERNAME=laraspace
DB_PASSWORD="👨‍🍳🥚🧂🥛=🍽️💪"

View File

@ -2,8 +2,9 @@
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="App\Tests\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Tests\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/ndjson-react" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/evenement/evenement" />
@ -37,16 +38,21 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/stopwatch" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/string" />
<excludeFolder url="file://$MODULE_DIR$/vendor/carbonphp/carbon-doctrine-types" />
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nesbot/carbon" />
<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/phpfui/orm" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpfui/translation" />
<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/psr/clock" />
<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" />
@ -60,6 +66,10 @@
<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/symfony/clock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php83" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
</content>
<orderEntry type="inheritedJdk" />

View File

@ -95,6 +95,15 @@
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/phpfui/orm" />
<path value="$PROJECT_DIR$/vendor/phpfui/translation" />
<path value="$PROJECT_DIR$/vendor/psr/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/clock" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php83" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/carbonphp/carbon-doctrine-types" />
<path value="$PROJECT_DIR$/vendor/nesbot/carbon" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.3" />

View File

@ -4,11 +4,11 @@ declare(strict_types = 1);
use PhpCsFixer\{Config, Finder, Runner\Parallel\ParallelConfigFactory};
$finder = (new Finder())->in([
$finder = (new Finder)->in([
__DIR__ . '/src',
])->exclude(['vendor'])->name('*.php')->ignoreDotFiles(true);
return (new Config())->setRules([
return (new Config)->setRules([
'@PSR12' => true,
'declare_equal_normalize' => [
'space' => 'single',
@ -21,6 +21,9 @@ return (new Config())->setRules([
'=>' => null,
],
],
'new_with_parentheses' => [
'named_class' => false,
]
])->setParallelConfig(ParallelConfigFactory::detect())->setUsingCache(true)->setRiskyAllowed(true)->setFinder(
$finder
)->setIndent(' ')->setLineEnding("\n");

View File

@ -1,42 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:20
*/
declare(strict_types = 1);
use Pattern\Creational\AbstractFactory\{NovapostDeliveryFactory, UkrpostDeliveryFactory};
use Pattern\Creational\AbstractFactory\{JustinDeliveryFactory, MeestDeliveryFactory};
/**
* @param array $factories
*
* @return void
*/
function delivery(array $factories): void
{
foreach ($factories as $factory) {
// getting the delivery service
$deliveryService = $factory->createDeliveryService();
// getting the parcel
$package = $factory->createPackage();
// checking the parcel
$package->getConsist();
// sending the parcel
$deliveryService->sendPackage($package);
echo '<hr/>' . PHP_EOL;
}
}
$factories = [
new MeestDeliveryFactory(),
new NovapostDeliveryFactory(),
new JustinDeliveryFactory(),
new UkrpostDeliveryFactory(),
];
delivery($factories);

View File

@ -7,7 +7,7 @@
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
"Tests\\": "tests/"
}
},
"authors": [
@ -18,9 +18,13 @@
],
"require": {
"php": "8.3.*",
"ext-simplexml": "*"
"ext-pdo": "*",
"ext-simplexml": "*",
"phpfui/orm": "^2.0",
"nesbot/carbon": "^3.10"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
"friendsofphp/php-cs-fixer": "^3.76",
"phpunit/phpunit": "^12.2"
},

2037
composer.lock generated

File diff suppressed because it is too large Load Diff

11
config/database.php Normal file
View File

@ -0,0 +1,11 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 18:03
*/
declare(strict_types = 1);

View File

@ -2,6 +2,7 @@
$time_start = microtime(true);
require '../vendor/autoload.php';
require '../src/helpers.php';
(new \Controller\DotEnvEnvironment())->load(__DIR__ . '/../');
?><!doctype html>
<html lang="uk">
<head>

View File

@ -0,0 +1,39 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:20
*/
declare(strict_types = 1);
use Pattern\Creational\AbstractFactory\{ParcelSender, ProvidersEnum as PostProvider};
/**
* @param array $parcels
*
* @return void
*/
function doDeliver(array $parcels): void
{
foreach ($parcels as $parcel) {
['provider' => $provider, 'destination_address' => $address] = $parcel;
$result = (new ParcelSender($provider, $address))->send();
echo $result ? "✔️ Sent successfully\n" : "❌ Sending failed\n";
}
}
$parcels = [
['provider' => PostProvider::NovaPost, 'destination_address' => "02020,\r\nм. Щастя, рХТЗ"],
[
'provider' => PostProvider::UkrPost,
'destination_address' => "Голобородько Семен Юхимович,\r\nn02020, Львівська обл.,\r\nм. Городок, вул. Головна, буд. 1, кв. 50",
],
['provider' => PostProvider::Meest, 'destination_address' => "Адреса 3"],
['provider' => PostProvider::Justin, 'destination_address' => "Відділення 777"],
];
doDeliver($parcels);

View File

@ -0,0 +1,34 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 15:35
*/
declare(strict_types = 1);
use Pattern\Behavioral\Observer\User;
use Pattern\Behavioral\Observer\UserObserver;
$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);
$user->setName("Another new name");
$user->setName("Once again new name");
$user->setName("Possibly other new name");
/** @noinspection ForgottenDebugOutputInspection */
dump(
getenv('APP_ENV'),
["Another new name", "Once again new name", "Possibly other new name"],
$userObserver->getNamesLog(),
["Another new name", "Once again new name", "Possibly other new name"] === $userObserver->getNamesLog()
);

View File

@ -0,0 +1,14 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 08:36
*/
declare(strict_types = 1);
use Pattern\SOLID\LiskovSubstitution\Bird;
$bird = new Bird;

View File

@ -0,0 +1,23 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 05:52
*/
declare(strict_types = 1);
// Separate extensible behaviour behind an interface, and flip the dependencies
use Pattern\SOLID\OpenClosed\Figures\AreaCalculator;
use Pattern\SOLID\OpenClosed\Figures\Circle;
use Pattern\SOLID\OpenClosed\Figures\Square;
$circle = new Circle(3);
$square = new Square(2, 5);
$areaCalculator = new AreaCalculator;
/** @noinspection ForgottenDebugOutputInspection */
dump('Circle(3)', $areaCalculator->calculate([$circle, $square]));

View File

@ -0,0 +1,22 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 00:04
*/
declare(strict_types = 1);
use Carbon\Carbon;
use Pattern\SOLID\SingleResponsibility\HtmlOutput;
use Pattern\SOLID\SingleResponsibility\SalesReporter;
use Pattern\SOLID\SingleResponsibility\SalesRepository;
$now = Carbon::now();
$report = new SalesReporter(new SalesRepository);
$output = $report->between($now->clone()->subDays(10)->format('Y-m-d'), $now->format('Y-m-d'), new HtmlOutput);
echo($output);

View File

@ -0,0 +1,32 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 02:48
*/
declare(strict_types = 1);
$c = 0;
$ite = new RecursiveDirectoryIterator('k:\tmp\faker_dir');
//$ite = new RecursiveDirectoryIterator('c:\tmp\Laracasts\Languages');
//$ite = new RecursiveDirectoryIterator('w:\tmp\Laracasts\Languages\PHP');
foreach (new RecursiveIteratorIterator($ite) as $filename => $cur) {
$pathInfo = pathinfo($filename);
if ($pathInfo['extension'] !== 'mp4') {
continue;
}
$lnkFile = $filename . '.LNK';
if (! file_exists($lnkFile)) {
symlink($filename, $lnkFile);
}
if (file_exists($lnkFile)) {
echo "<p>deleted $filename</p>";
//unlink($filename);
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 16:23
*/
declare(strict_types = 1);
namespace Controller;
class DotEnvEnvironment
{
public function load($path): void
{
$lines = file($path . '/.env');
foreach ($lines as $line) {
$split_line = explode('=', $line, 2);
if (count($split_line) !== 2) {
continue;
}
[$key, $value] = $split_line;
$key = trim($key);
$value = trim($value);
putenv(sprintf('%s=%s', $key, $value));
$_ENV[$key] = $value;
$_SERVER[$key] = $value;
}
}
}

View File

@ -0,0 +1,148 @@
<?php
declare(strict_types = 1);
namespace Database\Adapter;
use PDOException;
use RuntimeException;
use stdClass;
final class PDO
{
private ?\PDO $connection = null;
private $statement = null;
public function __construct(
string $hostname,
string $username,
string $password,
string $database,
string $port = '3306'
) {
try {
$this->connection = new \PDO(
"mysql:host=" . $hostname . ";port=" . $port . ";dbname=" . $database,
$username,
$password,
[\PDO::ATTR_PERSISTENT => true]
);
} catch (PDOException $e) {
throw new RuntimeException('Failed to connect to database. Reason: \'' . $e->getMessage() . '\'');
}
$this->connection->exec("SET NAMES 'utf8'");
$this->connection->exec("SET CHARACTER SET utf8");
$this->connection->exec("SET CHARACTER_SET_CONNECTION=utf8");
$this->connection->exec("SET SQL_MODE = ''");
}
public function bindParam($parameter, $variable, $data_type = \PDO::PARAM_STR, $length = 0): void
{
if ($length) {
$this->statement->bindParam($parameter, $variable, $data_type, $length);
} else {
$this->statement->bindParam($parameter, $variable, $data_type);
}
}
public function query($sql, $params = []): stdClass
{
$this->statement = $this->connection->prepare($sql);
$result = false;
try {
if ($this->statement && $this->statement->execute($params)) {
$data = [];
while ($row = $this->statement->fetch(\PDO::FETCH_ASSOC)) {
$data[] = $row;
}
$result = new stdClass();
$result->row = ($data[0] ?? []);
$result->rows = $data;
$result->num_rows = $this->statement->rowCount();
}
} catch (PDOException $e) {
throw new RuntimeException(
'Error: ' . $e->getMessage() . ' Error Code : ' . $e->getCode() . ' <br />' . $sql
);
}
if ($result) {
return $result;
}
$result = new stdClass();
$result->row = [];
$result->rows = [];
$result->num_rows = 0;
return $result;
}
public function prepare($sql): void
{
$this->statement = $this->connection->prepare($sql);
}
public function execute(): void
{
try {
if ($this->statement && $this->statement->execute()) {
$data = [];
while ($row = $this->statement->fetch(\PDO::FETCH_ASSOC)) {
$data[] = $row;
}
/** @noinspection PhpObjectFieldsAreOnlyWrittenInspection */
$result = new stdClass();
$result->row = $data[0] ?? [];
$result->rows = $data;
$result->num_rows = $this->statement->rowCount();
}
} catch (PDOException $e) {
throw new RuntimeException('Error: ' . $e->getMessage() . ' Error Code : ' . $e->getCode());
}
}
public function escape($value): array|string
{
return str_replace(
["\\", "\0", "\n", "\r", "\x1a", "'", '"'],
["\\\\", "\\0", "\\n", "\\r", "\Z", "\'", '\"'],
$value
);
}
public function countAffected(): int
{
if ($this->statement) {
return $this->statement->rowCount();
} else {
return 0;
}
}
public function getLastId(): false|string
{
return $this->connection->lastInsertId();
}
public function isConnected(): bool
{
if ($this->connection) {
return true;
} else {
return false;
}
}
public function __destruct()
{
$this->connection = null;
}
}

BIN
src/Database/Adapter/db.zip Normal file

Binary file not shown.

105
src/Database/DB.php Normal file
View File

@ -0,0 +1,105 @@
<?php
/**
* @package OpenCart
* @author Daniel Kerr
* @copyright Copyright (c) 2005 - 2017, OpenCart, Ltd. (https://www.opencart.com/)
* @license https://opensource.org/licenses/GPL-3.0
* @link https://www.opencart.com
*/
declare(strict_types = 1);
namespace Database;
use RuntimeException;
/**
* DB class
*/
class DB
{
private mixed $adaptor;
/**
* Constructor
*
* @param string $adaptor
* @param string $hostname
* @param string $username
* @param string $password
* @param string $database
* @param int|null $port
*
*/
public function __construct(
string $adaptor,
string $hostname,
string $username,
string $password,
string $database,
int $port = null
) {
$class = 'DB\\' . $adaptor;
if (class_exists($class)) {
$this->adaptor = new $class($hostname, $username, $password, $database, $port);
} else {
throw new RuntimeException('Error: Could not load database adaptor ' . $adaptor . '!');
}
}
/**
*
*
* @param string $sql
*
* @return array
*/
public function query(string $sql): array
{
return $this->adaptor->query($sql);
}
/**
*
*
* @param string $value
*
* @return string
*/
public function escape(string $value): string
{
return $this->adaptor->escape($value);
}
/**
*
*
* @return int
*/
public function countAffected(): int
{
return $this->adaptor->countAffected();
}
/**
*
*
* @return int
*/
public function getLastId(): int
{
return $this->adaptor->getLastId();
}
/**
*
*
* @return bool
*/
public function connected(): bool
{
return $this->adaptor->connected();
}
}

View File

@ -0,0 +1,15 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 02:47
*/
declare(strict_types = 1);
namespace FileSystem;
class ItterateFiles {
}

View File

@ -75,7 +75,6 @@ class User implements SplSubject
{
/** @var SplObserver $observer */
foreach ($this->observers as $observer) {
var_dump($observer);
$observer->update($this);
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:07
@ -19,7 +19,7 @@ interface AbstractFactoryInterface
public function createDeliveryService(): DeliveryServiceInterface;
/**
* @return PackageInterface
* @return ParcelInterface
*/
public function createPackage(): PackageInterface;
public function createParcel(): ParcelInterface;
}

View File

@ -0,0 +1,20 @@
<?php
/**
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-08
* @time: 10:59
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
class DeliveryServiceFactory
{
public static function create(ProvidersEnum $provider): AbstractFactoryInterface
{
return $provider->getService();
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:02
@ -13,5 +13,5 @@ namespace Pattern\Creational\AbstractFactory;
interface DeliveryServiceInterface
{
public function sendPackage(PackageInterface $package): void;
public function sendParcel(ParcelInterface $parcel): bool;
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:15
@ -18,8 +18,8 @@ class JustinDeliveryFactory implements AbstractFactoryInterface
return new JustinDeliveryService();
}
public function createPackage(): PackageInterface
public function createParcel(): ParcelInterface
{
return new JustinPackage();
return new JustinParcel();
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 19:57
@ -13,9 +13,11 @@ namespace Pattern\Creational\AbstractFactory;
class JustinDeliveryService implements DeliveryServiceInterface
{
public function sendPackage(PackageInterface $package): void
public function sendParcel(ParcelInterface $parcel): bool
{
/** @noinspection ForgottenDebugOutputInspection */
dump("Sending package via Justin...");
dump("Sending parcel via Justin...");
return true;
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:00
@ -11,11 +11,18 @@ declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
class JustinPackage implements PackageInterface
class JustinParcel implements ParcelInterface
{
public function getConsist(): void
{
/** @noinspection ForgottenDebugOutputInspection */
dump('Checking package from Justin...');
dump('Checking parcel from Justin...');
}
public function setDestination(string $destination): static
{
echo "<pre>$destination</pre></<br>>";
return $this;
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:15
@ -18,8 +18,8 @@ class MeestDeliveryFactory implements AbstractFactoryInterface
return new MeestDeliveryService();
}
public function createPackage(): PackageInterface
public function createParcel(): ParcelInterface
{
return new MeestPackage();
return new MeestParcel();
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 19:57
@ -14,9 +14,11 @@ namespace Pattern\Creational\AbstractFactory;
class MeestDeliveryService implements DeliveryServiceInterface
{
public function sendPackage(PackageInterface $package): void
public function sendParcel(ParcelInterface $parcel): bool
{
/** @noinspection ForgottenDebugOutputInspection */
dump("Sending package via Meest...");
dump("Sending parcel via Meest...");
return true;
}
}

View File

@ -1,21 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:00
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
class MeestPackage implements PackageInterface
{
public function getConsist(): void
{
/** @noinspection ForgottenDebugOutputInspection */
dump('Checking package from Meest...');
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:00
@ -11,11 +11,18 @@ declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
class UkrpostPackage implements PackageInterface
class MeestParcel implements ParcelInterface
{
public function getConsist(): void
{
/** @noinspection ForgottenDebugOutputInspection */
dump('Checking package from Ukrpost...');
dump('Checking parcel from Meest...');
}
public function setDestination(string $destination): static
{
echo "<pre>$destination</pre></<br>";
return $this;
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:15
@ -15,11 +15,11 @@ class NovapostDeliveryFactory implements AbstractFactoryInterface
{
public function createDeliveryService(): DeliveryServiceInterface
{
return new NovapostDeliveryService();
return new NovapostDeliveryService;
}
public function createPackage(): PackageInterface
public function createParcel(): ParcelInterface
{
return new NovapostPackage();
return new NovapostParcel;
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 19:57
@ -13,9 +13,11 @@ namespace Pattern\Creational\AbstractFactory;
class NovapostDeliveryService implements DeliveryServiceInterface
{
public function sendPackage(PackageInterface $package): void
public function sendParcel(ParcelInterface $parcel): true
{
/** @noinspection ForgottenDebugOutputInspection */
dump("Sending package via Novapost...");
dump("Sending parcel via Novapost...");
return true;
}
}

View File

@ -1,21 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:00
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
class NovapostPackage implements PackageInterface
{
public function getConsist(): void
{
/** @noinspection ForgottenDebugOutputInspection */
dump('Checking package from Novapost...');
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:00
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
class NovapostParcel implements ParcelInterface
{
public function getConsist(): void
{
/** @noinspection ForgottenDebugOutputInspection */
dump('Checking parcel from Novapost...');
}
/**
* @param string $destination
*
* @return $this
*/
public function setDestination(string $destination): static
{
echo "<pre>$destination</pre></<br>";
return $this;
}
}

View File

@ -1,17 +0,0 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 19:59
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
interface PackageInterface
{
public function getConsist(): void;
}

View File

@ -0,0 +1,27 @@
<?php
/**
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 19:59
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
interface ParcelInterface
{
/**
* @return void
*/
public function getConsist(): void;
/**
* @param string $destination
*
* @return $this
*/
public function setDestination(string $destination): static;
}

View File

@ -0,0 +1,41 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-08
* @time: 11:14
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
readonly class ParcelSender
{
private AbstractFactoryInterface $service;
public function __construct(
private ProvidersEnum $provider,
private string $destinationAddress,
) {
$this->service = DeliveryServiceFactory::create($this->provider);
}
public function send(): bool
{
// getting the delivery service
$deliveryService = $this->service->createDeliveryService();
// getting the parcel
$parcel = $this->service->createParcel();
// setting up address & checking the parcel
$parcel->setDestination($this->destinationAddress)->getConsist();
// sending the parcel
$deliveryService->sendParcel($parcel);
return true;
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-08
* @time: 10:20
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
enum ProvidersEnum
{
case NovaPost;
case UkrPost;
case Meest;
case Justin;
public function getService(): AbstractFactoryInterface
{
return match ($this) {
self::NovaPost => new NovapostDeliveryFactory,
self::UkrPost => new UkrpostDeliveryFactory,
self::Meest => new MeestDeliveryFactory,
self::Justin => new JustinDeliveryFactory,
};
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:15
@ -18,8 +18,8 @@ class UkrpostDeliveryFactory implements AbstractFactoryInterface
return new UkrpostDeliveryService();
}
public function createPackage(): PackageInterface
public function createParcel(): ParcelInterface
{
return new UkrpostPackage();
return new UkrpostParcel();
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* @package: patterns
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 19:57
@ -13,9 +13,11 @@ namespace Pattern\Creational\AbstractFactory;
class UkrpostDeliveryService implements DeliveryServiceInterface
{
public function sendPackage(PackageInterface $package): void
public function sendParcel(ParcelInterface $parcel): bool
{
/** @noinspection ForgottenDebugOutputInspection */
dump("Sending package via Ukrpost...");
dump("Sending parcel via Ukrpost...");
return true;
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* @parcel: patterns
* @author: Yevhen Odynets
* @date: 2025-07-03
* @time: 20:00
*/
declare(strict_types = 1);
namespace Pattern\Creational\AbstractFactory;
class UkrpostParcel implements ParcelInterface
{
public function getConsist(): void
{
/** @noinspection ForgottenDebugOutputInspection */
dump('Checking parcel from Ukrpost...');
}
/**
* @param string $destination
*
* @return $this
*/
public function setDestination(string $destination): static
{
echo "<pre>$destination</pre></<br>";
return $this;
}
}

View File

@ -0,0 +1,22 @@
🧠 **Переваги цієї архітектури**
✅ Легко додати нову службу доставки
Не потрібно змінювати існуючий код (принцип Open/Closed)
✅ Клієнтський код не залежить від реалізацій
✅ Можна легко замінювати служби в тестах (Mock)
🧩 **Структура оновленого коду:**
DeliveryServiceInterface — інтерфейс
{Justin|Meest|NovaPost|UkrPost}DeliveryService — реалізація
DeliveryServiceFactory — фабрика
ParcelSender — клієнтська логіка
index.php — приклад використання
Надай приклад реалізації патерну Singleton на мові програмування PHP

View File

@ -53,7 +53,7 @@ class Singleton
dump(trim($message . ': ' . $cls, ' :'));
if (!isset(self::$instances[$cls])) {
self::$instances[$cls] = new static();
self::$instances[$cls] = new static;
}
return self::$instances[$cls];

View File

@ -0,0 +1,20 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 08:35
*/
declare(strict_types = 1);
namespace Pattern\SOLID\LiskovSubstitution;
class Bird
{
public function fly(): int
{
return 10;
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 08:30
*/
declare(strict_types = 1);
namespace Pattern\SOLID\LiskovSubstitution;
/**
* Let q(x) be a property provable about objects x of type T.
* Then q(y) should be provable for objects y of type S where S is a subtype of T.
*
* Derived classes must be substitutable for their base classes.
*/
readonly class BirdRun
{
public function __construct(private Bird $bird) {}
public function run(): void
{
$flySpeed = $this->bird->fly();
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 09:35
*/
declare(strict_types = 1);
namespace Pattern\SOLID\LiskovSubstitution;
class Duck extends Bird
{
public function fly(): int
{
return 8;
}
public function swim(): int
{
return 2;
}
public function run(): int
{
return 3;
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 09:39
*/
declare(strict_types = 1);
namespace Pattern\SOLID\LiskovSubstitution;
use Pattern\SOLID\LiskovSubstitution\Bird;
class Ostrich extends Bird
{
public function fly(): int
{
return 0; //I am not able to fly
}
public function swim(): int
{
return 12;
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 05:28
*/
declare(strict_types = 1);
namespace Pattern\SOLID\OpenClosed\Figures;
class AreaCalculator
{
/**
* @param array $shapes
*
* @return int|float
*/
public function calculate(array $shapes, int $round_precision = 2): int|float
{
$area = [];
foreach ($shapes as $shape) {
$area[] = $shape->area();
}
$result = array_sum($area);
return is_int($result) ? $result : round($result, $round_precision, PHP_ROUND_HALF_UP);
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 05:32
*/
declare(strict_types = 1);
namespace Pattern\SOLID\OpenClosed\Figures;
class Circle implements ShapeInterface
{
public function __construct(public int|float $radius) {}
/**
* @return int|float
*/
public function area(): int|float
{
return M_PI * ($this->radius ** 2);
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 05:55
*/
declare(strict_types = 1);
namespace Pattern\SOLID\OpenClosed\Figures;
interface ShapeInterface
{
public function area(): int|float;
}

View File

@ -0,0 +1,25 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 05:27
*/
declare(strict_types = 1);
namespace Pattern\SOLID\OpenClosed\Figures;
class Square implements ShapeInterface
{
public function __construct(public int|float $width, public int|float $height) {}
/**
* @return int|float
*/
public function area(): int|float
{
return $this->width * $this->height;
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 06:20
*/
declare(strict_types = 1);
namespace Pattern\SOLID\OpenClosed\Payments;
class CashPaymentMethod implements PaymentMethodInterface
{
public function acceptPayment($receipt)
{
// TODO: Implement acceptPayment() method.
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 06:19
*/
declare(strict_types = 1);
namespace Pattern\SOLID\OpenClosed\Payments;
class Checkout
{
public function begin(Receipt $receipt, PaymentMethodInterface $payment): void
{
$payment->acceptPayment($receipt);
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 06:20
*/
declare(strict_types = 1);
namespace Pattern\SOLID\OpenClosed\Payments;
interface PaymentMethodInterface
{
public function acceptPayment($receipt);
}

View File

@ -0,0 +1,14 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 06:24
*/
declare(strict_types = 1);
namespace Pattern\SOLID\OpenClosed\Payments;
class Receipt {}

View File

@ -0,0 +1,27 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 03:51
*/
declare(strict_types = 1);
namespace Pattern\SOLID\SingleResponsibility;
?>
<style>
span {
color: green;
}
</style>
<?php
class HtmlOutput implements SalesReportOutputInterface
{
public function output($sales): string
{
return "<h2>Sales: <span>$" . number_format($sales, 2) . "</span>></h2>";
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 03:50
*/
declare(strict_types = 1);
namespace Pattern\SOLID\SingleResponsibility;
interface SalesReportOutputInterface
{
public function output($sales): string;
}

View File

@ -0,0 +1,26 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-06
* @time: 23:22
*/
declare(strict_types = 1);
namespace Pattern\SOLID\SingleResponsibility;
use Carbon\Carbon;
final readonly class SalesReporter
{
public function __construct(private SalesRepository $repository) {}
public function between(string|Carbon $startDate, string|Carbon $endDate, SalesReportOutputInterface $formatter): string
{
$sales = $this->repository->between($startDate, $endDate);
return $formatter->output($sales);
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-07
* @time: 03:45
*/
declare(strict_types = 1);
namespace Pattern\SOLID\SingleResponsibility;
class SalesRepository
{
/**
* Just emulate db query
*
* @param $startDate
* @param $endDate
*
* @return float
*/
public function between($startDate, $endDate): float
{
/** @noinspection ForgottenDebugOutputInspection */
dump($startDate, $endDate);
return 192_273_221 / 100;
}
}

View File

@ -44,3 +44,54 @@ function getFloatRange(): float
{
return (float)random_int(1000, 999999) / 100;
}
/**
* Flatten a multi-dimensional associative array with dots.
*
* @param iterable $array
* @param string $prepend
*
* @return array
* @throws RandomException
*/
//function array_dot(iterable $array, string $prepend = ''): array
//{
// $results = [];
//
// $flatten = static function ($data, $prefix) use (&$results, &$flatten): void {
// foreach ($data as $key => $value) {
// $newKey = $prefix.$key;
//
// if (is_array($value) && ! empty($value)) {
// $flatten($value, $newKey.'.');
// } else {
// $results[$newKey] = $value;
// }
// }
// };
//
// $flatten($array, $prepend);
//
// return $results;
//}
function abord(int $code): void
{
$response_message = $_SERVER['SERVER_PROTOCOL'];
switch ($code) {
case 404:
$response_message .= $code . ' Not Found';
break;
case 500:
$response_message .= $code . ' Internal Server Error';
break;
default:
$response_message .= ' 501 Not Implemented';
throw new RandomException($response_message, $code);
}
header($response_message);
http_response_code($code);
print($response_message);
}

View File

@ -9,8 +9,36 @@
declare(strict_types = 1);
preg_match('/^\/patterns\/([a-z0-9-]+)/', $_SERVER["REQUEST_URI"], $patterns);
$request_uri = $_SERVER["REQUEST_URI"];
const DIR_VIEW = '../resources/view';
if (isset($patterns[1]) && file_exists('../code/' . $patterns[1] . '.php')) {
require '../code/' . $patterns[1] . '.php';
$slugs = ['patterns', 'principles', 'tools'];
$regex_pattern = '/^\/(' . implode('|', $slugs) . ')\/([a-z0-9-]+)/';
if (preg_match($regex_pattern, $request_uri, $matches) && isset($matches[1])) {
[$uri, $controller, $view] = $matches;
$filepath = realpath(DIR_VIEW . $uri . '.php');
if (file_exists($filepath)) {
require $filepath;
}
}
/*if (preg_match($regex_pattern, $request_uri, $matches)) {
$filepath = DIR_VIEW . "$slug/" . $matches[1] . '.php';
if (isset($matches[1]) && file_exists($filepath)) {
require $filepath;
}
} else {
try {
abord(404);
} catch (RandomException $e) {
/** @noinspection ForgottenDebugOutputInspection */
// dump($e->getMessage());
///}
//}*/

View File

@ -9,7 +9,7 @@
declare(strict_types = 1);
namespace App\Tests;
namespace Tests;
use Pattern\Behavioral\Observer\User;
use Pattern\Behavioral\Observer\UserObserver;
@ -19,6 +19,7 @@ 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 */