Skip to content
Draft
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/vendor/
/.idea/
*.cache
*.cache

/benchmarking
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`

FROM php:8.1-cli-alpine as final
FROM php:8.3-cli-alpine as final
LABEL maintainer="team@appwrite.io"

ENV DEBIAN_FRONTEND=noninteractive \
PHP_VERSION=82
PHP_VERSION=83

RUN \
apk add --no-cache --virtual .deps \
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
"format": "vendor/bin/pint",
"check": "vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 512M",
"test": "vendor/bin/phpunit --configuration phpunit.xml",
"test:unit": "vendor/bin/phpunit tests/ --exclude-group e2e",
"test:e2e": "php tests/e2e/runner.php",
"bench": "vendor/bin/phpbench run --report=benchmark"
},
"require": {
"php": ">=8.3",
"php": ">=8.3.0",
"utopia-php/compression": "0.1.*",
"utopia-php/telemetry": "0.1.*",
"utopia-php/validators": "0.1.*"
Expand Down
125 changes: 108 additions & 17 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
use Utopia\Telemetry\Adapter\None as NoTelemetry;
use Utopia\Telemetry\Histogram;
use Utopia\Telemetry\UpDownCounter;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\FloatValidator;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;

class App
{
public const COMPRESSION_MIN_SIZE_DEFAULT = 1024;
/**
* Hook type constants
*/
public const string HOOK_INIT = 'init';
public const string HOOK_SHUTDOWN = 'shutdown';
public const string HOOK_ERRORS = 'errors';

public const int COMPRESSION_MIN_SIZE_DEFAULT = 1024;

/**
* Request method constants
Expand Down Expand Up @@ -83,6 +95,13 @@ class App
*/
protected static array $shutdown = [];

/**
* @var array<string,array<string,Hook[]>>
*/
protected static array $indexedHooks = [];

protected static bool $hooksIndexed = false;

/**
* Options
*
Expand Down Expand Up @@ -278,8 +297,12 @@ public static function init(): Hook
{
$hook = new Hook();
$hook->groups(['*']);
$hook->onGroupsUpdated(function () {
self::$hooksIndexed = false;
});

self::$init[] = $hook;
self::$hooksIndexed = false;

return $hook;
}
Expand All @@ -295,8 +318,12 @@ public static function shutdown(): Hook
{
$hook = new Hook();
$hook->groups(['*']);
$hook->onGroupsUpdated(function () {
self::$hooksIndexed = false;
});

self::$shutdown[] = $hook;
self::$hooksIndexed = false;

return $hook;
}
Expand Down Expand Up @@ -329,8 +356,12 @@ public static function error(): Hook
{
$hook = new Hook();
$hook->groups(['*']);
$hook->onGroupsUpdated(function () {
self::$hooksIndexed = false;
});

self::$errors[] = $hook;
self::$hooksIndexed = false;

return $hook;
}
Expand Down Expand Up @@ -582,6 +613,11 @@ public function match(Request $request, bool $fresh = false): ?Route
*/
public function execute(Route $route, Request $request, Response $response): static
{
if (!self::$hooksIndexed) {
$this->buildHookIndexes();
self::$hooksIndexed = true;
}

$arguments = [];
$groups = $route->getGroups();

Expand All @@ -590,17 +626,17 @@ public function execute(Route $route, Request $request, Response $response): sta

try {
if ($route->getHook()) {
foreach (self::$init as $hook) { // Global init hooks
if (in_array('*', $hook->getGroups())) {
if (isset(self::$indexedHooks[self::HOOK_INIT]['*'])) {
foreach (self::$indexedHooks[self::HOOK_INIT]['*'] as $hook) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
}

foreach ($groups as $group) {
foreach (self::$init as $hook) { // Group init hooks
if (in_array($group, $hook->getGroups())) {
if (isset(self::$indexedHooks[self::HOOK_INIT][$group])) {
foreach (self::$indexedHooks[self::HOOK_INIT][$group] as $hook) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
Expand All @@ -616,17 +652,17 @@ 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())) {
if (isset(self::$indexedHooks[self::HOOK_SHUTDOWN][$group])) {
foreach (self::$indexedHooks[self::HOOK_SHUTDOWN][$group] as $hook) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
}
}

if ($route->getHook()) {
foreach (self::$shutdown as $hook) { // Group shutdown hooks
if (in_array('*', $hook->getGroups())) {
if (isset(self::$indexedHooks[self::HOOK_SHUTDOWN]['*'])) {
foreach (self::$indexedHooks[self::HOOK_SHUTDOWN]['*'] as $hook) {
$arguments = $this->getArguments($hook, $pathValues, $request->getParams());
\call_user_func_array($hook->getAction(), $arguments);
}
Expand All @@ -636,8 +672,8 @@ public function execute(Route $route, Request $request, Response $response): sta
self::setResource('error', fn () => $e);

foreach ($groups as $group) {
foreach (self::$errors as $error) { // Group error hooks
if (in_array($group, $error->getGroups())) {
if (isset(self::$indexedHooks[self::HOOK_ERRORS][$group])) {
foreach (self::$indexedHooks[self::HOOK_ERRORS][$group] as $error) {
try {
$arguments = $this->getArguments($error, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
Expand All @@ -648,8 +684,8 @@ public function execute(Route $route, Request $request, Response $response): sta
}
}

foreach (self::$errors as $error) { // Global error hooks
if (in_array('*', $error->getGroups())) {
if (isset(self::$indexedHooks[self::HOOK_ERRORS]['*'])) {
foreach (self::$indexedHooks[self::HOOK_ERRORS]['*'] as $error) {
try {
$arguments = $this->getArguments($error, $pathValues, $request->getParams());
\call_user_func_array($error->getAction(), $arguments);
Expand Down Expand Up @@ -718,7 +754,7 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
}
if (\is_array($value)) {
$validator = $param['validator'];
$isArrayList = $validator instanceof \Utopia\Validator\ArrayList;
$isArrayList = $validator instanceof ArrayList;

if ($isArrayList) {
try {
Expand Down Expand Up @@ -747,7 +783,26 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
!($param['optional'] && $value === null) &&
$paramExists
) {
$this->validate($key, $param, $value);
$validator = $this->validate($key, $param, $value);

if ($existsInRequest && $value !== null) {
if ($validator instanceof Boolean) {
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($value === null) {
throw new Exception('Invalid boolean value for param "' . $key . '"', 400);
}
} elseif ($validator instanceof Integer && \is_string($value)) {
if (\is_numeric($value)) {
$value = (int)$value;
}
} elseif ($validator instanceof FloatValidator && \is_string($value)) {
if (\is_numeric($value)) {
$value = (float)$value;
}
} elseif ($validator instanceof Text && !\is_string($value)) {
$value = (string)$value;
}
}
}

$hook->setParamValue($key, $value);
Expand All @@ -766,6 +821,11 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
*/
public function run(Request $request, Response $response): static
{
if (!self::$hooksIndexed) {
$this->buildHookIndexes();
self::$hooksIndexed = true;
}

$this->activeRequests->add(1, [
'http.request.method' => $request->getMethod(),
'url.scheme' => $request->getProtocol(),
Expand Down Expand Up @@ -933,11 +993,11 @@ private function runInternal(Request $request, Response $response): static
* @param string $key
* @param array $param
* @param mixed $value
* @return void
* @return Validator
*
* @throws Exception
*/
protected function validate(string $key, array $param, mixed $value): void
protected function validate(string $key, array $param, mixed $value): Validator
{
$validator = $param['validator']; // checking whether the class exists

Expand All @@ -952,13 +1012,42 @@ protected function validate(string $key, array $param, mixed $value): void
if (!$validator->isValid($value)) {
throw new Exception('Invalid `' . $key . '` param: ' . $validator->getDescription(), 400);
}

return $validator;
}

/**
* Reset all the static variables
*
* @return void
*/
protected function buildHookIndexes(): void
{
self::$indexedHooks = [
self::HOOK_INIT => [],
self::HOOK_SHUTDOWN => [],
self::HOOK_ERRORS => []
];

foreach (self::$init as $hook) {
foreach ($hook->getGroups() as $group) {
self::$indexedHooks[self::HOOK_INIT][$group][] = $hook;
}
}

foreach (self::$shutdown as $hook) {
foreach ($hook->getGroups() as $group) {
self::$indexedHooks[self::HOOK_SHUTDOWN][$group][] = $hook;
}
}

foreach (self::$errors as $error) {
foreach ($error->getGroups() as $group) {
self::$indexedHooks[self::HOOK_ERRORS][$group][] = $error;
}
}
}

public static function reset(): void
{
Router::reset();
Expand All @@ -968,5 +1057,7 @@ public static function reset(): void
self::$init = [];
self::$shutdown = [];
self::$options = [];
self::$indexedHooks = [];
self::$hooksIndexed = false;
}
}
22 changes: 22 additions & 0 deletions src/Hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ public function getDesc(): string
return $this->desc;
}

/**
* @var callable|null
*/
protected $groupsUpdatedCallback = null;

/**
* Add Group
*
Expand All @@ -91,6 +96,23 @@ public function groups(array $groups): static
{
$this->groups = $groups;

if ($this->groupsUpdatedCallback !== null) {
($this->groupsUpdatedCallback)();
}

return $this;
}

/**
* Set callback for when groups are updated
*
* @param callable $callback
* @return static
*/
public function onGroupsUpdated(callable $callback): static
{
$this->groupsUpdatedCallback = $callback;

return $this;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ protected function generateHeaders(): array
$headers = [];

foreach ($_SERVER as $name => $value) {
if (\substr($name, 0, 5) == 'HTTP_') {
if (\str_starts_with($name, 'HTTP_')) {
$headers[\str_replace(' ', '-', \strtolower(\str_replace('_', ' ', \substr($name, 5))))] = $value;
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -706,13 +706,15 @@ public function send(string $body = ''): void

$headersSize = 0;
foreach ($this->headers as $name => $values) {
$nameLength = \strlen($name) + 2; // ": "

if (\is_array($values)) {
foreach ($values as $value) {
$headersSize += \strlen($name . ': ' . $value);
$headersSize += $nameLength + \strlen($value);
}
$headersSize += (\count($values) - 1) * 2; // linebreaks
} else {
$headersSize += \strlen($name . ': ' . $values);
$headersSize += $nameLength + \strlen($values);
}
}
$headersSize += (\count($this->headers) - 1) * 2; // linebreaks
Expand Down
Loading