Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Transactions #101

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 69 additions & 80 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -298,54 +298,6 @@ public static function setMode(string $value): void
self::$mode = $value;
}

/**
* If a resource has been created return it, otherwise create it and then return it
*
* @param string $name
* @param bool $fresh
* @return mixed
*
* @throws Exception
*/
public function getResource(string $name, bool $fresh = false): mixed
{
if ($name === 'utopia') {
return $this;
}

if (!\array_key_exists($name, $this->resources) || $fresh || self::$resourcesCallbacks[$name]['reset']) {
if (!\array_key_exists($name, self::$resourcesCallbacks)) {
throw new Exception('Failed to find resource: "' . $name . '"');
}

$this->resources[$name] = \call_user_func_array(
self::$resourcesCallbacks[$name]['callback'],
$this->getResources(self::$resourcesCallbacks[$name]['injections'])
);
}

self::$resourcesCallbacks[$name]['reset'] = false;

return $this->resources[$name];
}

/**
* Get Resources By List
*
* @param array $list
* @return array
*/
public function getResources(array $list): array
{
$resources = [];

foreach ($list as $name) {
$resources[$name] = $this->getResource($name);
}

return $resources;
}

/**
* Set a new resource callback
*
Expand Down Expand Up @@ -476,7 +428,7 @@ public function match(Request $request, bool $fresh = false): ?Route
* @param Route $route
* @param Request $request
*/
public function execute(Route $route, Request $request, Response $response): static
public function execute(Transaction $transaction, Route $route, Request $request, Response $response): static
{
$arguments = [];
$groups = $route->getGroups();
Expand All @@ -486,7 +438,7 @@ public function execute(Route $route, Request $request, Response $response): sta
if ($route->getHook()) {
foreach (self::$init as $hook) { // Global init hooks
if (in_array('*', $hook->getGroups())) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
$arguments = $this->getArguments($transaction, $hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
Expand All @@ -495,14 +447,14 @@ public function execute(Route $route, Request $request, Response $response): sta
foreach ($groups as $group) {
foreach (self::$init as $hook) { // Group init hooks
if (in_array($group, $hook->getGroups())) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
$arguments = $this->getArguments($transaction, $hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
}

if (!($response->isSent())) {
$arguments = $this->getArguments($route, $pathValues, $request->getParams());
$arguments = $this->getArguments($transaction, $route, $pathValues, $request->getParams());

// Call the action callback with the matched positions as params
\call_user_func_array($route->getAction(), $arguments);
Expand All @@ -512,7 +464,7 @@ public function execute(Route $route, Request $request, Response $response): sta
foreach ($groups as $group) {
foreach (self::$shutdown as $hook) { // Group shutdown hooks
if (in_array($group, $hook->getGroups())) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
$arguments = $this->getArguments($transaction, $hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
Expand All @@ -521,19 +473,20 @@ public function execute(Route $route, Request $request, Response $response): sta
if ($route->getHook()) {
foreach (self::$shutdown as $hook) { // Group shutdown hooks
if (in_array('*', $hook->getGroups())) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
$arguments = $this->getArguments($transaction, $hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
}
} catch (\Throwable $e) {
self::setResource('error', fn () => $e);
$transaction->setResource('error', fn () => $e);

foreach ($groups as $group) {
foreach (self::$errors as $error) { // Group error hooks
if (in_array($group, $error->getGroups())) {
try {
$arguments = $this->getArguments($error, $pathValues, $request->getParams());
$arguments = $this->getArguments($transaction, $error, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
} catch (\Throwable $e) {
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
Expand All @@ -545,7 +498,7 @@ public function execute(Route $route, Request $request, Response $response): sta
foreach (self::$errors as $error) { // Global error hooks
if (in_array('*', $error->getGroups())) {
try {
$arguments = $this->getArguments($error, $pathValues, $request->getParams());
$arguments = $this->getArguments($transaction, $error, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
} catch (\Throwable $e) {
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
Expand All @@ -567,7 +520,7 @@ public function execute(Route $route, Request $request, Response $response): sta
*
* @throws Exception
*/
protected function getArguments(Hook $hook, array $values, array $requestParams): array
protected function getArguments(Transaction $transaction, Hook $hook, array $values, array $requestParams): array
{
$arguments = [];
foreach ($hook->getParams() as $key => $param) { // Get value from route or request object
Expand All @@ -584,7 +537,7 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
}

if ($paramExists) {
$this->validate($key, $param, $value);
$this->validate($transaction, $key, $param, $value);
}
}

Expand All @@ -593,12 +546,41 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
}

foreach ($hook->getInjections() as $key => $injection) {
$arguments[$injection['order']] = $this->getResource($injection['name']);
$arguments[$injection['order']] = $transaction->getResource($injection['name']);
}

return $arguments;
}

public function createTransaction(Request $request, Response $response)
{
$transaction = new Transaction();

$transaction->setResource('request', function () use ($request) {
return $request;
});

$transaction->setResource('response', function () use ($response) {
return $response;
});

foreach (self::$resourcesCallbacks as $name => $value) {
$transaction->setResource($name, $value['callback'], $value['injections']);
}

$utopia = $this;
$transaction->setResource('utopia', function () use ($utopia) {
return $utopia;
});

$route = $this->match($request);
$transaction->setResource('route', function () use ($route) {
return $route;
});

return $transaction;
}

/**
* Run
*
Expand All @@ -607,19 +589,16 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
*
* @param Request $request
* @param Response $response
* @param ?Transaction $transaction
*/
public function run(Request $request, Response $response): static
public function run(Request $request, Response $response, Transaction $transaction = null): static
{
$this->resources['request'] = $request;
$this->resources['response'] = $response;

self::setResource('request', function () use ($request) {
return $request;
});

self::setResource('response', function () use ($response) {
return $response;
});
if(empty($transaction)) {
$transaction = $this->createTransaction($request, $response);
} else {
$transaction->setResource('request', fn () => $request);
$transaction->setResource('response', fn () => $response);
}

$method = $request->getMethod();
$route = $this->match($request);
Expand All @@ -636,15 +615,15 @@ public function run(Request $request, Response $response): static
foreach (self::$options as $option) { // Group options hooks
/** @var Hook $option */
if (in_array($group, $option->getGroups())) {
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
\call_user_func_array($option->getAction(), $this->getArguments($transaction, $option, [], $request->getParams()));
}
}
}

foreach (self::$options as $option) { // Global options hooks
/** @var Hook $option */
if (in_array('*', $option->getGroups())) {
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
\call_user_func_array($option->getAction(), $this->getArguments($transaction, $option, [], $request->getParams()));
}
}
} catch (\Throwable $e) {
Expand All @@ -654,7 +633,10 @@ public function run(Request $request, Response $response): static
self::setResource('error', function () use ($e) {
return $e;
});
\call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
$transaction->setResource('error', function () use ($e) {
return $e;
});
\call_user_func_array($error->getAction(), $this->getArguments($transaction, $error, [], $request->getParams()));
}
}
}
Expand All @@ -667,23 +649,27 @@ public function run(Request $request, Response $response): static
$this->route = $route;
$path = \parse_url($request->getURI(), PHP_URL_PATH);
$route->path($path);

$transaction->setResource('route', function () use ($route) {
return $route;
});
}

if (null !== $route) {
return $this->execute($route, $request, $response);
return $this->execute($transaction, $route, $request, $response);
} elseif (self::REQUEST_METHOD_OPTIONS == $method) {
try {
foreach ($groups as $group) {
foreach (self::$options as $option) { // Group options hooks
if (in_array($group, $option->getGroups())) {
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
\call_user_func_array($option->getAction(), $this->getArguments($transaction, $option, [], $request->getParams()));
}
}
}

foreach (self::$options as $option) { // Global options hooks
if (in_array('*', $option->getGroups())) {
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
\call_user_func_array($option->getAction(), $this->getArguments($transaction, $option, [], $request->getParams()));
}
}
} catch (\Throwable $e) {
Expand All @@ -692,7 +678,7 @@ public function run(Request $request, Response $response): static
self::setResource('error', function () use ($e) {
return $e;
});
\call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
\call_user_func_array($error->getAction(), $this->getArguments($transaction, $error, [], $request->getParams()));
}
}
}
Expand All @@ -702,7 +688,10 @@ public function run(Request $request, Response $response): static
self::setResource('error', function () {
return new Exception('Not Found', 404);
});
\call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
$transaction->setResource('error', function () {
return new Exception('Not Found', 404);
});
\call_user_func_array($error->getAction(), $this->getArguments($transaction, $error, [], $request->getParams()));
}
}
}
Expand All @@ -722,7 +711,7 @@ public function run(Request $request, Response $response): static
*
* @throws Exception
*/
protected function validate(string $key, array $param, mixed $value): void
protected function validate(Transaction $transaction, string $key, array $param, mixed $value): void
{
if ($param['optional'] && \is_null($value)) {
return;
Expand All @@ -731,7 +720,7 @@ protected function validate(string $key, array $param, mixed $value): void
$validator = $param['validator']; // checking whether the class exists

if (\is_callable($validator)) {
$validator = \call_user_func_array($validator, $this->getResources($param['injections']));
$validator = \call_user_func_array($validator, $transaction->getResources($param['injections']));
}

if (!$validator instanceof Validator) { // is the validator object an instance of the Validator class
Expand Down
65 changes: 65 additions & 0 deletions src/Transaction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace Utopia;

class Transaction
{
protected string $id;
protected array $resourcesCallbacks;
protected array $resources;

public function __construct()
{
$this->id = $this->unique();
$this->resourcesCallbacks = [];
$this->resources = [];
}

public function setResource(string $name, callable $callback, array $injections = []): void
{
$this->resourcesCallbacks[$name] = ['callback' => $callback, 'injections' => $injections];
}

public function getResource(string $name, bool $fresh = false): mixed
{
if ($name === 'transaction') {
return $this;
}

if (!\array_key_exists($name, $this->resources) || $fresh) {
if (!\array_key_exists($name, $this->resourcesCallbacks)) {
throw new Exception('Failed to find resource: "' . $name . '"');
}

$this->resources[$name] = \call_user_func_array(
$this->resourcesCallbacks[$name]['callback'],
$this->getResources($this->resourcesCallbacks[$name]['injections'])
);
}

return $this->resources[$name];
}

public function getResources(array $list): array
{
$resources = [];

foreach ($list as $name) {
$resources[$name] = $this->getResource($name);
}

return $resources;
}

protected static function unique(int $padding = 7): string
{
$uniqid = \uniqid();

if ($padding > 0) {
$bytes = \random_bytes((int) \ceil($padding / 2)); // one byte expands to two chars
$uniqid .= \substr(\bin2hex($bytes), 0, $padding);
}

return $uniqid;
}
}
Loading