Last active
October 16, 2023 21:00
-
-
Save NigelGreenway/502c50a139a767a3afd2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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