Skip to content

Instantly share code, notes, and snippets.

@NigelGreenway
Last active October 16, 2023 21:00
Show Gist options
  • Save NigelGreenway/502c50a139a767a3afd2 to your computer and use it in GitHub Desktop.
Save NigelGreenway/502c50a139a767a3afd2 to your computer and use it in GitHub Desktop.
<?php
$start = microtime(true);
/*
CREATE TABLE `employees` (
`sId` varchar(40) NOT NULL DEFAULT '',
`sUsername` varchar(255) NOT NULL DEFAULT '',
`sFirstname` varchar(255) NOT NULL DEFAULT '',
`sLastname` varchar(255) NOT NULL DEFAULT '',
`sEmailAddress` varchar(255) NOT NULL DEFAULT '',
`sPassword` varchar(255) NOT NULL DEFAULT '',
`sSalt` varchar(255) NOT NULL DEFAULT '',
`bIsActive` smallint(1) NOT NULL DEFAULT '1',
`sGravatarHash` varchar(255) DEFAULT '',
PRIMARY KEY (`sId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `employees` (`sId`, `sUsername`, `sFirstname`, `sLastname`, `sEmailAddress`, `sPassword`, `sSalt`, `bIsActive`, `sGravatarHash`)
VALUES
('1dca2fa9-c7d7-48df-8f9f-a42923123fe5', 'nigel.greenway', 'Nigel', 'Greenway', 'nigel.greenway@email.co.uk', 'my_passord_hash', 'my_salt', 1, NULL);
*/
final class PDOAdaptor
{
private $connection;
public function __construct(
$host,
$database,
$username,
$password
) {
$this->connection = new \PDO(
sprintf('mysql:host=%s;dbname=%s', $host, $database),
$username,
$password
);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function fetch($query, $parameters)
{
$query = $this->connection->prepare($query);
$query->execute($parameters);
return $query->fetch();
}
public function fetchAll($query, array $params = [])
{
$query = $this->connection->prepare($query);
$query->execute($params);
return $query->fetchAll();
}
public function disableAutoCommit()
{
$this->connection->beginTransaction();
}
public function addTransaction($query, $parameters)
{
try {
$query = $this->connection->prepare($query);
$query->execute($parameters);
} catch (\Exception $e) {
throw new InvalidSqlException($e);
$this->connection->rollback();
}
}
public function commit()
{
$this->connection->commit();
}
}
final class InvalidSqlException extends \Exception
{
public function __construct(\Exception $exception)
{
return parent::__construct($exception->getMessage(), $exception->getCode());
}
}
final class QueryBuilder
{
private $mapper;
public function __construct(
EntityMapper $mapping
) {
$this->mapping = $mapping;
}
public function buildFromObject($object)
{
$reflector = new \ReflectionClass($object);
$mapper = $this->mapping->handle($object);
$mapper = new $mapper;
foreach($reflector->getProperties() as $p) {
$p->setAccessible(true);
if ($p->getName() == $mapper::$idMap[0]) {
$id = $p->getValue($object);
}
$keys[] = sprintf("%s = :%s", $mapper::$fields[$p->getName()], $p->getName());
$data[':'.$p->getName()] = $p->getValue($object);
}
if (
isset($object->state) === true
&& $object->state === Hydrator::EXISTING
) {
$query = sprintf(
"UPDATE %s SET %s WHERE %s = '%s';",
$mapper::$table,
implode(', ', $keys),
$mapper::$idMap[1],
$id
);
} else {
$query = sprintf(
"INSERT INTO %s SET %s;",
$mapper::$table,
implode(', ', $keys)
);
}
return [
'query' => $query,
'parameters' => $data,
'state' => isset($object->state) === true ? $object->state : 0,
];
}
}
final class ProfileViewGateway
{
private $connection;
function __construct(
PDOAdaptor $connection
) {
$this->connection = $connection;
}
public function fetch($employeeID)
{
$viewData = $this
->connection
->fetch("SELECT
e.sFirstname AS 'first_name'
, e.sLastname AS 'last_names'
, e.sEmailAddress AS 'email_address'
, er.sHumanReadableName AS 'role_title'
FROM
employees e
LEFT JOIN employee_role_links erl ON e.sId = erl.sEmployeeId
LEFT JOIN employee_roles er ON erl.sRoleId = er.sId
",
[
':employeeID' => $employeeID,
]
);
return new ProfileViewModel(
$viewData['first_name'],
$viewData['last_names'],
$viewData['email_address'],
$viewData['role_title']
);
}
}
final class ProfileViewModel
{
private $firstName,
$lastNames,
$emailAddress,
$roleTitle
;
public function __construct(
$firstName,
$lastNames,
$emailAddress,
$roleTitle
) {
$this->firstName = $firstName;
$this->lastNames = $lastNames;
$this->emailAddress = $emailAddress;
$this->roleTitle = $roleTitle;
}
public function firstName() { return $this->firstName; }
public function lastNames() { return $this->lastNames; }
public function fullName() { return sprintf('%s %s', $this->firstName, $this->lastNames); }
public function emailAddress() { return $this->emailAddress; }
public function roleTitle() { return $this->roleTitle; }
}
final class Employee
{
private $id,
$username,
$firstName,
$lastNames,
$emailAddress,
$password,
$active
;
private function __construct(
$id,
$firstName,
$lastNames,
$emailAddress,
$password
) {
$this->id = $id;
$this->firstName = $firstName;
$this->lastNames = $lastNames;
$this->emailAddress = $emailAddress;
$this->password = $password;
$this->username = sprintf("%s %s", $firstName, $lastNames);
$this->active = 1;
}
public static function recruit(
$id,
$firstName,
$lastNames,
$emailAddress,
$password
) {
return new self(
$id,
$firstName,
$lastNames,
$emailAddress,
$password
);
}
public function changeEmailAddress($emailAddress)
{
$previousEmailAddress = $this->emailAddress;
$this->emailAddress = $emailAddress;
}
public function __clone()
{
$clone = $this;
$clone->id = sha1(rand(1,1000));
$clone->state = 0;
return $clone;
}
}
final class EntityMapper
{
private $mapping = [];
public function __construct(
array $mapping = []
) {
$this->mapping = $mapping;
}
public function handle($object)
{
return $this->mapping[get_class($object)];
}
}
class EmployeeMapper
{
public static $table = 'employees';
public static $entity = '\Employee';
public static $idMap = ['id', 'sId'];
public static $fields = [
'id' => 'sId',
'firstName' => 'sFirstname',
'lastNames' => 'sLastname',
'emailAddress' => 'sEmailAddress',
'password' => 'sPassword',
'username' => 'sUsername',
'active' => 'bIsActive',
];
}
final class UnitOfWork extends \SPLObjectStorage
{
private $storage = [],
$connection,
$queryBuilder;
public function __construct(
PDOAdaptor $connection,
QueryBuilder $queryBuilder
) {
$this->queryBuilder = $queryBuilder;
$this->connection = $connection;
}
public function add($object)
{
$payload = $this->queryBuilder->buildFromObject($object);
$this->attach($object, $payload);
}
public function remove($object)
{
$this->detach($object);
}
public function commit()
{
$this->connection->disableAutoCommit();
foreach($this as $transaction) {
$this->connection->addTransaction($this[$transaction]['query'], $this[$transaction]['parameters']);
}
$this->connection->commit();
}
}
final class EmployeeRepository extends EmployeeMapper
{
private $connection,
$unitOfWork
;
public function __construct(
PDOAdaptor $connection,
UnitOfWork $unitOfWork
) {
$this->connection = $connection;
$this->unitOfWork = $unitOfWork;
}
public function fetchAll()
{
$employees = [];
$data = $this
->connection
->fetchAll("SELECT * FROM ".self::$table);
foreach ($data as $employee) {
$employees[] = (new Hydrator)->hydrate(self::$entity, self::$fields, $employee);
}
return $employees;
}
public function remove(Employee $employee)
{
$this->unitOfWork->remove($employee);
}
public function store(Employee $employee)
{
$this->unitOfWork->add($employee);
}
}
final class Hydrator //Lydrator [light::hydrator]
{
const EXISTING = 1;
public function hydrate(
$class,
array $mapping,
array $data
) {
$reflector = new \ReflectionClass($class);
$entity = $reflector->newInstanceWithoutConstructor();
foreach($reflector->getProperties() as $property) {
$property->setAccessible(true);
$property->setValue($entity, $data[$mapping[$property->getName()]]);
}
$entity->state = self::EXISTING;
return $entity;
}
}
// ---------
$db = new \PDOAdaptor('127.0.0.1', 'conductor', 'root', '');
$mapper = new EntityMapper([
'Employee' => 'EmployeeMapper',
]);
$queryBuilder = new QueryBuilder($mapper);
$uow = new \UnitOfWork($db, $queryBuilder);
$employeeRepo = new EmployeeRepository($db, $uow);
$employees = $employeeRepo->fetchAll();
$employee = $employees[0];
$clone = clone $employees[0];
$employee->changeEmailAddress('nigel_greenway@email.co.uk');
$employeeRepo->store($employee);
$employeeRepo->store($clone);
$clone->changeEmailAddress('ngreenway@email.com');
$employeeRepo->store($clone);
for ($i=0; $i <= 50; $i++) {
$new = Employee::recruit(
$i,
$i.'_Firstname',
$i.'_lastNames',
$i.'_emailAddress',
$i.'_password'
);
$employeeRepo->store($new);
}
$uow->commit();
die;
$employeeView = new ProfileViewGateway($db);
$view = $employeeView->fetch('1dca2fa9-c7d7-48df-8f9f-a42923123fe5');
$format = <<<MSG
Fullname: %s,
Email Address: %s
MSG;
echo sprintf(
$format,
$view->fullName(),
$view->emailAddress()
);
$time = <<<MSG
Total: %0.3f[S]
MSG;
echo sprintf($time, microtime(true)-$start);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment