Addded Prototype Desin Pattern

This commit is contained in:
Yevhen Odynets 2025-07-04 09:05:39 +03:00
parent 979ae42cde
commit 1cf6e3d7d2
5 changed files with 310 additions and 3 deletions

40
code/prototype.php Normal file
View File

@ -0,0 +1,40 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 08:13
*/
declare(strict_types = 1);
use Pattern\Creational\Prototype\Author;
use Pattern\Creational\Prototype\Page;
function client(): void
{
$author = new Author("Джордж Орвелл");
$page = new Page(
"Колгосп тварин",
"Притча, сповнена гіркої іронії і сарказму.
Трагікомічна історія спільноти тварин, що зважилися позбутися пригноблення людьми,
і потрапила під бузувірську владу свиней.",
$author
);
// ...
$page->addComment("Nice book!");
// ...
$draft = clone $page;
echo "Dump of the clone. Note that the author is now referencing two objects.\n\n";
/** @noinspection ForgottenDebugOutputInspection */
dump($draft);
}
client();

View File

@ -1,4 +1,5 @@
<?php
$time_start = microtime(true);
require '../vendor/autoload.php';
require '../src/helpers.php';
?><!doctype html>
@ -19,7 +20,7 @@ require '../src/helpers.php';
height: 100dvh;
margin: 0;
padding: 0;
background: #ddd;
background: #e0e5e5;
color: #1c1b19;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
@ -40,22 +41,36 @@ require '../src/helpers.php';
transform: translate(-50%, -50%);
max-height: 92dvh;
overflow-y: auto;
background-color: attr(data-color type(<color>), #eef0f0);
}
pre {
pre, code {
font-family: Consolas, monospace;
font-size: 1.125rem;
}
pre {
align-self: center;
}
img.diagram {
max-width: 100%;
}
.et {
position: absolute;
top: .5rem;
left: .5rem;
font-size: small;
font-weight: 500;
color: #333;
}
</style>
</head>
<body>
<main>
<main data-color="#eef0f0">
<?php require '../src/router.php' ?>
</main>
<div class="et">ET: <?= (microtime(true) - $time_start) ?> secs</div>
<script>hljs.highlightAll()</script>
</body>
</html>

View File

@ -0,0 +1,27 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 07:54
*/
declare(strict_types = 1);
namespace Pattern\Creational\Prototype;
class Author
{
/**
* @var Page[]
*/
private array $pages = [];
public function __construct(private string $name) {}
public function addToPage(Page $page): void
{
$this->pages[] = $page;
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* @package: patterns
* @author: Yevhen Odynets
* @date: 2025-07-04
* @time: 07:53
*/
declare(strict_types = 1);
namespace Pattern\Creational\Prototype;
use DateTime;
/**
* Prototype.
*/
class Page
{
/** @noinspection PhpPropertyOnlyWrittenInspection */
private array $comments = [];
/** @noinspection PhpPropertyOnlyWrittenInspection */
private DateTime $date;
public function __construct(
private string $title,
private string $body,
private readonly Author $author,
) {
$this->author->addToPage($this);
$this->date = new DateTime();
}
public function addComment(string $comment): void
{
$this->comments[] = $comment;
}
/**
* You can control what data you want to carry over to the cloned object.
*
* For instance, when a page is cloned:
* - It gets a new "Copy of ..." title.
* - The author of the page remains the same. Therefore we leave the
* reference to the existing object while adding the cloned page to the list
* of the author's pages.
* - We don't carry over the comments from the old page.
* - We also attach a new date object to the page.
*/
public function __clone(): void
{
$this->title = "КОПІЯ: " . $this->title;
$this->body = "КОПІЯ: " . $this->body;
$this->author->addToPage($this);
$this->comments = [];
$this->date = new DateTime();
}
}

View File

@ -0,0 +1,165 @@
<?php
namespace RefactoringGuru\Builder\RealWorld;
/**
* The Builder interface declares a set of methods to assemble an SQL query.
*
* All of the construction steps are returning the current builder object to
* allow chaining: $builder->select(...)->where(...)
*/
interface SQLQueryBuilder
{
public function select(string $table, array $fields): SQLQueryBuilder;
public function where(string $field, string $value, string $operator = '='): SQLQueryBuilder;
public function limit(int $start, int $offset): SQLQueryBuilder;
// +100 other SQL syntax methods...
public function getSQL(): string;
}
/**
* Each Concrete Builder corresponds to a specific SQL dialect and may implement
* the builder steps a little bit differently from the others.
*
* This Concrete Builder can build SQL queries compatible with MySQL.
*/
class MysqlQueryBuilder implements SQLQueryBuilder
{
protected $query;
protected function reset(): void
{
$this->query = new \stdClass();
}
/**
* Build a base SELECT query.
*/
public function select(string $table, array $fields): SQLQueryBuilder
{
$this->reset();
$this->query->base = "SELECT " . implode(", ", $fields) . " FROM " . $table;
$this->query->type = 'select';
return $this;
}
/**
* Add a WHERE condition.
*/
public function where(string $field, string $value, string $operator = '='): SQLQueryBuilder
{
if (!in_array($this->query->type, ['select', 'update', 'delete'])) {
throw new \Exception("WHERE can only be added to SELECT, UPDATE OR DELETE");
}
$this->query->where[] = "$field $operator '$value'";
return $this;
}
/**
* Add a LIMIT constraint.
*/
public function limit(int $start, int $offset): SQLQueryBuilder
{
if (!in_array($this->query->type, ['select'])) {
throw new \Exception("LIMIT can only be added to SELECT");
}
$this->query->limit = " LIMIT " . $start . ", " . $offset;
return $this;
}
/**
* Get the final query string.
*/
public function getSQL(): string
{
$query = $this->query;
$sql = $query->base;
if (!empty($query->where)) {
$sql .= " WHERE " . implode(' AND ', $query->where);
}
if (isset($query->limit)) {
$sql .= $query->limit;
}
$sql .= ";";
return $sql;
}
}
/**
* This Concrete Builder is compatible with PostgreSQL. While Postgres is very
* similar to Mysql, it still has several differences. To reuse the common code,
* we extend it from the MySQL builder, while overriding some of the building
* steps.
*/
class PostgresQueryBuilder extends MysqlQueryBuilder
{
/**
* Among other things, PostgreSQL has slightly different LIMIT syntax.
*/
public function limit(int $start, int $offset): SQLQueryBuilder
{
parent::limit($start, $offset);
$this->query->limit = " LIMIT " . $start . " OFFSET " . $offset;
return $this;
}
// + tons of other overrides...
}
/**
* Note that the client code uses the builder object directly. A designated
* Director class is not necessary in this case, because the client code needs
* different queries almost every time, so the sequence of the construction
* steps cannot be easily reused.
*
* Since all our query builders create products of the same type (which is a
* string), we can interact with all builders using their common interface.
* Later, if we implement a new Builder class, we will be able to pass its
* instance to the existing client code without breaking it thanks to the
* SQLQueryBuilder interface.
*/
function clientCode(SQLQueryBuilder $queryBuilder)
{
// ...
$query = $queryBuilder
->select("users", ["name", "email", "password"])
->where("age", 18, ">")
->where("age", 30, "<")
->limit(10, 20)
->getSQL();
echo $query;
// ...
}
/**
* The application selects the proper query builder type depending on a current
* configuration or the environment settings.
*/
// if ($_ENV['database_type'] == 'postgres') {
// $builder = new PostgresQueryBuilder(); } else {
// $builder = new MysqlQueryBuilder(); }
//
// clientCode($builder);
echo "Testing MySQL query builder:\n";
clientCode(new MysqlQueryBuilder());
echo "\n\n";
echo "Testing PostgresSQL query builder:\n";
clientCode(new PostgresQueryBuilder());