Imported into new repository.
This commit is contained in:
commit
ac2255d24d
187 changed files with 15021 additions and 0 deletions
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
* text=auto
|
||||
*.sh text eol=lf
|
||||
*.php text eol=lf
|
||||
*.bat text eol=crlf
|
||||
VERSION text eol=lf
|
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Tt]humbs.db
|
||||
[Dd]esktop.ini
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.vs/
|
||||
.idea/
|
||||
docs/html/
|
||||
.phpdoc*
|
||||
.phpunit*
|
25
LICENCE
Normal file
25
LICENCE
Normal file
|
@ -0,0 +1,25 @@
|
|||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2021-2022, flashwave <me@flash.moe>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
39
README.md
Normal file
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Index
|
||||
|
||||
Index is a common library for my PHP projects.
|
||||
|
||||
It provides a number of components that I would otherwise copy between projects (and thus become out-of-sync) as well as a number of quality of life things on top of standard PHP stdlib functionality such as abstractions of arrays and strings as objects inspired by .NET's standard library.
|
||||
|
||||
|
||||
## Requirements and Dependencies
|
||||
|
||||
Index currently targets **PHP 8.1**. (also list extensions!!!!!!!!!)
|
||||
|
||||
### `Index\Data\MariaDB`
|
||||
|
||||
Requires the `mysqli` extension. `mysqlnd` is recommended as the underlying driver, but `libmysql` should work without a hitch. This driver also works for MySQL as the dependencies would suggest, but you should consider using MariaDB instead of possible.
|
||||
|
||||
### `Index\Data\SQLite`
|
||||
|
||||
Requires the `sqlite3` extension.
|
||||
|
||||
|
||||
## Versioning
|
||||
|
||||
Index versioning will mostly follow the [Semantic Versioning specification v2.0.0](https://semver.org/spec/v2.0.0.html), counting dropped support for a minor PHP version (e.g. 7.1 -> 7.2 or 7.4 -> 8.0) as a reason to increment the major version.
|
||||
Previous major versions may be supported for a time with backports depending on what projects of mine still target older versions of PHP.
|
||||
|
||||
The version is stored in the root of the repository in a file called `VERSION` and can be read out within Index using `Index\Environment::getIndexVersion()`.
|
||||
|
||||
|
||||
## Contribution
|
||||
|
||||
By submitting code for inclusion in the main Index source tree you agree to transfer ownership of the code to the project owner.
|
||||
The contributor will still be attributed for the contributed code, unless they ask for this attribution to be removed.
|
||||
This is to avoid intellectual property rights traps and drama that could lead to blackmail situations.
|
||||
If you do not agree with these terms, you are free to fork off.
|
||||
|
||||
|
||||
## Licencing
|
||||
|
||||
Index is available under the BSD 2-Clause License, a full version of which is enclosed in the LICENCE file.
|
37
TODO.md
Normal file
37
TODO.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
# TODO
|
||||
|
||||
## High Prio
|
||||
|
||||
- Create tests for everything testable.
|
||||
|
||||
- Draw out a plan for the templating backend.
|
||||
- Will probably just construct using the HTML abstraction stuff, would make AJAX'ing page updates trivial also.
|
||||
- Reinvestigate the semi-isolated PHP script template system I used in some other project, I forgot which.
|
||||
|
||||
- Determine structure for markup parsing abstraction.
|
||||
- Writing a bbcode and markdown parser outside of Index, will implement a structure when done.
|
||||
|
||||
- Create HTML construction utilities.
|
||||
|
||||
- Create similarly address GD2/Imagick wrappers.
|
||||
|
||||
- Review CSRF code, Something about it has me constantly wondering if its too Complex.
|
||||
|
||||
- Figure out the SQL Query builder.
|
||||
- Might make sense to just have a predicate builder and selector builder.
|
||||
|
||||
- Create a URL formatter.
|
||||
|
||||
- Add RSS/Atom feed construction utilities.
|
||||
|
||||
- Probably get rid of StringBuilder, PHP strings aren't entirely immutable so that whole issue doesn't even exist.
|
||||
I have a number of possible fixes in my head for this. The primary reason for A/W/IString was to have a consistent API between
|
||||
normal strings and mbstrings. I'm not sure yet but part of me wants to keep IString but move a lot of functionality into XString and make that the go-to. WString could remain, maybe in a reduced form? Having the encoding of the string associated with it is very convenient.
|
||||
|
||||
## Low Prio
|
||||
|
||||
- Get guides working on phpdoc.
|
||||
|
||||
- Create phpdoc template.
|
||||
|
||||
- Review all constructors and see which one should be marked @internal.
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
|||
0.2202.281831
|
17
bench/_init.php
Normal file
17
bench/_init.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../index.php';
|
||||
|
||||
function bench(string $name, int $times, callable $body): void {
|
||||
printf('Running "%s" %d times%s', $name, $times, PHP_EOL);
|
||||
|
||||
$sw = \Index\Performance\Stopwatch::startNew();
|
||||
|
||||
while(--$times > 0)
|
||||
$body();
|
||||
|
||||
$sw->stop();
|
||||
|
||||
printf('Took: %Fms %s', $sw->getElapsedTime(), PHP_EOL);
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
7
bench/example.php
Normal file
7
bench/example.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/_init.php';
|
||||
|
||||
bench('Example name', 100000, function() {
|
||||
// run something here
|
||||
$i = 1 + 1;
|
||||
});
|
11
docs/index.rst
Normal file
11
docs/index.rst
Normal file
|
@ -0,0 +1,11 @@
|
|||
.. meta::
|
||||
:layout: landingpage
|
||||
:tada: true
|
||||
|
||||
Index Documentation
|
||||
===================
|
||||
|
||||
No Markdown!
|
||||
------------
|
||||
|
||||
test RST file because phpdoc doesn't into markdown
|
24
index.php
Normal file
24
index.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
// index.php
|
||||
// Created: 2021-04-26
|
||||
// Updated: 2021-05-04
|
||||
|
||||
namespace Index;
|
||||
|
||||
define('NDX_ROOT', __DIR__);
|
||||
define('NDX_DIR_SRC', NDX_ROOT . DIRECTORY_SEPARATOR . 'src');
|
||||
|
||||
require_once NDX_DIR_SRC . DIRECTORY_SEPARATOR . 'Autoloader.php';
|
||||
|
||||
Autoloader::addNamespace(__NAMESPACE__, NDX_DIR_SRC);
|
||||
Autoloader::register();
|
||||
|
||||
// currently phpstan sucks and relies on error suppression, luckily it leaves a constant!
|
||||
if(!defined('__PHPSTAN_RUNNING__')) {
|
||||
// defining this WILL cause issues, never do it unless you HAVE to
|
||||
if(!defined('NDX_LEAVE_ERRORS'))
|
||||
Exceptions::convertErrors();
|
||||
|
||||
if(!defined('NDX_LEAVE_EXCEPTIONS'))
|
||||
Exceptions::handleExceptions();
|
||||
}
|
41
phpdoc.xml
Normal file
41
phpdoc.xml
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<phpdocumentor configVersion="3.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://www.phpdoc.org"
|
||||
xsi:noNamespaceSchemaLocation="data/xsd/phpdoc.xsd">
|
||||
<title>Index Documentation</title>
|
||||
<template name="default"/>
|
||||
<paths>
|
||||
<output>docs/html</output>
|
||||
</paths>
|
||||
<version number="0.1">
|
||||
<folder>latest</folder>
|
||||
<api format="php">
|
||||
<output>api</output>
|
||||
<visibility>public</visibility>
|
||||
<default-package-name>Index</default-package-name>
|
||||
<source dsn=".">
|
||||
<path>src</path>
|
||||
</source>
|
||||
<extensions>
|
||||
<extension>php</extension>
|
||||
</extensions>
|
||||
<ignore hidden="true" symlinks="true">
|
||||
<path>tests/**/*</path>
|
||||
</ignore>
|
||||
<ignore-tags>
|
||||
<ignore-tag>template</ignore-tag>
|
||||
<ignore-tag>template-extends</ignore-tag>
|
||||
<ignore-tag>template-implements</ignore-tag>
|
||||
<ignore-tag>extends</ignore-tag>
|
||||
<ignore-tag>implements</ignore-tag>
|
||||
</ignore-tags>
|
||||
</api>
|
||||
<guide format="rst">
|
||||
<source dsn=".">
|
||||
<path>docs</path>
|
||||
</source>
|
||||
<output>guide</output>
|
||||
</guide>
|
||||
</version>
|
||||
</phpdocumentor>
|
6
phpstan.neon
Normal file
6
phpstan.neon
Normal file
|
@ -0,0 +1,6 @@
|
|||
parameters:
|
||||
level: 5 # Raise this eventually
|
||||
paths:
|
||||
- src
|
||||
bootstrapFiles:
|
||||
- index.php
|
27
phpunit.xml
Normal file
27
phpunit.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
|
||||
bootstrap="index.php"
|
||||
colors="true"
|
||||
cacheResultFile=".phpunit.cache/test-results"
|
||||
executionOrder="depends,defects"
|
||||
forceCoversAnnotation="true"
|
||||
beStrictAboutCoversAnnotation="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTodoAnnotatedTests="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true"
|
||||
verbose="true">
|
||||
<testsuites>
|
||||
<testsuite name="default">
|
||||
<directory suffix="Test.php">tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<coverage cacheDirectory=".phpunit.cache/code-coverage"
|
||||
processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
</phpunit>
|
274
src/AString.php
Normal file
274
src/AString.php
Normal file
|
@ -0,0 +1,274 @@
|
|||
<?php
|
||||
// AString.php
|
||||
// Created: 2021-04-26
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index;
|
||||
|
||||
use Traversable;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Provides an immutable ASCII string with arrow methods.
|
||||
*
|
||||
* Normal PHP strings should be used for buffers/byte arrays.
|
||||
*/
|
||||
final class AString implements IString {
|
||||
use XStringTrait;
|
||||
|
||||
private string $value;
|
||||
|
||||
/**
|
||||
* Create an AString instance.
|
||||
*
|
||||
* @param string $value PHP string to inherit.
|
||||
* @return AString New instance of AString.
|
||||
*/
|
||||
public function __construct(string $value) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getLength(): int {
|
||||
return strlen($this->value);
|
||||
}
|
||||
|
||||
public function isEmpty(): bool {
|
||||
return $this->value === '';
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an offset exists in the string.
|
||||
*
|
||||
* You should call isset($string[$offset]) instead of $string->offsetExists($offset).
|
||||
*
|
||||
* @see https://www.php.net/manual/en/arrayaccess.offsetexists.php
|
||||
* @param int $offset Character offset.
|
||||
* @return bool true if it exists, false if not.
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool {
|
||||
return isset($this->value[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an offset from the string.
|
||||
*
|
||||
* You should do $string[$offset] instead of $string->offsetGet($offset).
|
||||
*
|
||||
* @see https://www.php.net/manual/en/arrayaccess.offsetget.php
|
||||
* @param int $offset Character offset.
|
||||
* @return string Character at that offset.
|
||||
*/
|
||||
public function offsetGet(mixed $offset): mixed {
|
||||
return $this->value[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an iterator object for this string.
|
||||
*
|
||||
* @return StringIterator An iterator for this string.
|
||||
*/
|
||||
public function getIterator(): Traversable {
|
||||
return new StringIterator($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data which should be serialized as json.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed Data to be passed to json_encode.
|
||||
*/
|
||||
public function jsonSerialize(): mixed {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function bencodeSerialise(): mixed {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a serialized representation of this object.
|
||||
*
|
||||
* @return array Serialized data.
|
||||
*/
|
||||
public function __serialize(): array {
|
||||
return [$this->value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs an object from a serialized string.
|
||||
*
|
||||
* @param array $serialized Serialized data.
|
||||
*/
|
||||
public function __unserialize(array $serialized): void {
|
||||
$this->value = $serialized[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this string is identical to another.
|
||||
*
|
||||
* @param mixed $other An instance of AString or a PHP string.
|
||||
* @return bool true if the strings have the same value, false if not.
|
||||
*/
|
||||
public function equals(mixed $other): bool {
|
||||
return $this->compare($other) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares whether this string is identical to another.
|
||||
*
|
||||
* @param mixed $other An instance of IString or a PHP string.
|
||||
*/
|
||||
public function compare(mixed $other): int {
|
||||
return strcmp($this->value, (string)$other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new identical AString instance.
|
||||
*
|
||||
* This method is somewhat pointless, given the immutable nature of this object,
|
||||
* but rather people calling this instead of calling ->substring(0);
|
||||
*
|
||||
* @return AString A new identical instance of AString.
|
||||
*/
|
||||
public function clone(): mixed {
|
||||
return new AString($this->value);
|
||||
}
|
||||
|
||||
public function indexOf(IString|string $text, int $offset = 0): int {
|
||||
$pos = strpos($this->value, (string)$text, $offset);
|
||||
if($pos === false)
|
||||
return -1;
|
||||
return $pos;
|
||||
}
|
||||
|
||||
public function contains(IString|string $text): bool {
|
||||
return str_contains($this->value, (string)$text);
|
||||
}
|
||||
|
||||
public function substring(int $offset, int|null $length = null): IString {
|
||||
return new AString(substr($this->value, $offset, $length));
|
||||
}
|
||||
|
||||
public function replace(IString|string $search, IString|string $replace): IString {
|
||||
return new AString(str_replace((string)$search, (string)$replace, $this->value));
|
||||
}
|
||||
|
||||
public function append(IString|string $string): IString {
|
||||
return new AString($this->value . (string)$string);
|
||||
}
|
||||
|
||||
public function prepend(IString|string $string): IString {
|
||||
return new AString(((string)$string) . $this->value);
|
||||
}
|
||||
|
||||
public function split(IString|string $separator, int $limit = PHP_INT_MAX): array {
|
||||
$separator = (string)$separator;
|
||||
if(empty($separator))
|
||||
throw new InvalidArgumentException('$separator may not be empty.');
|
||||
return XArray::select(
|
||||
explode($separator, $this->value, $limit),
|
||||
fn($str) => new AString($str)
|
||||
);
|
||||
}
|
||||
|
||||
public function chunk(int $chunkSize): array {
|
||||
return XArray::select(
|
||||
str_split($this->value, $chunkSize),
|
||||
fn($str) => new AString($str)
|
||||
);
|
||||
}
|
||||
|
||||
public function trim(IString|string $characters = IString::TRIM_CHARS): IString {
|
||||
return new AString(trim($this->value, (string)$characters));
|
||||
}
|
||||
|
||||
public function trimStart(IString|string $characters = IString::TRIM_CHARS): IString {
|
||||
return new AString(ltrim($this->value, (string)$characters));
|
||||
}
|
||||
|
||||
public function trimEnd(IString|string $characters = IString::TRIM_CHARS): IString {
|
||||
return new AString(rtrim($this->value, (string)$characters));
|
||||
}
|
||||
|
||||
public function toLower(): IString {
|
||||
return new AString(strtolower($this->value));
|
||||
}
|
||||
|
||||
public function toUpper(): IString {
|
||||
return new AString(strtoupper($this->value));
|
||||
}
|
||||
|
||||
public function reverse(): IString {
|
||||
return new AString(strrev($this->value));
|
||||
}
|
||||
|
||||
public function startsWith(IString|string $text): bool {
|
||||
return str_starts_with($this->value, (string)$text);
|
||||
}
|
||||
|
||||
public function endsWith(IString|string $text): bool {
|
||||
return str_ends_with($this->value, (string)$text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts this AString to a WString.
|
||||
*
|
||||
* @param ?string $encoding Intended encoding, null for the Index-level default.
|
||||
* @param bool $convert true to convert the string to the target encoding, false to leave the bytes as-is.
|
||||
* @return WString A WString of the provided encoding with the value of this AString.
|
||||
*/
|
||||
public function toWString(?string $encoding = null, bool $convert = true): WString {
|
||||
$value = $this->value;
|
||||
$encoding ??= WString::getDefaultEncoding();
|
||||
if($convert)
|
||||
$value = mb_convert_encoding($value, $encoding, 'ascii');
|
||||
return new WString($value, $encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins an iterable object together with a separator to create a string.
|
||||
*
|
||||
* @param iterable $source Source object.
|
||||
* @param IString|string $separator Separator to use as glue.
|
||||
* @return AString Resulting string.
|
||||
*/
|
||||
public static function join(iterable $source, IString|string $separator = ''): AString {
|
||||
if(!is_array($source)) {
|
||||
$parts = [];
|
||||
foreach($source as $value)
|
||||
$parts[] = $value;
|
||||
$source = $parts;
|
||||
}
|
||||
|
||||
return new AString(implode((string)$separator, $source));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reusable empty string instance.
|
||||
*
|
||||
* @return AString An empty string.
|
||||
*/
|
||||
public static function empty(): AString {
|
||||
static $empty = null;
|
||||
$empty ??= new AString('');
|
||||
return $empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value to AString.
|
||||
*
|
||||
* @param mixed $value Source value.
|
||||
* @return AString An AString representing the given value.
|
||||
*/
|
||||
public static function cast(mixed $value): AString {
|
||||
if($value instanceof AString)
|
||||
return $value;
|
||||
if($value instanceof WString)
|
||||
return $value->toAString();
|
||||
return new AString(strval($value));
|
||||
}
|
||||
}
|
108
src/Autoloader.php
Normal file
108
src/Autoloader.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
// Autoloader.php
|
||||
// Created: 2021-05-04
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Provides a simple PSR-4 style autoloader.
|
||||
*
|
||||
* Only basic types should be used in this class because this is the first file included
|
||||
* and obviously can't autoload things before it has been set up.
|
||||
*/
|
||||
final class Autoloader {
|
||||
private const EXTENSION = '.php';
|
||||
|
||||
private static array $namespaces = [];
|
||||
|
||||
/**
|
||||
* Registers this autoloader with PHP.
|
||||
*/
|
||||
public static function register(): void {
|
||||
spl_autoload_register([self::class, 'autoloader']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregistered this autoloader with PHP.
|
||||
*/
|
||||
public static function unregister(): void {
|
||||
spl_autoload_unregister([self::class, 'autoloader']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans a PHP class path for file system look up.
|
||||
*
|
||||
* @param string $name A PHP class path.
|
||||
* @return string A cleaned PHP class path.
|
||||
*/
|
||||
public static function cleanName(string $name): string {
|
||||
return trim($name, '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find a class in the registered namespaces directories.
|
||||
*
|
||||
* Only supports the .php extension, others are silly and only add overhead.
|
||||
*
|
||||
* @param string $className Target class path.
|
||||
*/
|
||||
public static function autoloader(string $className): void {
|
||||
$classPath = explode('\\', self::cleanName($className));
|
||||
|
||||
for($i = 0; $i < count($classPath); ++$i) {
|
||||
$rootSpace = implode('\\', array_slice($classPath, 0, $i + 1));
|
||||
|
||||
if(isset(self::$namespaces[$rootSpace])) {
|
||||
$path = self::$namespaces[$rootSpace]
|
||||
. DIRECTORY_SEPARATOR
|
||||
. implode(DIRECTORY_SEPARATOR, array_slice($classPath, $i + 1))
|
||||
. self::EXTENSION;
|
||||
|
||||
if(is_file($path)) {
|
||||
require_once $path;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a directory with a namespace. Projects making use of Index may use this.
|
||||
*
|
||||
* @param string $namespace Target namespace.
|
||||
* @param string $directory Directory containing the classes of this namespace.
|
||||
* @throws InvalidArgumentException if $namespace is an empty string.
|
||||
* @throws InvalidArgumentException if $directory is a non-existent directory.
|
||||
* @throws InvalidArgumentException if $namespace is already registered.
|
||||
*/
|
||||
public static function addNamespace(string $namespace, string $directory): void {
|
||||
if(empty($namespace))
|
||||
throw new InvalidArgumentException('$namespace may not be an empty string.');
|
||||
if(!is_dir($directory))
|
||||
throw new InvalidArgumentException('$directory must point to an existing directory.');
|
||||
|
||||
$namespace = self::cleanName($namespace);
|
||||
$directory = rtrim(realpath($directory), DIRECTORY_SEPARATOR);
|
||||
|
||||
if(isset(self::$namespaces[$namespace]))
|
||||
throw new InvalidArgumentException("{$namespace} is already a registered namespace.");
|
||||
|
||||
self::$namespaces[$namespace] = $directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a registered namespace.
|
||||
*
|
||||
* Attempts to unregister Index are ignored.
|
||||
*
|
||||
* @param string $namespace Namespace to be removed.
|
||||
*/
|
||||
public static function removeNamespace(string $namespace): void {
|
||||
$namespace = self::cleanName($namespace);
|
||||
if($namespace !== 'Index')
|
||||
unset(self::$namespaces[$namespace]);
|
||||
}
|
||||
}
|
37
src/Collections/ArrayIterator.php
Normal file
37
src/Collections/ArrayIterator.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
// ArrayIterator.php
|
||||
// Created: 2022-02-03
|
||||
// Updated: 2022-02-03
|
||||
|
||||
namespace Index\Collections;
|
||||
|
||||
use Iterator;
|
||||
|
||||
class ArrayIterator implements Iterator {
|
||||
private array $array;
|
||||
private bool $wasValid = true;
|
||||
|
||||
public function __construct(array $array) {
|
||||
$this->array = $array;
|
||||
}
|
||||
|
||||
public function current(): mixed {
|
||||
return current($this->array);
|
||||
}
|
||||
|
||||
public function key(): mixed {
|
||||
return key($this->array);
|
||||
}
|
||||
|
||||
public function next(): void {
|
||||
$this->wasValid = next($this->array) !== false;
|
||||
}
|
||||
|
||||
public function rewind(): void {
|
||||
$this->wasValid = reset($this->array) !== false;
|
||||
}
|
||||
|
||||
public function valid(): bool {
|
||||
return $this->wasValid;
|
||||
}
|
||||
}
|
10
src/Collections/IArrayable.php
Normal file
10
src/Collections/IArrayable.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
// IArrayable.php
|
||||
// Created: 2022-02-03
|
||||
// Updated: 2022-02-03
|
||||
|
||||
namespace Index\Collections;
|
||||
|
||||
interface IArrayable {
|
||||
function toArray(): array;
|
||||
}
|
17
src/Colour.php
Normal file
17
src/Colour.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
// Colour.php
|
||||
// Created: 2021-09-09
|
||||
// Updated: 2022-02-02
|
||||
|
||||
namespace Index;
|
||||
|
||||
abstract class Colour extends XObject {
|
||||
abstract public function getRaw(): int;
|
||||
abstract public function getRed(): int;
|
||||
abstract public function getGreen(): int;
|
||||
abstract public function getBlue(): int;
|
||||
|
||||
public function getAlpha(): float {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
26
src/ColourARGB.php
Normal file
26
src/ColourARGB.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
// ColourARGB.php
|
||||
// Created: 2021-09-08
|
||||
// Updated: 2022-01-03
|
||||
|
||||
namespace Index;
|
||||
|
||||
class ColourARGB extends ColourRGB {
|
||||
public function getAlphaRaw(): int {
|
||||
return ($this->raw >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
public function getAlpha(): float {
|
||||
return ((float)$this->getAlphaRaw() / 0xFF);
|
||||
}
|
||||
|
||||
public function toString(): IString {
|
||||
return new AString(sprintf(
|
||||
'rgba(%d,%d,%d,%F)',
|
||||
$this->getRed(),
|
||||
$this->getGreen(),
|
||||
$this->getBlue(),
|
||||
round($this->getAlpha(), 3)
|
||||
));
|
||||
}
|
||||
}
|
25
src/ColourLegacy.php
Normal file
25
src/ColourLegacy.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
// ColourLegacy.php
|
||||
// Created: 2021-09-09
|
||||
// Updated: 2022-01-20
|
||||
|
||||
namespace Index;
|
||||
|
||||
class ColourLegacy extends ColourRGB {
|
||||
private const INHERIT = 0x40000000;
|
||||
|
||||
public function shouldInherit(): bool {
|
||||
return ($this->raw & self::INHERIT) > 0;
|
||||
}
|
||||
|
||||
public function toString(): IString {
|
||||
if($this->shouldInherit()) {
|
||||
static $inherit = null;
|
||||
if($inherit === null)
|
||||
$inherit = new AString('inherit');
|
||||
return $inherit;
|
||||
}
|
||||
|
||||
return parent::toString();
|
||||
}
|
||||
}
|
34
src/ColourRGB.php
Normal file
34
src/ColourRGB.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
// ColourRGB.php
|
||||
// Created: 2021-09-09
|
||||
// Updated: 2021-09-09
|
||||
|
||||
namespace Index;
|
||||
|
||||
class ColourRGB extends Colour {
|
||||
protected int $raw;
|
||||
|
||||
public function __construct(int $raw) {
|
||||
$this->raw = $raw;
|
||||
}
|
||||
|
||||
public function getRaw(): int {
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
public function getRed(): int {
|
||||
return ($this->raw >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
public function getGreen(): int {
|
||||
return ($this->raw >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
public function getBlue(): int {
|
||||
return $this->raw & 0xFF;
|
||||
}
|
||||
|
||||
public function toString(): IString {
|
||||
return new AString('#' . str_pad(dechex($this->raw & 0xFFFFFF), 6, '0', STR_PAD_LEFT));
|
||||
}
|
||||
}
|
11
src/Data/BeginTransactionFailedException.php
Normal file
11
src/Data/BeginTransactionFailedException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// BeginTransactionFailedException.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when transaction start fails.
|
||||
*/
|
||||
class BeginTransactionFailedException extends TransactionException {}
|
11
src/Data/CommitFailedException.php
Normal file
11
src/Data/CommitFailedException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// CommitFailedException.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when a transaction commit fails.
|
||||
*/
|
||||
class CommitFailedException extends TransactionException {}
|
11
src/Data/ConnectionFailedException.php
Normal file
11
src/Data/ConnectionFailedException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// ConnectionFailedException.php
|
||||
// Created: 2022-01-29
|
||||
// Updated: 2022-02-02
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when a connection fails.
|
||||
*/
|
||||
class ConnectionFailedException extends DataException {}
|
13
src/Data/DataException.php
Normal file
13
src/Data/DataException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// DataException.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception type of the Index\Data namespace.
|
||||
*/
|
||||
class DataException extends RuntimeException {}
|
101
src/Data/DbTools.php
Normal file
101
src/Data/DbTools.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
// DbTools.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-28
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Index\Type;
|
||||
|
||||
/**
|
||||
* Common database actions.
|
||||
*/
|
||||
final class DbTools {
|
||||
private const DB_PROTOS = [
|
||||
'null' => NullDb\NullDbBackend::class,
|
||||
'mariadb' => MariaDB\MariaDBBackend::class,
|
||||
'mysql' => MariaDB\MariaDBBackend::class,
|
||||
'sqlite' => SQLite\SQLiteBackend::class,
|
||||
'sqlite3' => SQLite\SQLiteBackend::class,
|
||||
];
|
||||
|
||||
public static function create(string $dsn): IDbConnection {
|
||||
static $backends = [];
|
||||
|
||||
$uri = parse_url($dsn);
|
||||
if($uri === false)
|
||||
throw new InvalidArgumentException('$dsn is not a valid uri.');
|
||||
|
||||
$scheme = $uri['scheme'];
|
||||
|
||||
if(in_array($scheme, $backends))
|
||||
$backend = $backends[$scheme];
|
||||
else {
|
||||
$backend = null;
|
||||
|
||||
if(array_key_exists($scheme, self::DB_PROTOS))
|
||||
$name = self::DB_PROTOS[$scheme];
|
||||
else
|
||||
$name = str_replace('-', '\\', $scheme);
|
||||
|
||||
if(class_exists($name) && is_subclass_of($name, IDbBackend::class)) {
|
||||
$backend = new $name;
|
||||
$name = get_class($backend);
|
||||
}
|
||||
|
||||
if($backend === null)
|
||||
throw new DataException('No implementation is available for the specified scheme.');
|
||||
if(!$backend->isAvailable())
|
||||
throw new DataException('Requested database backend is not available, likely due to missing dependencies.');
|
||||
|
||||
$backends[$name] = $backend;
|
||||
}
|
||||
|
||||
return $backend->createConnection(
|
||||
$backend->parseDsn($uri)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction wrapper.
|
||||
*
|
||||
* Takes a database connection with transaction support and a callable that may return a boolean based on the success of the actions.
|
||||
* If the callable returns nothing, nothing will happen.
|
||||
* If the callable returns true, commit will be called.
|
||||
* If the callable returns false, rollback will be called.
|
||||
*
|
||||
* @param IDbTransactions $connection A database connection with transaction support.
|
||||
* @param callable $callable A callable that handles the transaction, may return a bool.
|
||||
*/
|
||||
public static function transaction(IDbTransactions $connection, callable $callable): void {
|
||||
$connection->beginTransaction();
|
||||
$result = $callable($connection) ?? null;
|
||||
if(is_bool($result)) {
|
||||
if($result)
|
||||
$connection->commit();
|
||||
else
|
||||
$connection->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the DbType of the passed argument. Should be used for DbType::AUTO.
|
||||
*
|
||||
* @param mixed $value A value of unknown type.
|
||||
* @return int DbType of the value passed in the argument.
|
||||
*/
|
||||
public static function detectType(mixed $value): int {
|
||||
if(is_null($value))
|
||||
return DbType::NULL;
|
||||
if(is_float($value))
|
||||
return DbType::FLOAT;
|
||||
if(is_int($value))
|
||||
return DbType::INTEGER;
|
||||
// ┌ should probably also check for Stringable, length should also be taken into consideration
|
||||
// ↓ though maybe with that it's better to assume that when an object is passed it'll always be Massive
|
||||
if(is_string($value))
|
||||
return DbType::STRING;
|
||||
return DbType::BLOB;
|
||||
}
|
||||
}
|
53
src/Data/DbType.php
Normal file
53
src/Data/DbType.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
// DbType.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-04
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Map of common database types.
|
||||
*/
|
||||
final class DbType {
|
||||
/**
|
||||
* Automatically detect the type. Should be used in combination with DbTools::detectType.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const AUTO = 0;
|
||||
|
||||
/**
|
||||
* Represents a NULL value. If this type is specified, the value it was associated with should be overriden with NULL.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const NULL = 1;
|
||||
|
||||
/**
|
||||
* An integer type.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const INTEGER = 2;
|
||||
|
||||
/**
|
||||
* A double precision floating point.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const FLOAT = 3;
|
||||
|
||||
/**
|
||||
* A textual string.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const STRING = 4;
|
||||
|
||||
/**
|
||||
* Binary blob data.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const BLOB = 5;
|
||||
}
|
35
src/Data/IDbBackend.php
Normal file
35
src/Data/IDbBackend.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
// IDbBackend.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2022-02-28
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Information about a database layer. Should not have any external dependencies.
|
||||
*/
|
||||
interface IDbBackend {
|
||||
/**
|
||||
* Checks whether the driver is available and a connection can be made.
|
||||
*
|
||||
* @return bool If true a connection can be made, if false a required extension is missing.
|
||||
*/
|
||||
function isAvailable(): bool;
|
||||
|
||||
/**
|
||||
* Creates a connection with the database described in the argument.
|
||||
*
|
||||
* @param IDbConnectionInfo $connectionInfo Object that describes the desired connection.
|
||||
* @throws \InvalidArgumentException An invalid implementation of IDbConnectionInfo was provided.
|
||||
* @return IDbConnection A connection described in the connection info.
|
||||
*/
|
||||
function createConnection(IDbConnectionInfo $connectionInfo): IDbConnection;
|
||||
|
||||
/**
|
||||
* Constructs a connection info instance from a dsn.
|
||||
*
|
||||
* @param string|array $dsn DSN with connection information.
|
||||
* @return IDbConnectionInfo Connection info based on the dsn.
|
||||
*/
|
||||
function parseDsn(string|array $dsn): IDbConnectionInfo;
|
||||
}
|
49
src/Data/IDbConnection.php
Normal file
49
src/Data/IDbConnection.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
// IDbConnection.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
use Index\ICloseable;
|
||||
|
||||
/**
|
||||
* Represents a connection to a database service.
|
||||
*/
|
||||
interface IDbConnection extends ICloseable {
|
||||
/**
|
||||
* Returns the ID of the last inserted row.
|
||||
*
|
||||
* @return int|string Last inserted ID.
|
||||
*/
|
||||
function getLastInsertId(): int|string;
|
||||
|
||||
/**
|
||||
* Prepares a statement for execution and returns a database statement instance.
|
||||
*
|
||||
* The statement should use question mark (?) parameters.
|
||||
*
|
||||
* @param string $query SQL query to prepare.
|
||||
* @return IDbStatement An instance of an implementation of IDbStatement.
|
||||
*/
|
||||
function prepare(string $query): IDbStatement;
|
||||
|
||||
/**
|
||||
* Executes a statement and returns a database result instance.
|
||||
*
|
||||
* @param string $query SQL query to execute.
|
||||
* @return IDbResult An instance of an implementation of IDbResult
|
||||
*/
|
||||
function query(string $query): IDbResult;
|
||||
|
||||
/**
|
||||
* Executes a statement and returns how many rows are affected.
|
||||
*
|
||||
* Does not request results back from the database and thus should have better
|
||||
* performance if the consumer doesn't care about them.
|
||||
*
|
||||
* @param string $query SQL query to execute.
|
||||
* @return int|string Number of rows affected by the query.
|
||||
*/
|
||||
function execute(string $query): int|string;
|
||||
}
|
13
src/Data/IDbConnectionInfo.php
Normal file
13
src/Data/IDbConnectionInfo.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// IDbConnectionInfo.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Base type for database connection info.
|
||||
*
|
||||
* Any database backend should have its own implementation of this, there are no baseline requirements.
|
||||
*/
|
||||
interface IDbConnectionInfo {}
|
88
src/Data/IDbResult.php
Normal file
88
src/Data/IDbResult.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
// IDbResult.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
use Index\AString;
|
||||
use Index\ICloseable;
|
||||
use Index\WString;
|
||||
use Index\IO\Stream;
|
||||
|
||||
/**
|
||||
* Represents a database result set.
|
||||
*/
|
||||
interface IDbResult extends ICloseable {
|
||||
/**
|
||||
* Fetches the next result set.
|
||||
*
|
||||
* @return bool true if the result set was loaded, false if no more results are available.
|
||||
*/
|
||||
function next(): bool;
|
||||
|
||||
/**
|
||||
* Checks if a given index has a NULL value.
|
||||
*
|
||||
* @param int|string $index Target index.
|
||||
* @return bool true if the value is null, false if not.
|
||||
*/
|
||||
function isNull(int|string $index): bool;
|
||||
|
||||
/**
|
||||
* Gets the value from the target index without any additional casting.
|
||||
*
|
||||
* @param int|string $index Target index.
|
||||
* @return mixed Target value.
|
||||
*/
|
||||
function getValue(int|string $index): mixed;
|
||||
|
||||
/**
|
||||
* Gets the value from the target index cast as a native string.
|
||||
*
|
||||
* @param int|string $index Target index.
|
||||
* @return string Returns a string of the value.
|
||||
*/
|
||||
function getString(int|string $index): string;
|
||||
|
||||
/**
|
||||
* Gets the value from the target index cast as an ASCII string.
|
||||
*
|
||||
* @param int|string $index Target index.
|
||||
* @return AString Returns an AString of the value.
|
||||
*/
|
||||
function getAString(int|string $index): AString;
|
||||
|
||||
/**
|
||||
* Gets the value from the target index cast as a multi-byte string.
|
||||
*
|
||||
* @param int|string $index Target index.
|
||||
* @param string $encoding Encoding of the string.
|
||||
* @return WString Returns an WString of the value.
|
||||
*/
|
||||
function getWString(int|string $index, string $encoding): WString;
|
||||
|
||||
/**
|
||||
* Gets the value from the target index cast as an integer.
|
||||
*
|
||||
* @param int|string $index Target index.
|
||||
* @return int Returns the value cast to an integer.
|
||||
*/
|
||||
function getInteger(int|string $index): int;
|
||||
|
||||
/**
|
||||
* Gets the value from the target index cast as a floating point number.
|
||||
*
|
||||
* @param int|string $index Target index.
|
||||
* @return float Returns the value cast to a floating point number.
|
||||
*/
|
||||
function getFloat(int|string $index): float;
|
||||
|
||||
/**
|
||||
* Gets the value from the target index as a Stream.
|
||||
*
|
||||
* @param int|string $index Target index.
|
||||
* @return ?Stream A Stream if data is available, null if not.
|
||||
*/
|
||||
function getStream(int|string $index): ?Stream;
|
||||
}
|
53
src/Data/IDbStatement.php
Normal file
53
src/Data/IDbStatement.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
// IDbStatement.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
use Index\ICloseable;
|
||||
|
||||
/**
|
||||
* Represents a prepared database statement.
|
||||
*/
|
||||
interface IDbStatement extends ICloseable {
|
||||
/**
|
||||
* Returns how many parameters there are.
|
||||
*
|
||||
* @return int Number of parameters.
|
||||
*/
|
||||
function getParameterCount(): int;
|
||||
|
||||
/**
|
||||
* Assigns a value to a parameter.
|
||||
*
|
||||
* @param int $ordinal Index of the target parameter.
|
||||
* @param mixed $value Value to assign to the parameter.
|
||||
* @param int $type Type of the value, if left to DbType::AUTO DbTools::detectType will be used on $value.
|
||||
*/
|
||||
function addParameter(int $ordinal, mixed $value, int $type = DbType::AUTO): void;
|
||||
|
||||
/**
|
||||
* Gets the result after execution.
|
||||
*
|
||||
* @return IDbResult Instance of an implementation of IDbResult.
|
||||
*/
|
||||
function getResult(): IDbResult;
|
||||
|
||||
/**
|
||||
* Returns the ID of the last inserted row.
|
||||
*
|
||||
* @return int|string Last inserted ID.
|
||||
*/
|
||||
function getLastInsertId(): int|string;
|
||||
|
||||
/**
|
||||
* Executes this statement.
|
||||
*/
|
||||
function execute(): void;
|
||||
|
||||
/**
|
||||
* Resets this statement for reuse.
|
||||
*/
|
||||
function reset(): void;
|
||||
}
|
57
src/Data/IDbTransactions.php
Normal file
57
src/Data/IDbTransactions.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
// IDbTransactions.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Indicates supports for transactions in a database connection.
|
||||
*/
|
||||
interface IDbTransactions extends IDbConnection {
|
||||
/**
|
||||
* Sets whether changes should be applied immediately or whether commit should always be called first.
|
||||
*
|
||||
* @param bool $state true if things should automatically be committed, false if not.
|
||||
*/
|
||||
function setAutoCommit(bool $state): void;
|
||||
|
||||
/**
|
||||
* Enters a transaction.
|
||||
*
|
||||
* @throws BeginTransactionFailedException If the creation of the transaction failed.
|
||||
*/
|
||||
function beginTransaction(): void;
|
||||
|
||||
/**
|
||||
* Commits the actions done during a transaction and ends the transaction.
|
||||
* A new transaction will be started if auto-commit is disabled.
|
||||
*
|
||||
* @throws CommitFailedException If the commit failed.
|
||||
*/
|
||||
function commit(): void;
|
||||
|
||||
/**
|
||||
* Rolls back to the state before a transaction start or to a specified save point.
|
||||
*
|
||||
* @param ?string $name Name of the save point, null for the entire transaction.
|
||||
* @throws RollbackFailedException If rollback failed.
|
||||
*/
|
||||
function rollback(?string $name = null): void;
|
||||
|
||||
/**
|
||||
* Creates a save point in the transaction that can be rolled back to.
|
||||
*
|
||||
* @param string $name Name for the save point.
|
||||
* @throws SavePointFailedException If save point creation failed.
|
||||
*/
|
||||
function savePoint(string $name): void;
|
||||
|
||||
/**
|
||||
* Releases a save point.
|
||||
*
|
||||
* @param string $name Name of the save point.
|
||||
* @throws ReleaseSavePointFailedException If releasing the save point failed.
|
||||
*/
|
||||
function releaseSavePoint(string $name): void;
|
||||
}
|
122
src/Data/MariaDB/MariaDBBackend.php
Normal file
122
src/Data/MariaDB/MariaDBBackend.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
// MariaDBBackend.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2022-02-28
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Index\Version;
|
||||
use Index\Data\IDbBackend;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbConnectionInfo;
|
||||
use Index\Net\EndPoint;
|
||||
use Index\Net\UnixEndPoint;
|
||||
|
||||
/**
|
||||
* Information about the MariaDB/MySQL database layer.
|
||||
*/
|
||||
class MariaDBBackend implements IDbBackend {
|
||||
public function isAvailable(): bool {
|
||||
return extension_loaded('mysqli');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function intToVersion(int $version): Version {
|
||||
$sub = $version % 100;
|
||||
$version = floor($version / 100);
|
||||
$minor = $version % 100;
|
||||
$version = floor($version / 100);
|
||||
$major = $version % 100;
|
||||
return new Version($major, $minor, $sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version of the underlying client library.
|
||||
*
|
||||
* @return Version Version of the client library.
|
||||
*/
|
||||
public function getClientVersion(): Version {
|
||||
return self::intToVersion(mysqli_get_client_version());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection with a MariaDB or MySQL server.
|
||||
*
|
||||
* @param MariaDBConnectionInfo $connectionInfo Object that describes the desired connection.
|
||||
* @return MariaDBConnection A connection with a MariaDB or MySQL server.
|
||||
*/
|
||||
public function createConnection(IDbConnectionInfo $connectionInfo): IDbConnection {
|
||||
if(!($connectionInfo instanceof MariaDBConnectionInfo))
|
||||
throw new InvalidArgumentException('$connectionInfo must by of type MariaDBConnectionInfo');
|
||||
|
||||
return new MariaDBConnection($connectionInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MariaDBConnectionInfo MariaDB connection info.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): IDbConnectionInfo {
|
||||
if(is_string($dsn)) {
|
||||
$dsn = parse_url($dsn);
|
||||
if($dsn === false)
|
||||
throw new InvalidArgumentException('$dsn is not a valid uri.');
|
||||
}
|
||||
|
||||
if(!isset($dsn['host']))
|
||||
throw new InvalidArgumentException('Host is missing from DSN.');
|
||||
if(!isset($dsn['path']))
|
||||
throw new InvalidArgumentException('Path is missing from DSN.');
|
||||
|
||||
$host = $dsn['host'];
|
||||
$needsUnix = $host === ':unix';
|
||||
|
||||
if(!$needsUnix && isset($dsn['port']))
|
||||
$host .= ':' . $dsn['port'];
|
||||
|
||||
$user = $dsn['user'] ?? '';
|
||||
$pass = $dsn['pass'] ?? '';
|
||||
$endPoint = $needsUnix ? null : EndPoint::parse($host);
|
||||
$dbName = str_replace('/', '_', trim($dsn['path'], '/')); // cute for table prefixes i think
|
||||
|
||||
if(!isset($dsn['query'])) {
|
||||
$charSet = null;
|
||||
$initCommand = null;
|
||||
$keyPath = null;
|
||||
$certPath = null;
|
||||
$certAuthPath = null;
|
||||
$trustedCertsPath = null;
|
||||
$cipherAlgos = null;
|
||||
$verifyCert = false;
|
||||
$useCompression = false;
|
||||
} else {
|
||||
parse_str(str_replace('+', '%2B', $dsn['query']), $query);
|
||||
|
||||
$unixPath = $query['socket'] ?? null;
|
||||
$charSet = $query['charset'] ?? null;
|
||||
$initCommand = $query['init'] ?? null;
|
||||
$keyPath = $query['enc_key'] ?? null;
|
||||
$certPath = $query['enc_cert'] ?? null;
|
||||
$certAuthPath = $query['enc_authority'] ?? null;
|
||||
$trustedCertsPath = $query['enc_trusted_certs'] ?? null;
|
||||
$cipherAlgos = $query['enc_ciphers'] ?? null;
|
||||
$verifyCert = !empty($query['enc_verify']);
|
||||
$useCompression = !empty($query['compress']);
|
||||
}
|
||||
|
||||
if($needsUnix) {
|
||||
if($unixPath === null)
|
||||
throw new InvalidArgumentException('Unix socket path is missing from DSN.');
|
||||
$endPoint = new UnixEndPoint($unixPath);
|
||||
}
|
||||
|
||||
return new MariaDBConnectionInfo(
|
||||
$endPoint, $user, $pass, $dbName,
|
||||
$charSet, $initCommand, $keyPath, $certPath,
|
||||
$certAuthPath, $trustedCertsPath, $cipherAlgos,
|
||||
$verifyCert, $useCompression
|
||||
);
|
||||
}
|
||||
}
|
93
src/Data/MariaDB/MariaDBCharacterSetInfo.php
Normal file
93
src/Data/MariaDB/MariaDBCharacterSetInfo.php
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
// MariaDBCharacterSetInfo.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-02
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Contains information about the character set.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/mysqli.get-charset
|
||||
*/
|
||||
class MariaDBCharacterSetInfo {
|
||||
private stdClass $charSet;
|
||||
|
||||
/**
|
||||
* Creates a new character set info instance.
|
||||
*
|
||||
* @param stdClass $charSet Anonymous object containing the information.
|
||||
* @return MariaDBCharacterSetInfo Character set information class.
|
||||
*/
|
||||
public function __construct(stdClass $charSet) {
|
||||
$this->charSet = $charSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current character set.
|
||||
*
|
||||
* @return string Character set name.
|
||||
*/
|
||||
public function getCharacterSet(): string {
|
||||
return $this->charSet->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the default collation.
|
||||
*
|
||||
* @return string Default collation name.
|
||||
*/
|
||||
public function getDefaultCollation(): string {
|
||||
return $this->charSet->collation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the directory the charcter was read from.
|
||||
* May be empty for built-in character sets.
|
||||
*
|
||||
* @return string Source directory.
|
||||
*/
|
||||
public function getDirectory(): string {
|
||||
return $this->charSet->dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum character width in bytes for this character set.
|
||||
*
|
||||
* @return int Minimum character width in bytes.
|
||||
*/
|
||||
public function getMinimumWidth(): int {
|
||||
return $this->charSet->min_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum character width in bytes for this character set.
|
||||
*
|
||||
* @return int Maximum character width in bytes.
|
||||
*/
|
||||
public function getMaximumWidth(): int {
|
||||
return $this->charSet->max_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal numeric identifier for this character set.
|
||||
*
|
||||
* @return int Character set identifier.
|
||||
*/
|
||||
public function getId(): int {
|
||||
return $this->charSet->number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the character set status.
|
||||
*
|
||||
* Whatever that means. Given the (?) in the official documentation, not even they know.
|
||||
*
|
||||
* @return int Character set status.
|
||||
*/
|
||||
public function getState(): int {
|
||||
return $this->charSet->state;
|
||||
}
|
||||
}
|
401
src/Data/MariaDB/MariaDBConnection.php
Normal file
401
src/Data/MariaDB/MariaDBConnection.php
Normal file
|
@ -0,0 +1,401 @@
|
|||
<?php
|
||||
// MariaDBConnection.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use mysqli;
|
||||
use mysqli_sql_exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\AString;
|
||||
use Index\Version;
|
||||
use Index\Data\BeginTransactionFailedException;
|
||||
use Index\Data\DataException;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbTransactions;
|
||||
use Index\Data\CommitFailedException;
|
||||
use Index\Data\ConnectionFailedException;
|
||||
use Index\Data\ReleaseSavePointFailedException;
|
||||
use Index\Data\RollbackFailedException;
|
||||
use Index\Data\SavePointFailedException;
|
||||
use Index\Data\QueryExecuteException;
|
||||
|
||||
/**
|
||||
* Represents a connection with a MariaDB or MySQL database server.
|
||||
*/
|
||||
class MariaDBConnection implements IDbConnection, IDbTransactions {
|
||||
/**
|
||||
* Refresh grant tables.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const REFRESH_GRANT = MYSQLI_REFRESH_GRANT;
|
||||
|
||||
/**
|
||||
* Flushes logs, like the FLUSH LOGS; statement.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const REFRESH_LOG = MYSQLI_REFRESH_LOG;
|
||||
|
||||
/**
|
||||
* Refresh tables, like the FLUSH TABLES; statement.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const REFRESH_TABLES = MYSQLI_REFRESH_TABLES;
|
||||
|
||||
/**
|
||||
* Refreshes the hosts cache, like the FLUSH HOSTS; statement.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const REFRESH_HOSTS = MYSQLI_REFRESH_HOSTS;
|
||||
|
||||
/**
|
||||
* Refresh the status variables, like the FLUSH STATUS; statement.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const REFRESH_STATUS = MYSQLI_REFRESH_STATUS;
|
||||
|
||||
/**
|
||||
* Flushes the thread cache.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const REFRESH_THREADS = MYSQLI_REFRESH_THREADS;
|
||||
|
||||
/**
|
||||
* Resets information about the master server and restarts on slave replication servers,
|
||||
* like the RESET SLAVE; statement.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const REFRESH_SLAVE = MYSQLI_REFRESH_SLAVE;
|
||||
|
||||
/**
|
||||
* Removes binary log files listed in the binary log index and truncates the index file
|
||||
* on master replication servers, like the RESET MASTER; statement.
|
||||
*/
|
||||
public const REFRESH_MASTER = MYSQLI_REFRESH_MASTER;
|
||||
|
||||
private mysqli $connection;
|
||||
|
||||
/**
|
||||
* Creates a new instance of MariaDBConnection.
|
||||
*
|
||||
* @param MariaDBConnectionInfo $connectionInfo Information about the connection.
|
||||
* @return MariaDBConnection A new instance of MariaDBConnection.
|
||||
*/
|
||||
public function __construct(MariaDBConnectionInfo $connectionInfo) {
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
|
||||
// I'm not sure if calling "new mysqli" without arguments is equivalent to this
|
||||
// the documentation would suggest it's not and that it just pulls from the config
|
||||
// nothing suggests otherwise too.
|
||||
// The output of mysqli_init is just an object anyway so we can safely use it instead
|
||||
// but continue to use it as an object.
|
||||
$this->connection = mysqli_init();
|
||||
$this->connection->options(MYSQLI_OPT_LOCAL_INFILE, 0);
|
||||
|
||||
if($connectionInfo->hasCharacterSet())
|
||||
$this->connection->options(MYSQLI_SET_CHARSET_NAME, $connectionInfo->getCharacterSet());
|
||||
|
||||
if($connectionInfo->hasInitCommand())
|
||||
$this->connection->options(MYSQLI_INIT_COMMAND, $connectionInfo->getInitCommand());
|
||||
|
||||
$flags = $connectionInfo->shouldUseCompression() ? MYSQLI_CLIENT_COMPRESS : 0;
|
||||
|
||||
if($connectionInfo->isSecure()) {
|
||||
$flags |= MYSQLI_CLIENT_SSL;
|
||||
|
||||
if($connectionInfo->shouldVerifyCertificate())
|
||||
$this->connection->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, 1);
|
||||
else
|
||||
$flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
|
||||
|
||||
$this->connection->ssl_set(
|
||||
$connectionInfo->getKeyPath(),
|
||||
$connectionInfo->getCertificatePath(),
|
||||
$connectionInfo->getCertificateAuthorityPath(),
|
||||
$connectionInfo->getTrustedCertificatesPath(),
|
||||
$connectionInfo->getCipherAlgorithms()
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if($connectionInfo->isUnixSocket())
|
||||
$this->connection->real_connect(
|
||||
'',
|
||||
$connectionInfo->getUserName(),
|
||||
$connectionInfo->getPassword(),
|
||||
$connectionInfo->getDatabaseName(),
|
||||
-1,
|
||||
$connectionInfo->getSocketPath(),
|
||||
$flags
|
||||
);
|
||||
else
|
||||
$this->connection->real_connect(
|
||||
$connectionInfo->getHost(),
|
||||
$connectionInfo->getUserName(),
|
||||
$connectionInfo->getPassword(),
|
||||
$connectionInfo->getDatabaseName(),
|
||||
$connectionInfo->getPort(),
|
||||
'',
|
||||
$flags
|
||||
);
|
||||
} catch(mysqli_sql_exception $ex) {
|
||||
throw new ConnectionFailedException($ex->getMessage(), $ex->getCode(), $ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of rows affected by the last operation.
|
||||
*
|
||||
* @return int|string Number of rows affected by the last operation.
|
||||
*/
|
||||
public function getAffectedRows(): int|string {
|
||||
return $this->connection->affected_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the currently active character set.
|
||||
*
|
||||
* @return string Name of the character set.
|
||||
*/
|
||||
public function getCharacterSet(): string {
|
||||
return $this->connection->character_set_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different character set.
|
||||
*
|
||||
* @param string $charSet Name of the new character set.
|
||||
* @throws InvalidArgumentException Switching to new character set failed.
|
||||
*/
|
||||
public function setCharacterSet(string $charSet): void {
|
||||
if(!$this->connection->set_charset($charSet))
|
||||
throw new InvalidArgumentException('$charSet is not a supported character set.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns info about the currently character set and collation.
|
||||
*
|
||||
* @return MariaDBCharacterSetInfo Information about the character set.
|
||||
*/
|
||||
public function getCharacterSetInfo(): MariaDBCharacterSetInfo {
|
||||
return new MariaDBCharacterSetInfo($this->connection->get_charset());
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the connection to a different user and default database.
|
||||
*
|
||||
* @param string $userName New user name.
|
||||
* @param string $password New password.
|
||||
* @param string|null $database New default database.
|
||||
* @throws DataException If the user switch action failed.
|
||||
*/
|
||||
public function switchUser(string $userName, string $password, string|null $database = null): void {
|
||||
if(!$this->connection->change_user($userName, $password, $database === null ? null : $database))
|
||||
throw new DataException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the default database for this connection.
|
||||
*
|
||||
* @param string $database New default database.
|
||||
* @throws DataException If the database switch failed.
|
||||
*/
|
||||
public function switchDatabase(string $database): void {
|
||||
if(!$this->connection->select_db($database))
|
||||
throw new DataException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version of the server.
|
||||
*
|
||||
* @return Version Version of the server.
|
||||
*/
|
||||
public function getServerVersion(): Version {
|
||||
return MariaDBBackend::intToVersion($this->connection->server_version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last error code.
|
||||
*
|
||||
* @return int Last error code.
|
||||
*/
|
||||
public function getLastErrorCode(): int {
|
||||
return $this->connection->errno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last error string.
|
||||
*
|
||||
* @return string Last error string.
|
||||
*/
|
||||
public function getLastErrorString(): string {
|
||||
return $this->connection->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current SQL State of the connection.
|
||||
*
|
||||
* @return string Current SQL State.
|
||||
*/
|
||||
public function getSQLState(): string {
|
||||
return $this->connection->sqlstate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of errors from the last command.
|
||||
*
|
||||
* @return array List of last errors.
|
||||
*/
|
||||
public function getLastErrors(): array {
|
||||
return MariaDBWarning::fromLastErrors($this->connection->error_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of columns for the last query.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastFieldCount(): int {
|
||||
return $this->connection->field_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of warnings.
|
||||
*
|
||||
* The result of SHOW WARNINGS;
|
||||
*
|
||||
* @return array List of warnings.
|
||||
*/
|
||||
public function getWarnings(): array {
|
||||
return MariaDBWarning::fromGetWarnings($this->connection->get_warnings());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of warnings for the last query.
|
||||
*
|
||||
* @return int Amount of warnings.
|
||||
*/
|
||||
public function getWarningCount(): int {
|
||||
return $this->connection->warning_count;
|
||||
}
|
||||
|
||||
public function getLastInsertId(): int|string {
|
||||
return $this->connection->insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a keep alive request to the server, or tries to reconnect if it has gone down.
|
||||
*
|
||||
* @return bool true if the keep alive was successful, false if the server is Gone.
|
||||
*/
|
||||
public function ping(): bool {
|
||||
return $this->connection->ping();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs various refresh operations, see the REFRESH_ constants for the flags.
|
||||
*
|
||||
* @param int $flags A bitset of REFRESH_ flags.
|
||||
*/
|
||||
public function refresh(int $flags): void {
|
||||
$this->connection->refresh($flags);
|
||||
}
|
||||
|
||||
public function setAutoCommit(bool $state): void {
|
||||
$this->connection->autocommit($state);
|
||||
}
|
||||
|
||||
public function beginTransaction(): void {
|
||||
if(!$this->connection->begin_transaction())
|
||||
throw new BeginTransactionFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function commit(): void {
|
||||
if(!$this->connection->commit())
|
||||
throw new CommitFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function rollback(?string $name = null): void {
|
||||
if(!$this->connection->rollback(0, $name))
|
||||
throw new RollbackFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function savePoint(string $name): void {
|
||||
if(!$this->connection->savepoint($name))
|
||||
throw new SavePointFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function releaseSavePoint(string $name): void {
|
||||
if(!$this->connection->release_savepoint($name))
|
||||
throw new ReleaseSavePointFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the thread ID for the current connection.
|
||||
*
|
||||
* @return int Thread ID.
|
||||
*/
|
||||
public function getThreadId(): int {
|
||||
return $this->connection->thread_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the server to kill a thread.
|
||||
*
|
||||
* @param int $threadId ID of the thread that should be killed.
|
||||
* @return bool true if the thread was killed, false if not.
|
||||
*/
|
||||
public function killThread(int $threadId): bool {
|
||||
return $this->connection->kill($threadId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MariaDBStatement A database statement.
|
||||
*/
|
||||
public function prepare(string $query): MariaDBStatement {
|
||||
$statement = $this->connection->prepare($query);
|
||||
if($statement === false)
|
||||
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
return new MariaDBStatement($statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MariaDBResult A database result.
|
||||
*/
|
||||
public function query(string $query): MariaDBResult {
|
||||
$result = $this->connection->query($query, MYSQLI_STORE_RESULT);
|
||||
if($result === false)
|
||||
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
// Yes, this always uses Native, for some reason the stupid limitation in libmysql only applies to preparing
|
||||
return new MariaDBResultNative($result);
|
||||
}
|
||||
|
||||
public function execute(string $query): int|string {
|
||||
if(!$this->connection->real_query($query))
|
||||
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
return $this->connection->affected_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection and associated resources.
|
||||
*/
|
||||
public function close(): void {
|
||||
$this->connection->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection and associated resources.
|
||||
*/
|
||||
public function __destruct() {
|
||||
$this->close();
|
||||
}
|
||||
}
|
438
src/Data/MariaDB/MariaDBConnectionInfo.php
Normal file
438
src/Data/MariaDB/MariaDBConnectionInfo.php
Normal file
|
@ -0,0 +1,438 @@
|
|||
<?php
|
||||
// MariaDBConnectionInfo.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2022-02-28
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Index\Data\IDbConnectionInfo;
|
||||
use Index\Net\EndPoint;
|
||||
use Index\Net\DnsEndPoint;
|
||||
use Index\Net\IPAddress;
|
||||
use Index\Net\IPEndPoint;
|
||||
use Index\Net\UnixEndPoint;
|
||||
|
||||
/**
|
||||
* Describes a MariaDB or MySQL connection.
|
||||
*/
|
||||
class MariaDBConnectionInfo implements IDbConnectionInfo {
|
||||
private EndPoint $endPoint;
|
||||
private string $userName;
|
||||
private string $password;
|
||||
private string $dbName;
|
||||
private ?string $charSet;
|
||||
private ?string $initCommand;
|
||||
private ?string $keyPath;
|
||||
private ?string $certPath;
|
||||
private ?string $certAuthPath;
|
||||
private ?string $trustedCertsPath;
|
||||
private ?string $cipherAlgos;
|
||||
private bool $verifyCert;
|
||||
private bool $useCompression;
|
||||
|
||||
/**
|
||||
* Creates an instance of MariaDBConnectionInfo.
|
||||
*
|
||||
* @param EndPoint $endPoint End point at which the server can be found.
|
||||
* @param string $userName User name to use for the connection.
|
||||
* @param string $password Password with which the user authenticates.
|
||||
* @param string $dbName Default database name.
|
||||
* @param string|null $charSet Default character set.
|
||||
* @param string|null $initCommand Command to execute on connect.
|
||||
* @param string|null $keyPath Path at which to find the private key for SSL.
|
||||
* @param string|null $certPath Path at which to find the certificate file for SSL.
|
||||
* @param string|null $certAuthPath Path at which to find the certificate authority file for SSL.
|
||||
* @param string|null $trustedCertsPath Path at which to find the trusted SSL CA certificates for SSL in PEM format.
|
||||
* @param string|null $cipherAlgos List of SSL encryption cipher that are allowed.
|
||||
* @param bool $verifyCert true if the client should verify the server's SSL certificate, false if not.
|
||||
* @param bool $useCompression true if compression should be used, false if not.
|
||||
* @return MariaDBConnectionInfo A connection info instance representing the given information.
|
||||
*/
|
||||
public function __construct(
|
||||
EndPoint $endPoint,
|
||||
string $userName,
|
||||
string $password,
|
||||
string $dbName,
|
||||
string|null $charSet,
|
||||
string|null $initCommand,
|
||||
string|null $keyPath,
|
||||
string|null $certPath,
|
||||
string|null $certAuthPath,
|
||||
string|null $trustedCertsPath,
|
||||
string|null $cipherAlgos,
|
||||
bool $verifyCert,
|
||||
bool $useCompression
|
||||
) {
|
||||
if(!($endPoint instanceof IPEndPoint)
|
||||
&& !($endPoint instanceof DnsEndPoint)
|
||||
&& !($endPoint instanceof UnixEndPoint))
|
||||
throw new InvalidArgumentException('$endPoint must be of type IPEndPoint, DnsEndPoint or UnixEndPoint.');
|
||||
$this->endPoint = $endPoint;
|
||||
$this->userName = $userName;
|
||||
$this->password = $password;
|
||||
$this->dbName = $dbName;
|
||||
$this->charSet = empty($charSet) ? null : $charSet;
|
||||
$this->initCommand = empty($initCommand) ? null : $initCommand;
|
||||
$this->keyPath = empty($keyPath) ? null : $keyPath;
|
||||
$this->certPath = empty($certPath) ? null : $certPath;
|
||||
$this->certAuthPath = empty($certAuthPath) ? null : $certAuthPath;
|
||||
$this->trustedCertsPath = empty($trustedCertsPath) ? null : $trustedCertsPath;
|
||||
$this->cipherAlgos = empty($cipherAlgos) ? null : $cipherAlgos;
|
||||
$this->verifyCert = $verifyCert;
|
||||
$this->useCompression = $useCompression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the end point value.
|
||||
*
|
||||
* @return EndPoint An instance of EndPoint.
|
||||
*/
|
||||
public function getEndPoint(): EndPoint {
|
||||
return $this->endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the end point is a unix socket path.
|
||||
*
|
||||
* @return bool true if the end point is a unix socket, false if not.
|
||||
*/
|
||||
public function isUnixSocket(): bool {
|
||||
return $this->endPoint instanceof UnixEndPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the host value of the end point.
|
||||
*
|
||||
* Will return an empty string if the end point is a unix socket path.
|
||||
*
|
||||
* @return string Target host name.
|
||||
*/
|
||||
public function getHost(): string {
|
||||
if($this->endPoint instanceof DnsEndPoint)
|
||||
return $this->endPoint->getHost();
|
||||
if($this->endPoint instanceof IPEndPoint)
|
||||
return $this->endPoint->getAddress()->getCleanAddress();
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port value of the end point.
|
||||
*
|
||||
* Will return -1 if the end point is a unix socket path.
|
||||
*
|
||||
* @return int Target port number.
|
||||
*/
|
||||
public function getPort(): int {
|
||||
if(($this->endPoint instanceof DnsEndPoint) || ($this->endPoint instanceof IPEndPoint)) {
|
||||
$port = $this->endPoint->getPort();
|
||||
return $port < 1 ? 3306 : $port;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unix socket path.
|
||||
*
|
||||
* Will return an empty string if the end point is not a unix socket path.
|
||||
*
|
||||
* @return string Target unix socket path.
|
||||
*/
|
||||
public function getSocketPath(): string {
|
||||
if($this->endPoint instanceof UnixEndPoint)
|
||||
return $this->endPoint->getPath();
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection user name.
|
||||
*
|
||||
* @return string Connection user name.
|
||||
*/
|
||||
public function getUserName(): string {
|
||||
return $this->userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection user password.
|
||||
*
|
||||
* @return string Connection user password.
|
||||
*/
|
||||
public function getPassword(): string {
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection default database name.
|
||||
*
|
||||
* @return string Default database name.
|
||||
*/
|
||||
public function getDatabaseName(): string {
|
||||
return $this->dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this connection info carries a default character set name.
|
||||
*
|
||||
* @return bool true if it has a character set name, false if not.
|
||||
*/
|
||||
public function hasCharacterSet(): bool {
|
||||
return $this->charSet !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default character set name.
|
||||
*
|
||||
* @return ?string Default character set name.
|
||||
*/
|
||||
public function getCharacterSet(): ?string {
|
||||
return $this->charSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this connection info carries an initialisation command.
|
||||
*
|
||||
* @return bool true if it has an initialisation command, false if not.
|
||||
*/
|
||||
public function hasInitCommand(): bool {
|
||||
return $this->initCommand !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initialisation command.
|
||||
*
|
||||
* @return ?string Initialisation command.
|
||||
*/
|
||||
public function getInitCommand(): ?string {
|
||||
return $this->initCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether SSL is to be used with the connection.
|
||||
*
|
||||
* @return bool true if SSL should be used, false if not.
|
||||
*/
|
||||
public function isSecure(): bool {
|
||||
return $this->keyPath !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the private key to use for this connection.
|
||||
*
|
||||
* @return ?string Private key path.
|
||||
*/
|
||||
public function getKeyPath(): ?string {
|
||||
return $this->keyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the certificate to use for this connection.
|
||||
*
|
||||
* @return ?string Certificate path.
|
||||
*/
|
||||
public function getCertificatePath(): ?string {
|
||||
return $this->certPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the certificate authority file to use for this connection.
|
||||
*
|
||||
* @return ?string Certificate authority file path.
|
||||
*/
|
||||
public function getCertificateAuthorityPath(): ?string {
|
||||
return $this->certAuthPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the trusted CA certificates directory for this connection.
|
||||
*
|
||||
* @return ?string Trusted CA certificates directory path.
|
||||
*/
|
||||
public function getTrustedCertificatesPath(): ?string {
|
||||
return $this->trustedCertsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of allowed SSL cipher algorithms.
|
||||
*
|
||||
* @return ?string Allowed SSL cipher algorithms.
|
||||
*/
|
||||
public function getCipherAlgorithms(): ?string {
|
||||
return $this->cipherAlgos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the client should verify the validity of the server's certificate.
|
||||
*
|
||||
* @return bool true if the certificate should be verified, false if not.
|
||||
*/
|
||||
public function shouldVerifyCertificate(): bool {
|
||||
return $this->verifyCert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether compression should be applied on the connection.
|
||||
*
|
||||
* Only makes sense for remote connections, don't use this locally...
|
||||
*
|
||||
* @return bool true if compression should be used, false if not.
|
||||
*/
|
||||
public function shouldUseCompression(): bool {
|
||||
return $this->useCompression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection info instance with a hostname and port number.
|
||||
*
|
||||
* @param string $host Server host name.
|
||||
* @param int $port Server port.
|
||||
* @param string $userName User name to use for the connection.
|
||||
* @param string $password Password with which the user authenticates.
|
||||
* @param string $dbName Default database name.
|
||||
* @param string|null $charSet Default character set.
|
||||
* @param string|null $initCommand Command to execute on connect.
|
||||
* @param string|null $keyPath Path at which to find the private key for SSL.
|
||||
* @param string|null $certPath Path at which to find the certificate file for SSL.
|
||||
* @param string|null $certAuthPath Path at which to find the certificate authority file for SSL.
|
||||
* @param string|null $trustedCertsPath Path at which to find the trusted SSL CA certificates for SSL in PEM format.
|
||||
* @param string|null $cipherAlgos List of SSL encryption cipher that are allowed.
|
||||
* @param bool $verifyCert true if the client should verify the server's SSL certificate, false if not.
|
||||
* @param bool $useCompression true if compression should be used, false if not.
|
||||
* @return MariaDBConnectionInfo A connection info instance representing the given information.
|
||||
*/
|
||||
public static function createHost(
|
||||
string $host,
|
||||
int $port,
|
||||
string $userName,
|
||||
string $password,
|
||||
string $dbName = '',
|
||||
string|null $charSet = null,
|
||||
string|null $initCommand = null,
|
||||
string|null $keyPath = null,
|
||||
string|null $certPath = null,
|
||||
string|null $certAuthPath = null,
|
||||
string|null $trustedCertsPath = null,
|
||||
string|null $cipherAlgos = null,
|
||||
bool $verifyCert = false,
|
||||
bool $useCompression = false
|
||||
): MariaDBConnectionInfo {
|
||||
return new MariaDBConnectionInfo(
|
||||
new DnsEndPoint($host, $port),
|
||||
$userName,
|
||||
$password,
|
||||
$dbName,
|
||||
$charSet,
|
||||
$initCommand,
|
||||
$keyPath,
|
||||
$certPath,
|
||||
$certAuthPath,
|
||||
$trustedCertsPath,
|
||||
$cipherAlgos,
|
||||
$verifyCert,
|
||||
$useCompression
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection info instance with a unix socket path.
|
||||
*
|
||||
* @param string $path Path to the unix socket.
|
||||
* @param string $userName User name to use for the connection.
|
||||
* @param string $password Password with which the user authenticates.
|
||||
* @param string $dbName Default database name.
|
||||
* @param string|null $charSet Default character set.
|
||||
* @param string|null $initCommand Command to execute on connect.
|
||||
* @param string|null $keyPath Path at which to find the private key for SSL.
|
||||
* @param string|null $certPath Path at which to find the certificate file for SSL.
|
||||
* @param string|null $certAuthPath Path at which to find the certificate authority file for SSL.
|
||||
* @param string|null $trustedCertsPath Path at which to find the trusted SSL CA certificates for SSL in PEM format.
|
||||
* @param string|null $cipherAlgos List of SSL encryption cipher that are allowed.
|
||||
* @param bool $verifyCert true if the client should verify the server's SSL certificate, false if not.
|
||||
* @param bool $useCompression true if compression should be used, false if not.
|
||||
* @return MariaDBConnectionInfo A connection info instance representing the given information.
|
||||
*/
|
||||
public static function createUnix(
|
||||
string $path,
|
||||
string $userName,
|
||||
string $password,
|
||||
string $dbName = '',
|
||||
string|null $charSet = null,
|
||||
string|null $initCommand = null,
|
||||
string|null $keyPath = null,
|
||||
string|null $certPath = null,
|
||||
string|null $certAuthPath = null,
|
||||
string|null $trustedCertsPath = null,
|
||||
string|null $cipherAlgos = null,
|
||||
bool $verifyCert = false,
|
||||
bool $useCompression = false
|
||||
): MariaDBConnectionInfo {
|
||||
return new MariaDBConnectionInfo(
|
||||
UnixEndPoint::parse($path),
|
||||
$userName,
|
||||
$password,
|
||||
$dbName,
|
||||
$charSet,
|
||||
$initCommand,
|
||||
$keyPath,
|
||||
$certPath,
|
||||
$certAuthPath,
|
||||
$trustedCertsPath,
|
||||
$cipherAlgos,
|
||||
$verifyCert,
|
||||
$useCompression
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse an end point string and creates a connection info instance using it..
|
||||
*
|
||||
* Supports IPv4, IPv6 and DNS hostnames with and without a port number and Unix socket paths prefixed with unix://
|
||||
*
|
||||
* @param string $endPoint End point string.
|
||||
* @param string $userName User name to use for the connection.
|
||||
* @param string $password Password with which the user authenticates.
|
||||
* @param string $dbName Default database name.
|
||||
* @param string|null $charSet Default character set.
|
||||
* @param string|null $initCommand Command to execute on connect.
|
||||
* @param string|null $keyPath Path at which to find the private key for SSL.
|
||||
* @param string|null $certPath Path at which to find the certificate file for SSL.
|
||||
* @param string|null $certAuthPath Path at which to find the certificate authority file for SSL.
|
||||
* @param string|null $trustedCertsPath Path at which to find the trusted SSL CA certificates for SSL in PEM format.
|
||||
* @param string|null $cipherAlgos List of SSL encryption cipher that are allowed.
|
||||
* @param bool $verifyCert true if the client should verify the server's SSL certificate, false if not.
|
||||
* @param bool $useCompression true if compression should be used, false if not.
|
||||
* @return MariaDBConnectionInfo A connection info instance representing the given information.
|
||||
*/
|
||||
public static function create(
|
||||
string $endPoint,
|
||||
string $userName,
|
||||
string $password,
|
||||
string $dbName = '',
|
||||
string|null $charSet = null,
|
||||
string|null $initCommand = null,
|
||||
string|null $keyPath = null,
|
||||
string|null $certPath = null,
|
||||
string|null $certAuthPath = null,
|
||||
string|null $trustedCertsPath = null,
|
||||
string|null $cipherAlgos = null,
|
||||
bool $verifyCert = false,
|
||||
bool $useCompression = false
|
||||
): MariaDBConnectionInfo {
|
||||
return new MariaDBConnectionInfo(
|
||||
EndPoint::parse($endPoint),
|
||||
$userName,
|
||||
$password,
|
||||
$dbName,
|
||||
$charSet,
|
||||
$initCommand,
|
||||
$keyPath,
|
||||
$certPath,
|
||||
$certAuthPath,
|
||||
$trustedCertsPath,
|
||||
$cipherAlgos,
|
||||
$verifyCert,
|
||||
$useCompression
|
||||
);
|
||||
}
|
||||
}
|
81
src/Data/MariaDB/MariaDBParameter.php
Normal file
81
src/Data/MariaDB/MariaDBParameter.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
// MariaDBParameter.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-02
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use Index\Data\DbTools;
|
||||
use Index\Data\DbType;
|
||||
|
||||
/**
|
||||
* Represents a bound parameter.
|
||||
*/
|
||||
class MariaDBParameter {
|
||||
private int $ordinal;
|
||||
private int $type;
|
||||
private mixed $value;
|
||||
|
||||
/**
|
||||
* Create a new MariaDBParameter.
|
||||
*
|
||||
* @param int $ordinal Parameter number.
|
||||
* @param int $type DbType number.
|
||||
* @param mixed $value Value to bind.
|
||||
* @return MariaDBParameter A new parameter instance.
|
||||
*/
|
||||
public function __construct(int $ordinal, int $type, mixed $value) {
|
||||
$this->ordinal = $ordinal;
|
||||
|
||||
if($type == DbType::AUTO)
|
||||
$type = DbTools::detectType($value);
|
||||
|
||||
$this->type = $type;
|
||||
$this->value = $type === DbType::NULL ? null : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parameter number.
|
||||
*
|
||||
* @return int Parameter number.
|
||||
*/
|
||||
public function getOrdinal(): int {
|
||||
return $this->ordinal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DbType of for the parameter.
|
||||
*
|
||||
* @return int DbType.
|
||||
*/
|
||||
public function getDbType(): int {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type character for the mysqli bind.
|
||||
*
|
||||
* @return string Type character.
|
||||
*/
|
||||
public function getBindType(): string {
|
||||
switch($this->type) {
|
||||
case DbType::INTEGER:
|
||||
return 'i';
|
||||
case DbType::FLOAT:
|
||||
return 'd';
|
||||
case DbType::BLOB:
|
||||
return 'b';
|
||||
default:
|
||||
return 's';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value for the parameter.
|
||||
*
|
||||
* @return mixed Value.
|
||||
*/
|
||||
public function getValue(): mixed {
|
||||
return $this->value;
|
||||
}
|
||||
}
|
103
src/Data/MariaDB/MariaDBResult.php
Normal file
103
src/Data/MariaDB/MariaDBResult.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
// MariaDBResult.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use mysqli_result;
|
||||
use mysqli_stmt;
|
||||
use InvalidArgumentException;
|
||||
use Index\AString;
|
||||
use Index\WString;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\IO\Stream;
|
||||
use Index\IO\TempFileStream;
|
||||
|
||||
/**
|
||||
* Represents a MariaDB/MySQL database result.
|
||||
*/
|
||||
abstract class MariaDBResult implements IDbResult {
|
||||
protected mysqli_stmt|mysqli_result $result;
|
||||
protected array $currentRow = [];
|
||||
|
||||
/**
|
||||
* Creates a new MariaDBResult instance.
|
||||
*
|
||||
* @param mysqli_stmt|mysqli_result $result A result to work with.
|
||||
* @return MariaDBResult A result instance.
|
||||
*/
|
||||
public function __construct(mysqli_stmt|mysqli_result $result) {
|
||||
$this->result = $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of available rows in this result.
|
||||
*
|
||||
* @return int|string Number of available rows.
|
||||
*/
|
||||
public function getRowCount(): int|string {
|
||||
return $this->result->num_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of fields in the result per row.
|
||||
*
|
||||
* @return int Number of fields.
|
||||
*/
|
||||
public function getFieldCount(): int {
|
||||
return $this->result->field_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to a specific row.
|
||||
*
|
||||
* @param int $offset Offset of the row.
|
||||
*/
|
||||
public function seekRow(int $offset): void {
|
||||
if(!$this->result->data_seek($offset))
|
||||
throw new InvalidArgumentException('$offset is not a valid row offset.');
|
||||
}
|
||||
|
||||
abstract public function next(): bool;
|
||||
|
||||
public function isNull(int|string $index): bool {
|
||||
return array_key_exists($index, $this->currentRow) && $this->currentRow[$index] === null;
|
||||
}
|
||||
|
||||
public function getValue(int|string $index): mixed {
|
||||
return $this->currentRow[$index] ?? null;
|
||||
}
|
||||
|
||||
public function getString(int|string $index): string {
|
||||
return strval($this->getValue($index));
|
||||
}
|
||||
|
||||
public function getAString(int|string $index): AString {
|
||||
return new AString($this->getString($index));
|
||||
}
|
||||
|
||||
public function getWString(int|string $index, string $encoding): WString {
|
||||
return new WString($this->getString($index), $encoding);
|
||||
}
|
||||
|
||||
public function getInteger(int|string $index): int {
|
||||
return intval($this->getValue($index));
|
||||
}
|
||||
|
||||
public function getFloat(int|string $index): float {
|
||||
return floatval($this->getValue($index));
|
||||
}
|
||||
|
||||
public function getStream(int|string $index): ?Stream {
|
||||
if($this->isNull($index))
|
||||
return null;
|
||||
return new TempFileStream($this->getValue($index));
|
||||
}
|
||||
|
||||
abstract function close(): void;
|
||||
|
||||
public function __destruct() {
|
||||
$this->close();
|
||||
}
|
||||
}
|
53
src/Data/MariaDB/MariaDBResultLib.php
Normal file
53
src/Data/MariaDB/MariaDBResultLib.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
// MariaDBResultLib.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-04
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use mysqli_stmt;
|
||||
use Index\Data\QueryExecuteException;
|
||||
|
||||
/**
|
||||
* Implementation of MariaDBResult for libmysql.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MariaDBResultLib extends MariaDBResult {
|
||||
private array $fields = [];
|
||||
private array $data = [];
|
||||
|
||||
public function __construct(mysqli_stmt $statement) {
|
||||
parent::__construct($statement);
|
||||
|
||||
if(!$statement->store_result())
|
||||
throw new QueryExecuteException($statement->error, $statement->errno);
|
||||
|
||||
$metadata = $statement->result_metadata();
|
||||
while($field = $metadata->fetch_field())
|
||||
$this->fields[] = &$this->data[$field->name];
|
||||
|
||||
call_user_func_array([$statement, 'bind_result'], $this->fields);
|
||||
}
|
||||
|
||||
public function next(): bool {
|
||||
$result = $this->result->fetch();
|
||||
if($result === false)
|
||||
throw new QueryExecuteException($this->result->error, $this->result->errno);
|
||||
if($result === null)
|
||||
return false;
|
||||
|
||||
$i = 0;
|
||||
$this->currentRow = [];
|
||||
foreach($this->data as $key => $value) {
|
||||
$this->currentRow[$i++] = $value;
|
||||
$this->currentRow[$key] = $value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
$this->result->free_result();
|
||||
}
|
||||
}
|
40
src/Data/MariaDB/MariaDBResultNative.php
Normal file
40
src/Data/MariaDB/MariaDBResultNative.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
// MariaDBResultNative.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-04
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use mysqli_result;
|
||||
use mysqli_stmt;
|
||||
use Index\Data\QueryExecuteException;
|
||||
|
||||
/**
|
||||
* Implementation of MariaDBResult for mysqlnd.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MariaDBResultNative extends MariaDBResult {
|
||||
public function __construct(mysqli_stmt|mysqli_result $result) {
|
||||
if($result instanceof mysqli_stmt) {
|
||||
$_result = $result->get_result();
|
||||
if($_result === false)
|
||||
throw new QueryExecuteException($result->error, $result->errno);
|
||||
$result = $_result;
|
||||
}
|
||||
|
||||
parent::__construct($result);
|
||||
}
|
||||
|
||||
public function next(): bool {
|
||||
$result = $this->result->fetch_array(MYSQLI_BOTH);
|
||||
if($result === null)
|
||||
return false;
|
||||
$this->currentRow = $result;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
$this->result->close();
|
||||
}
|
||||
}
|
165
src/Data/MariaDB/MariaDBStatement.php
Normal file
165
src/Data/MariaDB/MariaDBStatement.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
// MariaDBStatement.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use mysqli_stmt;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Data\DbType;
|
||||
use Index\Data\QueryExecuteException;
|
||||
use Index\Data\IDbStatement;
|
||||
use Index\IO\Stream;
|
||||
|
||||
/**
|
||||
* Represents a MariaDB/MySQL prepared statement.
|
||||
*/
|
||||
class MariaDBStatement implements IDbStatement {
|
||||
private mysqli_stmt $statement;
|
||||
private array $params = [];
|
||||
|
||||
/**
|
||||
* Creates a MariaDBStatement.
|
||||
*
|
||||
* @param mysqli_stmt $statement Underlying statement object.
|
||||
* @return MariaDBStatement A new statement instance.
|
||||
*/
|
||||
public function __construct(mysqli_stmt $statement) {
|
||||
$this->statement = $statement;
|
||||
}
|
||||
|
||||
private static bool $constructed = false;
|
||||
private static string $resultImplementation;
|
||||
|
||||
/**
|
||||
* Determines which MariaDBResult implementation should be used.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function construct(): void {
|
||||
if(self::$constructed !== false)
|
||||
throw new RuntimeException('Static constructor was already called.');
|
||||
self::$constructed = true;
|
||||
self::$resultImplementation = function_exists('mysqli_stmt_get_result')
|
||||
? MariaDBResultNative::class
|
||||
: MariaDBResultLib::class;
|
||||
}
|
||||
|
||||
public function getParameterCount(): int {
|
||||
return $this->statement->param_count;
|
||||
}
|
||||
|
||||
public function addParameter(int $ordinal, mixed $value, int $type = DbType::AUTO): void {
|
||||
if($ordinal < 1 || $ordinal > $this->getParameterCount())
|
||||
throw new InvalidArgumentException('$ordinal is not a valid parameter number.');
|
||||
$this->params[$ordinal - 1] = new MariaDBParameter($ordinal, $type, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last error code.
|
||||
*
|
||||
* @return int Last error code.
|
||||
*/
|
||||
public function getLastErrorCode(): int {
|
||||
return $this->statement->errno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last error string.
|
||||
*
|
||||
* @return string Last error string.
|
||||
*/
|
||||
public function getLastErrorString(): string {
|
||||
return $this->statement->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current SQL State of this statement.
|
||||
*
|
||||
* @return string Current SQL State.
|
||||
*/
|
||||
public function getSQLState(): string {
|
||||
return $this->statement->sqlstate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of errors from the last command.
|
||||
*
|
||||
* @return array List of last errors.
|
||||
*/
|
||||
public function getLastErrors(): array {
|
||||
return MariaDBWarning::fromLastErrors($this->statement->error_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of warnings.
|
||||
*
|
||||
* The result of SHOW WARNINGS;
|
||||
*
|
||||
* @return array List of warnings.
|
||||
*/
|
||||
public function getWarnings(): array {
|
||||
return MariaDBWarning::fromGetWarnings($this->statement->get_warnings());
|
||||
}
|
||||
|
||||
public function getResult(): MariaDBResult {
|
||||
return new self::$resultImplementation($this->statement);
|
||||
}
|
||||
|
||||
public function getLastInsertId(): int|string {
|
||||
return $this->statement->insert_id;
|
||||
}
|
||||
|
||||
public function execute(): void {
|
||||
$args = [''];
|
||||
|
||||
foreach($this->params as $key => $param) {
|
||||
$args[0] .= $param->getBindType();
|
||||
$type = $param->getDbType();
|
||||
$value = $param->getValue();
|
||||
|
||||
if($type === DbType::NULL)
|
||||
$value = null;
|
||||
elseif($type === DbType::BLOB) {
|
||||
if($value instanceof Stream) {
|
||||
while(($data = $value->read(8192)) !== null)
|
||||
$this->statement->send_long_data($key, $data);
|
||||
} elseif(is_resource($value)) {
|
||||
while($data = fread($value, 8192))
|
||||
$this->statement->send_long_data($key, $data);
|
||||
} else
|
||||
$this->statement->send_long_data($key, strval($value));
|
||||
|
||||
$value = null;
|
||||
}
|
||||
|
||||
|
||||
${"value{$key}"} = $value;
|
||||
$args[] = &${"value{$key}"};
|
||||
}
|
||||
|
||||
if(!empty($args[0]))
|
||||
call_user_func_array([$this->statement, 'bind_param'], $args);
|
||||
|
||||
if(!$this->statement->execute())
|
||||
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function reset(): void {
|
||||
$this->params = [];
|
||||
if(!$this->statement->reset())
|
||||
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
$this->statement->close();
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
MariaDBStatement::construct();
|
95
src/Data/MariaDB/MariaDBWarning.php
Normal file
95
src/Data/MariaDB/MariaDBWarning.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
// MariaDBWarning.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Data\MariaDB;
|
||||
|
||||
use mysqli_warning;
|
||||
use Stringable;
|
||||
use Index\XArray;
|
||||
|
||||
/**
|
||||
* Represents a MariaDB/MySQL warning.
|
||||
*/
|
||||
class MariaDBWarning implements Stringable {
|
||||
private string $message;
|
||||
private string $sqlState;
|
||||
private int $code;
|
||||
|
||||
/**
|
||||
* Creates a new MariaDBWarning object.
|
||||
*
|
||||
* @param string $message Warning message string.
|
||||
* @param string $sqlState SQL State of the warning.
|
||||
* @param int $code Error code of the warning.
|
||||
* @return MariaDBWarning A new warning instance.
|
||||
*/
|
||||
public function __construct(string $message, string $sqlState, int $code) {
|
||||
$this->message = $message;
|
||||
$this->sqlState = $sqlState;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the warning message string.
|
||||
*
|
||||
* @return string Warning message string.
|
||||
*/
|
||||
public function getMessage(): string {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the warning SQL State.
|
||||
*
|
||||
* @return string SQL State.
|
||||
*/
|
||||
public function getSQLState(): string {
|
||||
return $this->sqlState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the warning error code.
|
||||
*
|
||||
* @return int Warning error code.
|
||||
*/
|
||||
public function getCode(): int {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return sprintf('[%s] %d: %s', $this->sqlState, $this->code, $this->message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of warning objects from the output of $last_errors.
|
||||
*
|
||||
* @param array $errors Array of warning arrays.
|
||||
* @return array Array of warnings objects.
|
||||
*/
|
||||
public static function fromLastErrors(array $errors): array {
|
||||
return XArray::select($errors, fn($error) => new MariaDBWarning($error['error'], $error['sqlstate'], $error['errno']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of warning objects from a mysqli_warning instance.
|
||||
*
|
||||
* @param mysqli_warning|false $warnings Warning object.
|
||||
* @return array Array of warning objects.
|
||||
*/
|
||||
public static function fromGetWarnings(mysqli_warning|false $warnings): array {
|
||||
$array = [];
|
||||
|
||||
if($warnings !== false)
|
||||
do {
|
||||
$array[] = new MariaDBWarning(
|
||||
$warnings->message,
|
||||
$warnings->sqlstate,
|
||||
$warnings->errno
|
||||
);
|
||||
} while($warnings->next());
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
36
src/Data/NullDb/NullDbBackend.php
Normal file
36
src/Data/NullDb/NullDbBackend.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
// NullDbBackend.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-28
|
||||
|
||||
namespace Index\Data\NullDb;
|
||||
|
||||
use Index\Data\IDbBackend;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbConnectionInfo;
|
||||
|
||||
/**
|
||||
* Information about the dummy database layer.
|
||||
*/
|
||||
class NullDbBackend implements IDbBackend {
|
||||
public function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dummy database connection.
|
||||
*
|
||||
* @param NullDbConnectionInfo $connectionInfo Dummy connection info.
|
||||
* @return NullDbConnection Dummy connection instance.
|
||||
*/
|
||||
public function createConnection(IDbConnectionInfo $connectionInfo): IDbConnection {
|
||||
return new NullDbConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NullDbConnectionInfo Dummy connection info instance.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): IDbConnectionInfo {
|
||||
return new NullDbConnectionInfo;
|
||||
}
|
||||
}
|
33
src/Data/NullDb/NullDbConnection.php
Normal file
33
src/Data/NullDb/NullDbConnection.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
// NullDbConnection.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Data\NullDb;
|
||||
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbStatement;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
/**
|
||||
* Represents a dummy database connection.
|
||||
*/
|
||||
class NullDbConnection implements IDbConnection {
|
||||
public function getLastInsertId(): int|string {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function prepare(string $query): IDbStatement {
|
||||
return new NullDbStatement;
|
||||
}
|
||||
|
||||
public function query(string $query): IDbResult {
|
||||
return new NullDbResult;
|
||||
}
|
||||
|
||||
public function execute(string $query): int|string {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
13
src/Data/NullDb/NullDbConnectionInfo.php
Normal file
13
src/Data/NullDb/NullDbConnectionInfo.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// NullDbConnectionInfo.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data\NullDb;
|
||||
|
||||
use Index\Data\IDbConnectionInfo;
|
||||
|
||||
/**
|
||||
* Represents dummy connection info.
|
||||
*/
|
||||
class NullDbConnectionInfo implements IDbConnectionInfo {}
|
54
src/Data/NullDb/NullDbResult.php
Normal file
54
src/Data/NullDb/NullDbResult.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
// NullDbResult.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data\NullDb;
|
||||
|
||||
use Index\AString;
|
||||
use Index\WString;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\IO\Stream;
|
||||
|
||||
/**
|
||||
* Represents a dummy database result.
|
||||
*/
|
||||
class NullDbResult implements IDbResult {
|
||||
public function next(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isNull(int|string $index): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getValue(int|string $index): mixed {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getString(int|string $index): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getAString(int|string $index): AString {
|
||||
return AString::empty();
|
||||
}
|
||||
|
||||
public function getWString(int|string $index, string $encoding): WString {
|
||||
return WString::empty();
|
||||
}
|
||||
|
||||
public function getInteger(int|string $index): int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getFloat(int|string $index): float {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public function getStream(int|string $index): ?Stream {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
35
src/Data/NullDb/NullDbStatement.php
Normal file
35
src/Data/NullDb/NullDbStatement.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
// NullDbStatement.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data\NullDb;
|
||||
|
||||
use Index\Data\DbType;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\Data\IDbStatement;
|
||||
|
||||
/**
|
||||
* Represents a dummy database statement.
|
||||
*/
|
||||
class NullDbStatement implements IDbStatement {
|
||||
public function getParameterCount(): int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function addParameter(int $ordinal, mixed $value, int $type = DbType::AUTO): void {}
|
||||
|
||||
public function getResult(): IDbResult {
|
||||
return new NullDbResult;
|
||||
}
|
||||
|
||||
public function getLastInsertId(): int|string {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function execute(): void {}
|
||||
|
||||
public function reset(): void {}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
11
src/Data/QueryExecuteException.php
Normal file
11
src/Data/QueryExecuteException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// QueryExecuteException.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when query execution fails.
|
||||
*/
|
||||
class QueryExecuteException extends DataException {}
|
11
src/Data/ReleaseSavePointFailedException.php
Normal file
11
src/Data/ReleaseSavePointFailedException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// ReleaseSavePointFailedException.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when save point release fails.
|
||||
*/
|
||||
class ReleaseSavePointFailedException extends TransactionException {}
|
11
src/Data/RollbackFailedException.php
Normal file
11
src/Data/RollbackFailedException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// RollbackFailedException.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when transaction rollback fails.
|
||||
*/
|
||||
class RollbackFailedException extends TransactionException {}
|
74
src/Data/SQLite/SQLiteBackend.php
Normal file
74
src/Data/SQLite/SQLiteBackend.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
// SQLiteBackend.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-28
|
||||
|
||||
namespace Index\Data\SQLite;
|
||||
|
||||
use SQLite3;
|
||||
use InvalidArgumentException;
|
||||
use Index\Version;
|
||||
use Index\Data\IDbBackend;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbConnectionInfo;
|
||||
|
||||
/**
|
||||
* Information about the SQLite 3 database layer.
|
||||
*/
|
||||
class SQLiteBackend implements IDbBackend {
|
||||
public function isAvailable(): bool {
|
||||
return extension_loaded('sqlite3');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version of the underlying library.
|
||||
*
|
||||
* @return Version Version of the library.
|
||||
*/
|
||||
public function getVersion(): Version {
|
||||
return Version::parse(SQLite3::version()['versionString']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SQLite client.
|
||||
*
|
||||
* @param SQLiteConnectionInfo $connectionInfo Object that describes the desired connection.
|
||||
* @return SQLiteConnection A client for an SQLite database.
|
||||
*/
|
||||
public function createConnection(IDbConnectionInfo $connectionInfo): IDbConnection {
|
||||
if(!($connectionInfo instanceof SQLiteConnectionInfo))
|
||||
throw new InvalidArgumentException('$connectionInfo must by of type SQLiteConnectionInfo');
|
||||
|
||||
return new SQLiteConnection($connectionInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SQLiteConnectionInfo SQLite connection info.
|
||||
*/
|
||||
public function parseDsn(string|array $dsn): IDbConnectionInfo {
|
||||
if(is_string($dsn)) {
|
||||
$dsn = parse_url($dsn);
|
||||
if($dsn === false)
|
||||
throw new InvalidArgumentException('$dsn is not a valid uri.');
|
||||
}
|
||||
|
||||
$path = $dsn['path'] ?? '';
|
||||
|
||||
if($path === 'memory:')
|
||||
$path = ':memory:';
|
||||
|
||||
if(!isset($dsn['query'])) {
|
||||
$encKey = '';
|
||||
$readOnly = false;
|
||||
$create = true;
|
||||
} else {
|
||||
parse_str(str_replace('+', '%2B', $dsn['query']), $query);
|
||||
|
||||
$encKey = $query['key'] ?? '';
|
||||
$readOnly = !empty($query['readOnly']);
|
||||
$create = empty($query['openOnly']);
|
||||
}
|
||||
|
||||
return new SQLiteConnectionInfo($path, $encKey, $readOnly, $create);
|
||||
}
|
||||
}
|
569
src/Data/SQLite/SQLiteConnection.php
Normal file
569
src/Data/SQLite/SQLiteConnection.php
Normal file
|
@ -0,0 +1,569 @@
|
|||
<?php
|
||||
// SQLiteConnection.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Data\SQLite;
|
||||
|
||||
use SQLite3;
|
||||
use Index\Data\BeginTransactionFailedException;
|
||||
use Index\Data\DataException;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbTransactions;
|
||||
use Index\Data\IDbStatement;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\Data\CommitFailedException;
|
||||
use Index\Data\ReleaseSavePointFailedException;
|
||||
use Index\Data\RollbackFailedException;
|
||||
use Index\Data\SavePointFailedException;
|
||||
use Index\Data\QueryExecuteException;
|
||||
use Index\IO\GenericStream;
|
||||
use Index\IO\Stream;
|
||||
|
||||
/**
|
||||
* Represents a client for an SQLite database.
|
||||
*/
|
||||
class SQLiteConnection implements IDbConnection, IDbTransactions {
|
||||
/**
|
||||
* CREATE INDEX authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the index.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_INDEX = SQLite3::CREATE_INDEX;
|
||||
|
||||
/**
|
||||
* CREATE TABLE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_TABLE = SQLite3::CREATE_TABLE;
|
||||
|
||||
/**
|
||||
* CREATE TEMP INDEX authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the index.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_TEMP_INDEX = SQLite3::CREATE_TEMP_INDEX;
|
||||
|
||||
/**
|
||||
* CREATE TEMP TABLE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_TEMP_TABLE = SQLite3::CREATE_TEMP_TABLE;
|
||||
|
||||
/**
|
||||
* CREATE TEMP TRIGGER authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the trigger.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_TEMP_TRIGGER = SQLite3::CREATE_TEMP_TRIGGER;
|
||||
|
||||
/**
|
||||
* CREATE TEMP VIEW authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the view.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_TEMP_VIEW = SQLite3::CREATE_TEMP_VIEW;
|
||||
|
||||
/**
|
||||
* CREATE TRIGGER authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the trigger.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_TRIGGER = SQLite3::CREATE_TRIGGER;
|
||||
|
||||
/**
|
||||
* CREATE VIEW authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the view.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_VIEW = SQLite3::CREATE_VIEW;
|
||||
|
||||
/**
|
||||
* DELETE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DELETE = SQLite3::DELETE;
|
||||
|
||||
/**
|
||||
* DROP INDEX authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the index.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_INDEX = SQLite3::DROP_INDEX;
|
||||
|
||||
/**
|
||||
* DROP TABLE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_TABLE = SQLite3::DROP_TABLE;
|
||||
|
||||
/**
|
||||
* DROP TEMP INDEX authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the index.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_TEMP_INDEX = SQLite3::DROP_TEMP_INDEX;
|
||||
|
||||
/**
|
||||
* DROP TEMP TABLE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_TEMP_TABLE = SQLite3::DROP_TEMP_TABLE;
|
||||
|
||||
/**
|
||||
* DROP TEMP TRIGGER authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the trigger.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_TEMP_TRIGGER = SQLite3::DROP_TEMP_TRIGGER;
|
||||
|
||||
/**
|
||||
* DROP TEMP VIEW authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the view.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_TEMP_VIEW = SQLite3::DROP_TEMP_VIEW;
|
||||
|
||||
/**
|
||||
* DROP TRIGGER authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the trigger.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_TRIGGER = SQLite3::DROP_TRIGGER;
|
||||
|
||||
/**
|
||||
* DROP VIEW authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the view.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_VIEW = SQLite3::DROP_VIEW;
|
||||
|
||||
/**
|
||||
* INSERT authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const INSERT = SQLite3::INSERT;
|
||||
|
||||
/**
|
||||
* PRAGMA authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the pragma.
|
||||
* Third parameter may be the first argument passed to the pragma.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const PRAGMA = SQLite3::PRAGMA;
|
||||
|
||||
/**
|
||||
* READ authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
* Third parameter will be the name of the column.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const READ = SQLite3::READ;
|
||||
|
||||
/**
|
||||
* SELECT authorizer.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const SELECT = SQLite3::SELECT;
|
||||
|
||||
/**
|
||||
* TRANSACTION authorizer.
|
||||
*
|
||||
* Second parameter will be the operation.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const TRANSACTION = SQLite3::TRANSACTION;
|
||||
|
||||
/**
|
||||
* UPDATE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
* Third parameter will be the name of the column.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const UPDATE = SQLite3::UPDATE;
|
||||
|
||||
/**
|
||||
* ATTACH authorizer.
|
||||
*
|
||||
* Second parameter will be the filename.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const ATTACH = SQLite3::ATTACH;
|
||||
|
||||
/**
|
||||
* DETACH authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the database.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DETACH = SQLite3::DETACH;
|
||||
|
||||
/**
|
||||
* ALTER TABLE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the database.
|
||||
* Third parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const ALTER_TABLE = SQLite3::ALTER_TABLE;
|
||||
|
||||
/**
|
||||
* REINDEX authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the index.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const REINDEX = SQLite3::REINDEX;
|
||||
|
||||
/**
|
||||
* ANALYZE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const ANALYZE = SQLite3::ANALYZE;
|
||||
|
||||
/**
|
||||
* CREATE VTABLE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
* Third parameter will be the name of the module.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const CREATE_VTABLE = SQLite3::CREATE_VTABLE;
|
||||
|
||||
/**
|
||||
* DROP VTABLE authorizer.
|
||||
*
|
||||
* Second parameter will be the name of the table.
|
||||
* Third parameter will be the name of the module.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DROP_VTABLE = SQLite3::DROP_VTABLE;
|
||||
|
||||
/**
|
||||
* FUNCTION authorizer.
|
||||
*
|
||||
* Third parameter will be the name of the function.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const FUNCTION = SQLite3::FUNCTION;
|
||||
|
||||
/**
|
||||
* SAVEPOINT authorizer.
|
||||
*
|
||||
* Second parameter will be the operation.
|
||||
* Third parameter will be the name of the savepoint.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const SAVEPOINT = SQLite3::SAVEPOINT;
|
||||
|
||||
/**
|
||||
* RECURSIVE authorizer.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const RECURSIVE = SQLite3::RECURSIVE;
|
||||
|
||||
/**
|
||||
* DETERMINISTIC flag for SQLite registered functions.
|
||||
*
|
||||
* When specified the function will always return the same result given the same inputs within a single SQL statement.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DETERMINISTIC = SQLITE3_DETERMINISTIC;
|
||||
|
||||
/**
|
||||
* OK authorization status.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const OK = SQLite3::OK;
|
||||
|
||||
/**
|
||||
* DENY authorization status.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const DENY = SQLite3::DENY;
|
||||
|
||||
/**
|
||||
* IGNORE authorization status.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const IGNORE = SQLite3::IGNORE;
|
||||
|
||||
private SQLite3 $connection;
|
||||
private bool $autoCommit = true;
|
||||
|
||||
/**
|
||||
* Creates a new SQLite client.
|
||||
*
|
||||
* @param SQLiteConnectionInfo $connectionInfo Information about the client.
|
||||
* @return SQLiteConnection A new SQLite client.
|
||||
*/
|
||||
public function __construct(SQLiteConnectionInfo $connectionInfo) {
|
||||
$flags = 0;
|
||||
if($connectionInfo->shouldReadOnly())
|
||||
$flags |= SQLITE3_OPEN_READONLY;
|
||||
else
|
||||
$flags |= SQLITE3_OPEN_READWRITE;
|
||||
|
||||
if($connectionInfo->shouldCreate())
|
||||
$flags |= SQLITE3_OPEN_CREATE;
|
||||
|
||||
$this->connection = new SQLite3(
|
||||
$connectionInfo->getFileName(),
|
||||
$flags,
|
||||
$connectionInfo->getEncryptionKey()
|
||||
);
|
||||
$this->connection->enableExceptions(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of rows affected by the last operation.
|
||||
*
|
||||
* @return int|string Number of rows affected by the last operation.
|
||||
*/
|
||||
public function getAffectedRows(): int|string {
|
||||
return $this->connection->changes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a PHP function for use as an SQL aggregate function.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/sqlite3.createaggregate.php
|
||||
* @param string $name Name of the SQL aggregate.
|
||||
* @param callable $stepCallback Callback function to be called for each row of the result set.
|
||||
* Definition: step(mixed $context, int $rowNumber, mixed $value, mixed ...$values): mixed
|
||||
* @param callable $finalCallback Callback function to be called to finalize the aggregate operation and return the value.
|
||||
* Definition: fini(mixed $content, int $rowNumber): mixed
|
||||
* @param int $argCount Number of arguments this aggregate function takes, -1 for any amount.
|
||||
* @return bool true if the aggregate function was created successfully, false if not.
|
||||
*/
|
||||
public function createAggregate(string $name, callable $stepCallback, callable $finalCallback, int $argCount = -1): bool {
|
||||
return $this->connection->createAggregate($name, $stepCallback, $finalCallback, $argCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a PHP function for use as an SQL collating function.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/sqlite3.createcollation.php
|
||||
* @param string $name Name of the SQL collating function.
|
||||
* @param callable $callback Callback function that returns -1, 0 or 1 to indicate sort order.
|
||||
* Definition: collation(mixed $value1, mixed $value2): int
|
||||
* @return bool true if the collating function was registered, false if not.
|
||||
*/
|
||||
public function createCollation(string $name, callable $callback): bool {
|
||||
return $this->connection->createCollation($name, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a PHP function for use as an SQL scalar function.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/sqlite3.createfunction.php
|
||||
* @param string $name Name of the SQL function.
|
||||
* @param callable $callback Callback function to register.
|
||||
* Definition: callback(mixed $value, mixed ...$values): mixed
|
||||
* @param int $argCount Number of arguments this aggregate function takes, -1 for any amount.
|
||||
* @param int $flags A bitset of flags.
|
||||
*/
|
||||
public function createFunction(string $name, callable $callback, int $argCount = -1, int $flags = 0): bool {
|
||||
return $this->connection->createFunction($name, $callback, $argCount, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a callback to be used as an authorizer to limit what statements can do.
|
||||
*
|
||||
* @param callable|null $callback An authorizer callback or null to remove the previous authorizer.
|
||||
* Definition: authorizer(int $actionCode, mixed $param2, mixed $param3, string $database, mixed $trigger): int
|
||||
* $param2 and $param3 differ based on $actionCode.
|
||||
* Must return one of the OK, DENY or IGNORE constants.
|
||||
* @return bool true if the action was successful, false if not.
|
||||
*/
|
||||
public function setAuthorizer(callable|null $callback): bool {
|
||||
return $this->connection->setAuthorizer($callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last error string.
|
||||
*
|
||||
* @return string Last error string.
|
||||
*/
|
||||
public function getLastErrorString(): string {
|
||||
return $this->connection->lastErrorMsg();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last error code.
|
||||
*
|
||||
* @return int Last error code.
|
||||
*/
|
||||
public function getLastErrorCode(): int {
|
||||
return $this->connection->lastErrorCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a BLOB field as a Stream.
|
||||
*
|
||||
* To be used instead of getStream on SQLiteResult.
|
||||
*
|
||||
* @param string $table Name of the source table.
|
||||
* @param string $column Name of the source column.
|
||||
* @param int $rowId ID of the source row.
|
||||
* @throws DataException If opening the BLOB failed.
|
||||
* @return Stream BLOB field as a Stream.
|
||||
*/
|
||||
public function getBlobStream(string $table, string $column, int $rowId): Stream {
|
||||
$handle = $this->connection->openBlob($table, $column, $rowId);
|
||||
if(!is_resource($handle))
|
||||
throw new DataException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
return new GenericStream($this->connection->openBlob($table, $column, $rowId));
|
||||
}
|
||||
|
||||
public function getLastInsertId(): int|string {
|
||||
return $this->connection->lastInsertRowID();
|
||||
}
|
||||
|
||||
public function prepare(string $query): IDbStatement {
|
||||
$statement = $this->connection->prepare($query);
|
||||
if($statement === false)
|
||||
throw new QueryExecuteException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
return new SQLiteStatement($this, $statement);
|
||||
}
|
||||
|
||||
public function query(string $query): IDbResult {
|
||||
$result = $this->connection->query($query);
|
||||
if($result === false)
|
||||
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
return new SQLiteResult($result);
|
||||
}
|
||||
|
||||
public function execute(string $query): int|string {
|
||||
if(!$this->connection->exec($query))
|
||||
throw new QueryExecuteException($this->getLastErrorString(), $this->getLastErrorCode());
|
||||
return $this->connection->changes();
|
||||
}
|
||||
|
||||
public function setAutoCommit(bool $state): void {
|
||||
// there's not really a completely reliable way to set a persistent auto commit disable state
|
||||
if($state === $this->autoCommit)
|
||||
return;
|
||||
$this->autoCommit = $state;
|
||||
|
||||
if($state) {
|
||||
$this->commit();
|
||||
} else {
|
||||
$this->beginTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public function beginTransaction(): void {
|
||||
if(!$this->connection->exec('BEGIN;'))
|
||||
throw new BeginTransactionFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function commit(): void {
|
||||
if(!$this->connection->exec('COMMIT;'))
|
||||
throw new CommitFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
if(!$this->autoCommit)
|
||||
$this->beginTransaction();
|
||||
}
|
||||
|
||||
public function rollback(?string $name = null): void {
|
||||
if(!$this->connection->exec(empty($name) ? 'ROLLBACK;' : "ROLLBACK TO {$name};"))
|
||||
throw new CommitFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
if(!$this->autoCommit)
|
||||
$this->beginTransaction();
|
||||
}
|
||||
|
||||
public function savePoint(string $name): void {
|
||||
if(!$this->connection->exec("SAVEPOINT {$name};"))
|
||||
throw new SavePointFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function releaseSavePoint(string $name): void {
|
||||
if(!$this->connection->exec("RELEASE SAVEPOINT {$name};"))
|
||||
throw new ReleaseSavePointFailedException((string)$this->getLastErrorString(), $this->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function close(): void {
|
||||
$this->connection->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection and associated resources.
|
||||
*/
|
||||
public function __destruct() {
|
||||
$this->close();
|
||||
}
|
||||
}
|
116
src/Data/SQLite/SQLiteConnectionInfo.php
Normal file
116
src/Data/SQLite/SQLiteConnectionInfo.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
// SQLiteConnectionInfo.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-28
|
||||
|
||||
namespace Index\Data\SQLite;
|
||||
|
||||
use Index\Data\IDbConnectionInfo;
|
||||
|
||||
/**
|
||||
* Represents information about a SQLite Client.
|
||||
*/
|
||||
class SQLiteConnectionInfo implements IDbConnectionInfo {
|
||||
private string $fileName;
|
||||
private bool $readOnly;
|
||||
private bool $create;
|
||||
private string $encryptionKey;
|
||||
|
||||
/**
|
||||
* Creates a new SQLiteConnectionInfo instance.
|
||||
*
|
||||
* @param string $fileName Path to the SQLite database.
|
||||
* @param string $encryptionKey Key to encrypt the database with.
|
||||
* @param bool $readOnly Set to true if the database should be opened read-only.
|
||||
* @param bool $create Set to true to create the database if it does not exist.
|
||||
* @return SQLiteConnectionInfo Information to create an SQLite database client.
|
||||
*/
|
||||
public function __construct(
|
||||
string $fileName,
|
||||
string $encryptionKey,
|
||||
bool $readOnly,
|
||||
bool $create
|
||||
) {
|
||||
$this->fileName = $fileName;
|
||||
$this->encryptionKey = $encryptionKey;
|
||||
$this->readOnly = $readOnly;
|
||||
$this->create = $create;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path of the SQLite database.
|
||||
*/
|
||||
public function getFileName(): string {
|
||||
return $this->fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key to use for encryption of the database.
|
||||
*
|
||||
* @return string Encryption key.
|
||||
*/
|
||||
public function getEncryptionKey(): string {
|
||||
return $this->encryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the SQLite database should be opened read-only.
|
||||
*
|
||||
* @return bool true if the database should be opened read-only, false if not.
|
||||
*/
|
||||
public function shouldReadOnly(): bool {
|
||||
return $this->readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the SQLite database should be created if it does not exist.
|
||||
*
|
||||
* @return bool true if the database should be created, false if not.
|
||||
*/
|
||||
public function shouldCreate(): bool {
|
||||
return $this->create;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection info instance with a path.
|
||||
*
|
||||
* @param string $path Path to the SQLite database.
|
||||
* @param string $encryptionKey Key to encrypt the database with.
|
||||
* @param bool $readOnly Set to true if the database should be opened read-only.
|
||||
* @param bool $create Set to true to create the database if it does not exist.
|
||||
* @return SQLiteConnectionInfo Information to create an SQLite database client.
|
||||
*/
|
||||
public static function createPath(
|
||||
string $path,
|
||||
string $encryptionKey = '',
|
||||
bool $readOnly = false,
|
||||
bool $create = true
|
||||
): SQLiteConnectionInfo {
|
||||
return new SQLiteConnectionInfo(
|
||||
$path,
|
||||
$encryptionKey,
|
||||
$readOnly,
|
||||
$create
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection info instance for an in-memory database.
|
||||
*
|
||||
* @param string $encryptionKey Key to encrypt the database with.
|
||||
* @return SQLiteConnectionInfo Information to create an SQLite database client.
|
||||
*/
|
||||
public static function createMemory(string $encryptionKey = ''): SQLiteConnectionInfo {
|
||||
return new SQLiteConnectionInfo(':memory:', $encryptionKey, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection info instance for a database stored in a temporary file.
|
||||
*
|
||||
* @param string $encryptionKey Key to encrypt the database with.
|
||||
* @return SQLiteConnectionInfo Information to create an SQLite database client.
|
||||
*/
|
||||
public static function createTemp(string $encryptionKey = ''): SQLiteConnectionInfo {
|
||||
return new SQLiteConnectionInfo('', $encryptionKey, false, true);
|
||||
}
|
||||
}
|
89
src/Data/SQLite/SQLiteResult.php
Normal file
89
src/Data/SQLite/SQLiteResult.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
// SQLiteResult.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data\SQLite;
|
||||
|
||||
use SQLite3Result;
|
||||
use InvalidArgumentException;
|
||||
use Index\AString;
|
||||
use Index\WString;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\IO\Stream;
|
||||
use Index\IO\TempFileStream;
|
||||
|
||||
/**
|
||||
* Represents an SQLite result set.
|
||||
*/
|
||||
class SQLiteResult implements IDbResult {
|
||||
private SQLite3Result $result;
|
||||
private array $currentRow = [];
|
||||
|
||||
/**
|
||||
* Creates a new instance of SQLiteResult.
|
||||
*
|
||||
* @param SQLite3Result $result Raw underlying result class.
|
||||
* @return SQLiteResult A new SQLiteResult instance.
|
||||
*/
|
||||
public function __construct(SQLite3Result $result) {
|
||||
$this->result = $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of columns per row in the result.
|
||||
*
|
||||
* @return int|string Number of columns in a row.
|
||||
*/
|
||||
public function getFieldCount(): int|string {
|
||||
return $this->result->numColumns();
|
||||
}
|
||||
|
||||
public function next(): bool {
|
||||
$result = $this->result->fetchArray(SQLITE3_BOTH);
|
||||
if($result === false)
|
||||
return false;
|
||||
$this->currentRow = $result;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isNull(int|string $index): bool {
|
||||
return array_key_exists($index, $this->currentRow) && $this->currentRow[$index] === null;
|
||||
}
|
||||
|
||||
public function getValue(int|string $index): mixed {
|
||||
return $this->currentRow[$index] ?? null;
|
||||
}
|
||||
|
||||
public function getString(int|string $index): string {
|
||||
return strval($this->getValue($index));
|
||||
}
|
||||
|
||||
public function getAString(int|string $index): AString {
|
||||
return new AString($this->getString($index));
|
||||
}
|
||||
|
||||
public function getWString(int|string $index, string $encoding): WString {
|
||||
return new WString($this->getString($index), $encoding);
|
||||
}
|
||||
|
||||
public function getInteger(int|string $index): int {
|
||||
return intval($this->getValue($index));
|
||||
}
|
||||
|
||||
public function getFloat(int|string $index): float {
|
||||
return floatval($this->getValue($index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value from the target index as a Stream.
|
||||
* If you're aware that you're using SQLite it may make more sense to use SQLiteConnection::getBlobStream instead.
|
||||
*/
|
||||
public function getStream(int|string $index): ?Stream {
|
||||
if($this->isNull($index))
|
||||
return null;
|
||||
return new TempFileStream($this->getValue($index));
|
||||
}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
100
src/Data/SQLite/SQLiteStatement.php
Normal file
100
src/Data/SQLite/SQLiteStatement.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
// SQLiteStatement.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2022-02-16
|
||||
|
||||
namespace Index\Data\SQLite;
|
||||
|
||||
use SQLite3Result;
|
||||
use SQLite3Stmt;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Data\DbTools;
|
||||
use Index\Data\DbType;
|
||||
use Index\Data\QueryExecuteException;
|
||||
use Index\Data\IDbStatement;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\IO\Stream;
|
||||
|
||||
/**
|
||||
* Represents a prepared SQLite SQL statement.
|
||||
*/
|
||||
class SQLiteStatement implements IDbStatement {
|
||||
private SQLiteConnection $connection;
|
||||
private SQLite3Stmt $statement;
|
||||
private ?SQLite3Result $result = null;
|
||||
|
||||
/**
|
||||
* Creates a new SQLiteStatement instance.
|
||||
*
|
||||
* @param SQLiteConnection $connection A reference to the connection which creates this statement.
|
||||
* @param SQLite3Stmt $statement The raw statement instance.
|
||||
* @return SQLiteStatement A new statement instance.
|
||||
*/
|
||||
public function __construct(SQLiteConnection $connection, SQLite3Stmt $statement) {
|
||||
$this->connection = $connection;
|
||||
$this->statement = $statement;
|
||||
}
|
||||
|
||||
public function getParameterCount(): int {
|
||||
return $this->statement->paramCount();
|
||||
}
|
||||
|
||||
public function addParameter(int $ordinal, mixed $value, int $type = DbType::AUTO): void {
|
||||
if($ordinal < 1 || $ordinal > $this->getParameterCount())
|
||||
throw new InvalidArgumentException('$ordinal is not a valid parameter number.');
|
||||
if($type === DbType::AUTO)
|
||||
$type = DbTools::detectType($value);
|
||||
|
||||
switch($type) {
|
||||
case DbType::NULL:
|
||||
$value = null;
|
||||
$type = SQLITE3_NULL;
|
||||
break;
|
||||
case DbType::INTEGER:
|
||||
$type = SQLITE3_INTEGER;
|
||||
break;
|
||||
case DbType::FLOAT:
|
||||
$type = SQLITE3_FLOAT;
|
||||
break;
|
||||
case DbType::STRING:
|
||||
$type = SQLITE3_TEXT;
|
||||
break;
|
||||
case DbType::BLOB:
|
||||
$type = SQLITE3_BLOB;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException('$type is not a supported type.');
|
||||
}
|
||||
|
||||
if(!$this->statement->bindValue($ordinal, $value, $type))
|
||||
throw new QueryExecuteException((string)$this->connection->getLastErrorString(), $this->connection->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function getResult(): IDbResult {
|
||||
if($this->result === null)
|
||||
throw new RuntimeException('No result is available.');
|
||||
return new SQLiteResult($this->result);
|
||||
}
|
||||
|
||||
public function getLastInsertId(): int|string {
|
||||
return $this->connection->getLastInsertId();
|
||||
}
|
||||
|
||||
public function execute(): void {
|
||||
$result = $this->statement->execute();
|
||||
if($result === false)
|
||||
throw new QueryExecuteException((string)$this->connection->getLastErrorString(), $this->connection->getLastErrorCode());
|
||||
$this->result = $result;
|
||||
}
|
||||
|
||||
public function reset(): void {
|
||||
$this->result = null;
|
||||
if(!$this->statement->clear())
|
||||
throw new QueryExecuteException((string)$this->connection->getLastErrorString(), $this->connection->getLastErrorCode());
|
||||
if(!$this->statement->reset())
|
||||
throw new QueryExecuteException((string)$this->connection->getLastErrorString(), $this->connection->getLastErrorCode());
|
||||
}
|
||||
|
||||
public function close(): void {}
|
||||
}
|
11
src/Data/SavePointFailedException.php
Normal file
11
src/Data/SavePointFailedException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// SavePointFailedException.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when save point creation fails.
|
||||
*/
|
||||
class SavePointFailedException extends TransactionException {}
|
11
src/Data/TransactionException.php
Normal file
11
src/Data/TransactionException.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// TransactionException.php
|
||||
// Created: 2021-05-02
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index\Data;
|
||||
|
||||
/**
|
||||
* Exception for errors during transactions.
|
||||
*/
|
||||
class TransactionException extends DataException {}
|
667
src/DateTime.php
Normal file
667
src/DateTime.php
Normal file
|
@ -0,0 +1,667 @@
|
|||
<?php
|
||||
// DateTime.php
|
||||
// Created: 2021-06-11
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index;
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use InvalidArgumentException;
|
||||
use JsonSerializable;
|
||||
use RuntimeException;
|
||||
use Stringable;
|
||||
|
||||
/**
|
||||
* Represents a date and time
|
||||
*/
|
||||
class DateTime extends DateTimeImmutable implements JsonSerializable, Stringable, IComparable, IEquatable {
|
||||
/**
|
||||
* Represents Sunday for getDayOfWeek.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const SUNDAY = 0;
|
||||
|
||||
/**
|
||||
* Represents Monday for getDayOfWeek.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const MONDAY = 1;
|
||||
|
||||
/**
|
||||
* Represents Tuesday for getDayOfWeek.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const TUESDAY = 2;
|
||||
|
||||
/**
|
||||
* Represents Wednesday for getDayOfWeek.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const WEDNESDAY = 3;
|
||||
|
||||
/**
|
||||
* Represents Thursday for getDayOfWeek.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const THURSDAY = 4;
|
||||
|
||||
/**
|
||||
* Represents Friday for getDayOfWeek.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const FRIDAY = 5;
|
||||
|
||||
/**
|
||||
* Represents Saturday for getDayOfWeek.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const SATURDAY = 6;
|
||||
|
||||
private const COMPARE = 'YmdHisu';
|
||||
|
||||
/**
|
||||
* Creates a new DateTime object.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/datetime.formats.php
|
||||
* @param string $dateTime A date/time string.
|
||||
* @param DateTimeZone|null $timeZone An object representing the desired time zone.
|
||||
* @return DateTime A new DateTime instance.
|
||||
*/
|
||||
public function __construct(string $dateTime = 'now', DateTimeZone|null $timeZone = null) {
|
||||
parent::__construct($dateTime, $timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date component of this instance.
|
||||
*
|
||||
* @return DateTime New instance with the same date but the time set to 00:00:00.
|
||||
*/
|
||||
public function getDate(): DateTime {
|
||||
return self::create($this->getYear(), $this->getMonth(), $this->getDay(), 0, 0, 0, 0, $this->getTimezone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the year component of this instance.
|
||||
*
|
||||
* @return int Year component.
|
||||
*/
|
||||
public function getYear(): int {
|
||||
return (int)$this->format('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the month component of this instance.
|
||||
*
|
||||
* @return int Month component, ranging from 1 to 12.
|
||||
*/
|
||||
public function getMonth(): int {
|
||||
return (int)$this->format('n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the day component of this instance.
|
||||
*
|
||||
* @return int Day component, ranging from 1 to 31.
|
||||
*/
|
||||
public function getDay(): int {
|
||||
return (int)$this->format('j');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the day of the week represented by this instance.
|
||||
*
|
||||
* See the SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY and SATURDAY constants for the possible return values of this function.
|
||||
*
|
||||
* @return int Integer between 0 and 6 representing the day of the week.
|
||||
*/
|
||||
public function getDayOfWeek(): int {
|
||||
return (int)$this->format('w');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the day of the year represented by this instance.
|
||||
*
|
||||
* @return int Day of the year, ranging from 1-366.
|
||||
*/
|
||||
public function getDayOfYear(): int {
|
||||
return ((int)$this->format('z')) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ISO8601 week number of this instance.
|
||||
*
|
||||
* @return int Integer representing the current week number.
|
||||
*/
|
||||
public function getWeek(): int {
|
||||
return (int)$this->format('W');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time of day for this instance.
|
||||
*
|
||||
* @return TimeSpan A TimeSpan representing the amount of time elapsed since midnight.
|
||||
*/
|
||||
public function getTimeOfDay(): TimeSpan {
|
||||
return TimeSpan::create(0, $this->getHour(), $this->getMinute(), $this->getSecond(), $this->getMicrosecond());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hour component of this instance.
|
||||
*
|
||||
* @return int Hour component, ranging from 0 to 23.
|
||||
*/
|
||||
public function getHour(): int {
|
||||
return (int)$this->format('G');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minute component of this instance.
|
||||
*
|
||||
* @return int Minute component, ranging from 0 to 59.
|
||||
*/
|
||||
public function getMinute(): int {
|
||||
return (int)$this->format('i');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the second component of this instance.
|
||||
*
|
||||
* @return int Second component, ranging from 0 to 59.
|
||||
*/
|
||||
public function getSecond(): int {
|
||||
return (int)$this->format('s');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the millisecond component of this instance.
|
||||
*
|
||||
* @return int Millisecond component, ranging from 0 to 999.
|
||||
*/
|
||||
public function getMillisecond(): int {
|
||||
return (int)$this->format('v');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the microsecond component of this instance.
|
||||
*
|
||||
* @return int Microsecond component, ranging from 0 to 999999.
|
||||
*/
|
||||
public function getMicrosecond(): int {
|
||||
return (int)$this->format('u');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of seconds that have elapsed since midnight January 1st, 1970 UTC for this instance.
|
||||
*
|
||||
* @return int Number of seconds.
|
||||
*/
|
||||
public function getUnixTimeSeconds(): int {
|
||||
return (int)$this->format('U');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of milliseconds that have elapsed since midnight January 1st, 1970 UTC for this instance.
|
||||
*
|
||||
* @return float Number of milliseconds.
|
||||
*/
|
||||
public function getUnixTimeMilliseconds(): float {
|
||||
return (float)$this->format('Uv');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether Daylight Savings is in effect during the time this instance represents.
|
||||
*
|
||||
* @return bool true if DST is in effect, false if not.
|
||||
*/
|
||||
public function isDaylightSavingTime(): bool {
|
||||
return $this->format('I') !== '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the year this instance represents is a leap year.
|
||||
*
|
||||
* @return bool true if the year is a leap year, false if not.
|
||||
*/
|
||||
public function isLeapYear(): bool {
|
||||
return $this->format('L') !== '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this DateTime uses UTC date/time.
|
||||
*
|
||||
* @return bool true if UTC, false if not.
|
||||
*/
|
||||
public function isUTC(): bool {
|
||||
return $this->getTimezone()->equals(TimeZoneInfo::utc());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time zone for this instance.
|
||||
*
|
||||
* @return TimeZoneInfo Time zone for this instance.
|
||||
*/
|
||||
public function getTimezone(): TimeZoneInfo {
|
||||
return TimeZoneInfo::cast(parent::getTimezone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a period of time to this date time object.
|
||||
*
|
||||
* @param DateInterval $timeSpan Time period to add to this DateTime.
|
||||
* @return DateTime A DateTime instance which is the sum of this instance and the provided period.
|
||||
*/
|
||||
public function add(DateInterval $timeSpan): DateTime {
|
||||
return self::cast(parent::add($timeSpan));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of years to this instance.
|
||||
*
|
||||
* @param float $years Number of years to add.
|
||||
* @return DateTime New DateTime instance with the years added.
|
||||
*/
|
||||
public function addYears(float $years): DateTime {
|
||||
return $this->add(TimeSpan::fromDays(365 * $years));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of months to this instance.
|
||||
*
|
||||
* @param float $months Number of months to add.
|
||||
* @return DateTime New DateTime instance with the months added.
|
||||
*/
|
||||
public function addMonths(float $months): DateTime {
|
||||
return $this->add(TimeSpan::fromDays(365 * 31 * $months));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of days to this instance.
|
||||
*
|
||||
* @param float $days Number of days to add.
|
||||
* @return DateTime New DateTime instance with the days added.
|
||||
*/
|
||||
public function addDays(float $days): DateTime {
|
||||
return $this->add(TimeSpan::fromDays($days));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of hours to this instance.
|
||||
*
|
||||
* @param float $hours Number of hours to add.
|
||||
* @return DateTime New DateTime instance with the hours added.
|
||||
*/
|
||||
public function addHours(float $hours): DateTime {
|
||||
return $this->add(TimeSpan::fromHours($hours));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of minutes to this instance.
|
||||
*
|
||||
* @param float $minutes Number of minutes to add.
|
||||
* @return DateTime New DateTime instance with the minutes added.
|
||||
*/
|
||||
public function addMinutes(float $minutes): DateTime {
|
||||
return $this->add(TimeSpan::fromMinutes($minutes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of seconds to this instance.
|
||||
*
|
||||
* @param float $seconds Number of seconds to add.
|
||||
* @return DateTime New DateTime instance with the seconds added.
|
||||
*/
|
||||
public function addSeconds(float $seconds): DateTime {
|
||||
return $this->add(TimeSpan::fromSeconds($seconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of milliseconds to this instance.
|
||||
*
|
||||
* @param float $millis Number of milliseconds to add.
|
||||
* @return DateTime New DateTime instance with the milliseconds added.
|
||||
*/
|
||||
public function addMilliseconds(float $millis): DateTime {
|
||||
return $this->add(TimeSpan::fromMilliseconds($millis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number of microseconds to this instance.
|
||||
*
|
||||
* @param float $micros Number of microseconds to add.
|
||||
* @return DateTime New DateTime instance with the microseconds added.
|
||||
*/
|
||||
public function addMicroseconds(float $micros): DateTime {
|
||||
return $this->add(TimeSpan::fromMicroseconds($micros));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for subtract, must exist because of the DateTimeImmutable inheritance but I don't like short names.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function sub(DateInterval $interval): DateTime {
|
||||
return $this->subtract($interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts a time period from this DateTime.
|
||||
*
|
||||
* @param DateInterval $timeSpan Time period to be subtracted.
|
||||
* @return DateTime A new DateTime instance which is this instance minus the given time period.
|
||||
*/
|
||||
public function subtract(DateInterval $timeSpan): DateTime {
|
||||
return self::cast(parent::sub($timeSpan));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for difference, must exist because of the DateTimeImmutable inheritance but I don't like short names.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function diff(DateTimeInterface $dateTime, bool $absolute = false): TimeSpan {
|
||||
return $this->difference($dateTime, $absolute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts another Date/Time from this DateTime.
|
||||
*
|
||||
* @param DateTimeInterface $dateTime Date/time to subtract.
|
||||
* @return TimeSpan Difference between this DateTime and the provided one.
|
||||
*/
|
||||
public function difference(DateTimeInterface $dateTime, bool $absolute = false): TimeSpan {
|
||||
return TimeSpan::cast(parent::diff($dateTime, $absolute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a modifier on this instance and returns the result as a new instance.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/datetime.formats.php
|
||||
* @param string $modifier A date/time format.
|
||||
* @return DateTime The modified date/time.
|
||||
*/
|
||||
public function modify(string $modifier): DateTime {
|
||||
return self::cast(parent::modify($modifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DateTime with the given date component.
|
||||
*
|
||||
* @param int $year Desired year component.
|
||||
* @param int $month Desired month component.
|
||||
* @param int $day Desired day component.
|
||||
* @return DateTime A new DateTime instance with the given date component.
|
||||
*/
|
||||
public function setDate(int $year, int $month, int $day): DateTime {
|
||||
return self::cast(parent::setDate($year, $month, $day));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DateTime with the given ISO date for the date component.
|
||||
*
|
||||
* @param int $year Year of the date.
|
||||
* @param int $week Week of the date.
|
||||
* @param int $dayOfWeek Offset from the first of the week.
|
||||
* @return DateTime A new DateTime instance with the given date component.
|
||||
*/
|
||||
public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTime {
|
||||
return self::cast(parent::setISODate($year, $week, $dayOfWeek));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DateTime with the given time component.
|
||||
*
|
||||
* @param int $hour Desired hour component.
|
||||
* @param int $minute Desired minute component.
|
||||
* @param int $second Desired second component.
|
||||
* @param int $microsecond Desired microsecond component.
|
||||
* @return DateTime A new DateTime instance with the given time component.
|
||||
*/
|
||||
public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): DateTime {
|
||||
return self::cast(parent::setTime($hour, $minute, $second, $microsecond));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DateTime with the date and time components based on a Unix timestamp.
|
||||
*
|
||||
* @param int $timestamp Unix timestamp representing the date.
|
||||
* @return DateTime A new DateTime instance with the given date and time.
|
||||
*/
|
||||
public function setTimestamp(int $timestamp): DateTime {
|
||||
return self::cast(parent::setTimestamp($timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DateTime with the given time zone.
|
||||
*
|
||||
* @param DateTimeZone $timeZone An object representing the desired time zone.
|
||||
* @return DateTime A new DateTime with the given time zone.
|
||||
*/
|
||||
public function setTimezone(DateTimeZone $timeZone): DateTime {
|
||||
return self::cast(parent::setTimezone($timeZone));
|
||||
}
|
||||
|
||||
public function compare(mixed $other): int {
|
||||
if($other instanceof DateTimeInterface)
|
||||
return strcmp($this->format(self::COMPARE), $other->format(self::COMPARE));
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function equals(mixed $other): bool {
|
||||
if($other instanceof DateTimeInterface)
|
||||
return $this->format(self::COMPARE) === $other->format(self::COMPARE);
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isLessThan(DateTimeInterface $other): bool {
|
||||
return $this->compare($other) < 0;
|
||||
}
|
||||
|
||||
public function isLessThanOrEqual(DateTimeInterface $other): bool {
|
||||
return $this->compare($other) <= 0;
|
||||
}
|
||||
|
||||
public function isMoreThan(DateTimeInterface $other): bool {
|
||||
return $this->compare($other) > 0;
|
||||
}
|
||||
|
||||
public function isMoreThanOrEqual(DateTimeInterface $other): bool {
|
||||
return $this->compare($other) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats this DateTime as an ISO8601 date/time string.
|
||||
*
|
||||
* @return string This object represented as an ISO8601 date/time string.
|
||||
*/
|
||||
public function toISO8601String(): string {
|
||||
return $this->format(DateTimeInterface::ATOM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats this DateTime as a Cookie date/time string.
|
||||
*
|
||||
* @return string This object represented as a Cookie date/time string.
|
||||
*/
|
||||
public function toCookieString(): string {
|
||||
return $this->format(DateTimeInterface::COOKIE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats this DateTime as an RFC 822 date/time string.
|
||||
*
|
||||
* @return string This object represented as an RFC 822 date/time string.
|
||||
*/
|
||||
public function toRFC822String(): string {
|
||||
return $this->format(DateTimeInterface::RFC822);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string representation of this object.
|
||||
*
|
||||
* @return string Representation of this object.
|
||||
*/
|
||||
public function __toString(): string {
|
||||
return $this->format(DateTimeInterface::ATOM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data which should be serialized as json.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed Data to be passed to json_encode.
|
||||
*/
|
||||
public function jsonSerialize(): mixed {
|
||||
return (string)$this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this immutable \Index\DateTime type to a mutable \DateTime type.
|
||||
*
|
||||
* @return \DateTime A new mutable \DateTime instance.
|
||||
*/
|
||||
public function toNative(): \DateTime {
|
||||
return \DateTime::createFromImmutable($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DateTime object that is set to the current date and time, expressed in the provided time zone.
|
||||
*
|
||||
* @param DateTimeZone $timeZone Desired time zone, null for the current default time zone.
|
||||
* @return DateTime An instance representing now.
|
||||
*/
|
||||
public static function now(?DateTimeZone $timeZone = null): DateTime {
|
||||
return new DateTime('now', $timeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DateTime object that is set to the current date and time, expressed in UTC.
|
||||
*
|
||||
* @return DateTime An instance representing now in UTC.
|
||||
*/
|
||||
public static function utcNow(): DateTime {
|
||||
return self::now(TimeZoneInfo::utc());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Unix time seconds to a DateTime object.
|
||||
*
|
||||
* @param int $seconds Unix time seconds.
|
||||
* @return DateTime A DateTime instance representing the Unix time.
|
||||
*/
|
||||
public static function fromUnixTimeSeconds(int $seconds): DateTime {
|
||||
return new DateTime('@' . $seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Unix time milliseconds to a DateTime object.
|
||||
*
|
||||
* @param float $millis Unix time milliseconds.
|
||||
* @return DateTime A DateTime instance representing the Unix time.
|
||||
*/
|
||||
public static function fromUnixTimeMilliseconds(float $millis): DateTime {
|
||||
return new DateTime('@' . ($millis / 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DateTime instance with the given components.
|
||||
*
|
||||
* @param int $year Desired year component.
|
||||
* @param int $month Desired month component.
|
||||
* @param int $day Desired day component.
|
||||
* @param int $hour Desired hour component.
|
||||
* @param int $minute Desired minute component.
|
||||
* @param int $second Desired second component.
|
||||
* @param int $micros Desired microsecond component.
|
||||
* @param ?DateTimeZone $timeZone Desired time zone.
|
||||
*/
|
||||
public static function create(int $year, int $month = 1, int $day = 1, int $hour = 0, int $minute = 0, int $second = 0, int $micros = 0, ?DateTimeZone $timeZone = null): DateTime {
|
||||
if($year < 1 || $year > 9999)
|
||||
throw new InvalidArgumentException('$year may not be less than 1 or more than 9999.');
|
||||
if($month < 1 || $month > 12)
|
||||
throw new InvalidArgumentException('$month may not be less than 1 or more than 12.');
|
||||
if($day < 1 || $day > 31)
|
||||
throw new InvalidArgumentException('$day may not be less than 1 or more than 31.');
|
||||
if($hour < 0 || $hour > 23)
|
||||
throw new InvalidArgumentException('$hour may not be less than 0 or more than 23.');
|
||||
if($minute < 0 || $minute > 59)
|
||||
throw new InvalidArgumentException('$minute may not be less than 0 or more than 59.');
|
||||
if($second < 0 || $second > 59)
|
||||
throw new InvalidArgumentException('$second may not be less than 0 or more than 59.');
|
||||
if($micros < 0 || $micros > 999999)
|
||||
throw new InvalidArgumentException('$micros may not be less than 0 or more than 999999.');
|
||||
|
||||
return new DateTime(
|
||||
sprintf(
|
||||
'%04d-%02d-%02dT%02d:%02d:%02d.%06d',
|
||||
$year, $month, $day, $hour, $minute, $second, $micros
|
||||
),
|
||||
$timeZone
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a time string and creates a DateTime using it.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/datetime.createfromformat
|
||||
* @param string $format Format that the passed string should be in.
|
||||
* @param string $dateTime String representing the time.
|
||||
* @param ?DateTimeZone $timeZone Desired time zone.
|
||||
* @return DateTime|false A DateTime instance, or false on failure.
|
||||
*/
|
||||
public static function createFromFormat(string $format, string $dateTime, ?DateTimeZone $timeZone = null): DateTime|false {
|
||||
$instance = parent::createFromFormat($format, $dateTime, $timeZone);
|
||||
if($instance === false)
|
||||
return false;
|
||||
return self::cast($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DateTime instance from an interface implementation.
|
||||
*
|
||||
* @param DateTimeInterface $object Object that should be sourced.
|
||||
* @return DateTime A new DateTime instance.
|
||||
*/
|
||||
public static function createFromInterface(DateTimeInterface $object): DateTime {
|
||||
return self::cast($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new immutable DateTime instance from a mutable instance.
|
||||
*
|
||||
* @param \DateTime $object Mutable object that should be sourced.
|
||||
* @return DateTime New immutable DateTime representing the same value.
|
||||
*/
|
||||
public static function createFromMutable(\DateTime $object): DateTime {
|
||||
return self::cast($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function __set_state(array $array): DateTime {
|
||||
return self::cast(parent::__set_state($array));
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure a DateTimeInterface implementing object is a DateTime instance.
|
||||
*
|
||||
* @param DateTimeInterface $dateTime Input object.
|
||||
* @return DateTime If the input was a DateTime, the same instance will be returned.
|
||||
* If the input was something else, a new DateTime instance will be returned based on the input.
|
||||
*/
|
||||
public static function cast(DateTimeInterface $dateTime): DateTime {
|
||||
if($dateTime instanceof DateTime)
|
||||
return $dateTime;
|
||||
return new DateTime($dateTime->format('@U.u'), $dateTime->getTimezone());
|
||||
}
|
||||
}
|
340
src/Environment.php
Normal file
340
src/Environment.php
Normal file
|
@ -0,0 +1,340 @@
|
|||
<?php
|
||||
// Environment.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Provides information about the current runtime environment.
|
||||
*/
|
||||
final class Environment {
|
||||
/**
|
||||
* Contains the end-of-line sequence for the current platform.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const NEWLINE = PHP_EOL;
|
||||
|
||||
/**
|
||||
* Contains the amount of bytes in an integer for this system.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const INT_SIZE = PHP_INT_SIZE;
|
||||
|
||||
private static ?Version $indexVersion = null;
|
||||
private static ?Version $phpVersion = null;
|
||||
private static array $phpVersions = [];
|
||||
|
||||
/**
|
||||
* Checks whether the current PHP environment is running in debug mode.
|
||||
*
|
||||
* Essentially checks if any sort of error reporting is enabled. It shouldn't be in prod.
|
||||
*
|
||||
* @return bool true if the environment is in debug mode, false is not.
|
||||
*/
|
||||
public static function isDebug(): bool {
|
||||
return error_reporting() !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles debug mode.
|
||||
*
|
||||
* Essentially turns all error reporting on or off.
|
||||
*
|
||||
* @param bool $debug true if debug mode should be enabled, false if it should be disabled.
|
||||
*/
|
||||
public static function setDebug(bool $debug = false): void {
|
||||
if($debug) {
|
||||
ini_set('display_errors', 'on');
|
||||
error_reporting(-1);
|
||||
} else {
|
||||
ini_set('display_errors', 'off');
|
||||
error_reporting(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an environment variable.
|
||||
*
|
||||
* @param string $name Name of the environment variable.
|
||||
* @param bool $localOnly true if only variables local to the application should be read,
|
||||
* false if greater level operating system variables should be included.
|
||||
* @return ?string null if the variable doesn't exist, otherwise a string of the value.
|
||||
*/
|
||||
public static function getVariable(string $name, bool $localOnly = true): ?string {
|
||||
return ($value = getenv($name, $localOnly)) === false ? null : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an application-local environment variable.
|
||||
*
|
||||
* @param string $name Name of the environment variable.
|
||||
* @param mixed $value Value that should be assigned to the environment variable. Will be cast to a string.
|
||||
*/
|
||||
public static function setVariable(string $name, mixed $value): void {
|
||||
putenv($name . '=' . ((string)$value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an application-local environment variable.
|
||||
*
|
||||
* @param string $name Name of the environment variable.
|
||||
*/
|
||||
public static function removeVariable(string $name): void {
|
||||
putenv($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current version of the Index library.
|
||||
*
|
||||
* Parses the VERSION in the root of the Index directory.
|
||||
*
|
||||
* @return Version A Version instance representing the version of the Index Library.
|
||||
*/
|
||||
public static function getIndexVersion(): Version {
|
||||
if(self::$indexVersion === null)
|
||||
self::$indexVersion = Version::parse(trim(file_get_contents(NDX_ROOT . DIRECTORY_SEPARATOR . 'VERSION')));
|
||||
return self::$indexVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version of the PHP installation Index is running on.
|
||||
*
|
||||
* @return Version A Version instance representing the version of PHP.
|
||||
*/
|
||||
public static function getPHPVersion(): Version {
|
||||
if(self::$phpVersion === null)
|
||||
self::$phpVersion = new Version(PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION);
|
||||
return self::$phpVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version of a PHP extension.
|
||||
*
|
||||
* @param string $extension Name of the extension.
|
||||
* @return ?Version null if the extension is not installed, otherwise an instance of Version representing the version of the extension.
|
||||
*/
|
||||
public static function getPHPExtensionVersion(string $extension): ?Version {
|
||||
if(!isset(self::$phpVersions[$extension])) {
|
||||
$rawVersion = phpversion($extension);
|
||||
self::$phpVersions[$extension] = empty($rawVersion) ? Version::empty() : Version::parse($rawVersion);
|
||||
}
|
||||
return self::$phpVersions[$extension];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the Operating System PHP is running on.
|
||||
*
|
||||
* @return string Name of the Operating System.
|
||||
*/
|
||||
public static function getOSName(): string {
|
||||
static $value = null;
|
||||
$value ??= php_uname('s');
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on Windows.
|
||||
*
|
||||
* @return bool true if we're running on Windows, otherwise false.
|
||||
*/
|
||||
public static function isWindows(): bool {
|
||||
return self::isWindowsNT()
|
||||
|| self::getOSName() === 'Windows';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on Windows NT.
|
||||
*
|
||||
* @return bool true if we're running on Windows NT, otherwise false.
|
||||
*/
|
||||
public static function isWindowsNT(): bool {
|
||||
return ($os = self::getOSName()) === 'Windows NT'
|
||||
|| $os === 'Windows_NT'
|
||||
|| $os === 'WindowsNT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on a Unix-like Operating System.
|
||||
*
|
||||
* @return bool true if we're running on a Unix-like Operating System, otherwise false.
|
||||
*/
|
||||
public static function isUnixLike(): bool {
|
||||
return self::isLinux()
|
||||
|| self::isBSD()
|
||||
|| self::isMacOS()
|
||||
|| self::isSolaris();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on a BSD derivative.
|
||||
*
|
||||
* @return bool true if we're running on a BSD derivative, otherwise false.
|
||||
*/
|
||||
public static function isBSD(): bool {
|
||||
return self::isOpenBSD()
|
||||
|| self::isFreeBSD()
|
||||
|| self::isNetBSD()
|
||||
|| self::isDragonFlyBSD()
|
||||
|| self::getOSName() === 'BSD';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on OpenBSD.
|
||||
*
|
||||
* @return bool true if we're running on OpenBSD, otherwise false.
|
||||
*/
|
||||
public static function isOpenBSD(): bool {
|
||||
return self::getOSName() === 'OpenBSD';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on FreeBSD.
|
||||
*
|
||||
* @return bool true if we're running on FreeBSD, otherwise false.
|
||||
*/
|
||||
public static function isFreeBSD(): bool {
|
||||
return self::getOSName() === 'FreeBSD';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on NetBSD.
|
||||
*
|
||||
* @return bool true if we're running on NetBSD, otherwise false.
|
||||
*/
|
||||
public static function isNetBSD(): bool {
|
||||
return self::getOSName() === 'NetBSD';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on DragonFly BSD.
|
||||
*
|
||||
* @return bool true if we're running on DragonFly BSD, otherwise false.
|
||||
*/
|
||||
public static function isDragonFlyBSD(): bool {
|
||||
return self::getOSName() === 'DragonFly';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on macOS.
|
||||
*
|
||||
* @return bool true if we're running on macOS, otherwise false.
|
||||
*/
|
||||
public static function isMacOS(): bool {
|
||||
return self::getOSName() === 'Darwin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on Solaris.
|
||||
*
|
||||
* @return bool true if we're running on Solaris, otherwise false.
|
||||
*/
|
||||
public static function isSolaris(): bool {
|
||||
return self::getOSName() === 'SunOS';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on Linux.
|
||||
*
|
||||
* @return bool true if we're running on Linux, otherwise false.
|
||||
*/
|
||||
public static function isLinux(): bool {
|
||||
return self::getOSName() === 'Linux';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the SAPI PHP is currently running through.
|
||||
*
|
||||
* @return string Name of the SAPI.
|
||||
*/
|
||||
public static function getSAPIName(): string {
|
||||
static $sapi = null;
|
||||
$sapi ??= php_sapi_name();
|
||||
return $sapi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running through Apache2Handler.
|
||||
*
|
||||
* @return bool true if we're running through Apache2Handler, otherwise false.
|
||||
*/
|
||||
public static function isApache2(): bool {
|
||||
return ($sapi = self::getSAPIName()) === 'apache' || $sapi === 'apache2handler';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running through CGI.
|
||||
*
|
||||
* @return bool true if we're running through CGI, otherwise false.
|
||||
*/
|
||||
public static function isCGI(): bool {
|
||||
return self::getSAPIName() === 'cgi';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running through FastCGI (and/or php-fpm).
|
||||
*
|
||||
* @return bool true if we're running through FastCGI, otherwise false.
|
||||
*/
|
||||
public static function isFastCGI(): bool {
|
||||
return ($sapi = self::getSAPIName()) === 'cgi-fcgi' || $sapi === 'fpm-fcgi';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running through console.
|
||||
*
|
||||
* @return bool true if we're running through console, otherwise false.
|
||||
*/
|
||||
public static function isConsole(): bool {
|
||||
return self::getSAPIName() === 'cli';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running through the built-in development server.
|
||||
*
|
||||
* @return bool true if we're running through the built-in development server, otherwise false.
|
||||
*/
|
||||
public static function isDebugServer(): bool {
|
||||
return self::getSAPIName() === 'cli-server';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running through LiteSpeed.
|
||||
*
|
||||
* @return bool true if we're running through LiteSpeed, otherwise false.
|
||||
*/
|
||||
public static function isLiteSpeed(): bool {
|
||||
return self::getSAPIName() === 'litespeed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running through PHPDBG.
|
||||
*
|
||||
* @return bool true if we're running through PHPDBG, otherwise false.
|
||||
*/
|
||||
public static function isPHPDebugger(): bool {
|
||||
return self::getSAPIName() === 'phpdbg';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on a 32-bit system.
|
||||
*
|
||||
* @return bool true if we're on 32-bit, false if not.
|
||||
*/
|
||||
public static function is32Bit(): bool {
|
||||
return PHP_INT_SIZE === 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're running on a 64-bit system.
|
||||
*
|
||||
* @return bool true if we're on 64-bit, false if not.
|
||||
*/
|
||||
public static function is64Bit(): bool {
|
||||
return PHP_INT_SIZE === 8;
|
||||
}
|
||||
}
|
79
src/Exceptions.php
Normal file
79
src/Exceptions.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
// Exceptions.php
|
||||
// Created: 2021-04-30
|
||||
// Updated: 2021-05-12
|
||||
|
||||
namespace Index;
|
||||
|
||||
use ErrorException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Provides handling for uncaught exceptions and errors.
|
||||
*/
|
||||
final class Exceptions {
|
||||
/**
|
||||
* Convert errors to ErrorExceptions.
|
||||
*
|
||||
* Automatically invoked by inclusion of index.php into your project unless the constant <code>NDX_LEAVE_ERRORS</code> is defined beforehand.
|
||||
* This is not recommended as it may cause undefined behaviour in some classes.
|
||||
* This will also make error suppression not work, luckily you've not been using that since PHP 5. Right? Right?!
|
||||
* Besides, this makes it possible to try..catch errors.
|
||||
*/
|
||||
public static function convertErrors(): void {
|
||||
self::restoreErrors();
|
||||
set_error_handler([self::class, 'handleError'], -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores error handling to the default PHP state.
|
||||
*/
|
||||
public static function restoreErrors(): void {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle uncaught exceptions.
|
||||
*
|
||||
* Automatically invoked by inclusion of index.php into your project unless the constant <code>NDX_LEAVE_EXCEPTIONS</code> is defined.
|
||||
*/
|
||||
public static function handleExceptions(): void {
|
||||
self::restoreExceptions();
|
||||
//set_exception_handler([self::class, 'handleException']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores uncaught exception handling to the default PHP state.
|
||||
*/
|
||||
public static function restoreExceptions(): void {
|
||||
restore_exception_handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts errors to ErrorExceptions.
|
||||
*
|
||||
* Paramater documentation is copied from the set_error_handler page on php.net
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.set-error-handler.php
|
||||
* @param int $errno The first parameter, errno, will be passed the level of the error raised, as an integer.
|
||||
* @param string $errstr The second parameter, errstr, will be passed the error message, as a string.
|
||||
* @param string $errfile If the callback accepts a third parameter, errfile, it will be passed the filename that the error was raised in, as a string.
|
||||
* @param int $errline If the callback accepts a fourth parameter, errline, it will be passed the line number where the error was raised, as an integer.
|
||||
* @throws ErrorException An ErrorException with the provided parameters.
|
||||
* @return bool if this were false the PHP error handler would continue, but returning is never reached.
|
||||
*/
|
||||
public static function handleError(int $errno, string $errstr, string $errfile, int $errline): bool {
|
||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles uncaught exceptions.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.set-exception-handler.php
|
||||
* @param ?Throwable $ex Uncaught Throwable to handle. May be null to reset state(?) apparently.
|
||||
*/
|
||||
public static function handleException(?Throwable $ex): void {
|
||||
if($ex === null)
|
||||
return;
|
||||
}
|
||||
}
|
48
src/Http/Content/BencodedContent.php
Normal file
48
src/Http/Content/BencodedContent.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
// BencodedContent.php
|
||||
// Created: 2022-02-10
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Content;
|
||||
|
||||
use Stringable;
|
||||
use Index\IO\Stream;
|
||||
use Index\IO\FileStream;
|
||||
use Index\Serialisation\Serialiser;
|
||||
use Index\Serialisation\IBencodeSerialisable;
|
||||
|
||||
class BencodedContent implements Stringable, IHttpContent, IBencodeSerialisable {
|
||||
private mixed $content;
|
||||
|
||||
public function __construct(mixed $content) {
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function getContent(): mixed {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function bencodeSerialise(): mixed {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return Serialiser::bencode()->serialise($this->content);
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->encode();
|
||||
}
|
||||
|
||||
public static function fromEncoded(Stream|string $encoded): BencodedContent {
|
||||
return new BencodedContent(Serialiser::bencode()->deserialise($encoded));
|
||||
}
|
||||
|
||||
public static function fromFile(string $path): BencodedContent {
|
||||
return self::fromEncoded(FileStream::openRead($path));
|
||||
}
|
||||
|
||||
public static function fromRequest(): BencodedContent {
|
||||
return self::fromFile('php://input');
|
||||
}
|
||||
}
|
54
src/Http/Content/FormContent.php
Normal file
54
src/Http/Content/FormContent.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
// FormContent.php
|
||||
// Created: 2022-02-10
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Content;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\Http\HttpUploadedFile;
|
||||
|
||||
class FormContent implements IHttpContent {
|
||||
private array $postFields;
|
||||
private array $uploadedFiles;
|
||||
|
||||
public function __construct(array $postFields, array $uploadedFiles) {
|
||||
$this->postFields = $postFields;
|
||||
$this->uploadedFiles = $uploadedFiles;
|
||||
}
|
||||
|
||||
public function getParam(string $name, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed {
|
||||
if(!isset($this->postFields[$name]))
|
||||
return null;
|
||||
return filter_var($this->postFields[$name] ?? null, $filter, $options);
|
||||
}
|
||||
|
||||
public function hasParam(string $name): bool {
|
||||
return isset($this->postFields[$name]);
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return $this->postFields;
|
||||
}
|
||||
|
||||
public function getUploadedFile(string $name): HttpUploadedFile {
|
||||
if(!isset($this->uploadedFiles[$name]))
|
||||
throw new RuntimeException('No file with name $name present.');
|
||||
return $this->uploadedFiles[$name];
|
||||
}
|
||||
|
||||
public static function fromRaw(array $post, array $files): FormContent {
|
||||
return new FormContent(
|
||||
$post,
|
||||
HttpUploadedFile::createFromFILES($files)
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromRequest(): FormContent {
|
||||
return self::fromRaw($_POST, $_FILES);
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return '';
|
||||
}
|
||||
}
|
11
src/Http/Content/IHttpContent.php
Normal file
11
src/Http/Content/IHttpContent.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// IHttpContent.php
|
||||
// Created: 2022-02-08
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Content;
|
||||
|
||||
use Stringable;
|
||||
|
||||
interface IHttpContent extends Stringable {
|
||||
}
|
48
src/Http/Content/JsonContent.php
Normal file
48
src/Http/Content/JsonContent.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
// JsonContent.php
|
||||
// Created: 2022-02-10
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Content;
|
||||
|
||||
use JsonSerializable;
|
||||
use Stringable;
|
||||
use Index\IO\Stream;
|
||||
use Index\IO\FileStream;
|
||||
use Index\Serialisation\Serialiser;
|
||||
|
||||
class JsonContent implements Stringable, IHttpContent, JsonSerializable {
|
||||
private mixed $content;
|
||||
|
||||
public function __construct(mixed $content) {
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function getContent(): mixed {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return Serialiser::json()->serialise($this->content);
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->encode();
|
||||
}
|
||||
|
||||
public static function fromEncoded(Stream|string $encoded): JsonContent {
|
||||
return new JsonContent(Serialiser::json()->deserialise($encoded));
|
||||
}
|
||||
|
||||
public static function fromFile(string $path): JsonContent {
|
||||
return self::fromEncoded(FileStream::openRead($path));
|
||||
}
|
||||
|
||||
public static function fromRequest(): JsonContent {
|
||||
return self::fromFile('php://input');
|
||||
}
|
||||
}
|
36
src/Http/Content/StreamContent.php
Normal file
36
src/Http/Content/StreamContent.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
// StreamContent.php
|
||||
// Created: 2022-02-10
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Content;
|
||||
|
||||
use Stringable;
|
||||
use Index\IO\Stream;
|
||||
use Index\IO\FileStream;
|
||||
|
||||
class StreamContent implements Stringable, IHttpContent {
|
||||
private Stream $stream;
|
||||
|
||||
public function __construct(Stream $stream) {
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
public function getStream(): Stream {
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return (string)$this->stream;
|
||||
}
|
||||
|
||||
public static function fromFile(string $path): StreamContent {
|
||||
return new StreamContent(
|
||||
FileStream::openRead($path)
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromRequest(): StreamContent {
|
||||
return self::fromFile('php://input');
|
||||
}
|
||||
}
|
36
src/Http/Content/StringContent.php
Normal file
36
src/Http/Content/StringContent.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
// StringContent.php
|
||||
// Created: 2022-02-10
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Content;
|
||||
|
||||
use Stringable;
|
||||
|
||||
class StringContent implements Stringable, IHttpContent {
|
||||
private string $string;
|
||||
|
||||
public function __construct(string $string) {
|
||||
$this->string = $string;
|
||||
}
|
||||
|
||||
public function getString(): string {
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return $this->string;
|
||||
}
|
||||
|
||||
public static function fromObject(string $string): StringContent {
|
||||
return new StringContent($string);
|
||||
}
|
||||
|
||||
public static function fromFile(string $path): StringContent {
|
||||
return new StringContent(file_get_contents($path));
|
||||
}
|
||||
|
||||
public static function fromRequest(): StringContent {
|
||||
return self::fromFile('php://input');
|
||||
}
|
||||
}
|
71
src/Http/Headers/AcceptEncodingHeader.php
Normal file
71
src/Http/Headers/AcceptEncodingHeader.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
// AcceptEncodingHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class AcceptEncodingHeader {
|
||||
private array $encodings;
|
||||
private array $rejects;
|
||||
|
||||
public function __construct(array $encodings, array $rejects) {
|
||||
$this->encodings = $encodings;
|
||||
$this->rejects = $rejects;
|
||||
}
|
||||
|
||||
public function getEncodings(): array {
|
||||
return $this->encodings;
|
||||
}
|
||||
|
||||
public function getRejects(): array {
|
||||
return $this->rejects;
|
||||
}
|
||||
|
||||
public function accepts(string $algoName): int {
|
||||
$algoName = strtolower($algoName);
|
||||
foreach($this->encodings as $algo)
|
||||
if($algo->quality !== 0 && ($algo->name === $algoName || $algo->name === '*'))
|
||||
return $algo->quality;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function rejects(string $locale): bool {
|
||||
return in_array(strtolower($locale), $this->rejects)
|
||||
|| in_array('*', $this->rejects);
|
||||
}
|
||||
|
||||
public static function parse(array $lines): AcceptEncodingHeader {
|
||||
$parts = explode(',', (string)$lines[0]);
|
||||
$algos = [];
|
||||
$rejects = [];
|
||||
|
||||
foreach($parts as $part)
|
||||
try {
|
||||
$part = explode(';', $part);
|
||||
$algo = new stdClass;
|
||||
$algo->name = strtolower(trim(array_shift($part)));
|
||||
$algo->quality = 1;
|
||||
|
||||
foreach($part as $param) {
|
||||
if(substr($param, 0, 2) === 'q=') {
|
||||
$algo->quality = min(0, max(1, floatval(substr($param, 2))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$algos[] = $algo;
|
||||
if($algo->quality === 0)
|
||||
$rejects[] = $algo->name;
|
||||
} catch(InvalidArgumentException $ex) {}
|
||||
|
||||
if(empty($algos))
|
||||
throw new InvalidArgumentException('Failed to parse Accept-Encoding header.');
|
||||
|
||||
return new AcceptEncodingHeader($algos, $rejects);
|
||||
}
|
||||
}
|
63
src/Http/Headers/AcceptHeader.php
Normal file
63
src/Http/Headers/AcceptHeader.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
// AcceptHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Index\MediaType;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class AcceptHeader {
|
||||
private array $types;
|
||||
private array $rejects;
|
||||
|
||||
public function __construct(array $types, array $rejects) {
|
||||
$this->types = $types;
|
||||
$this->rejects = $rejects;
|
||||
}
|
||||
|
||||
public function getTypes(): array {
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
public function getRejects(): array {
|
||||
return $this->rejects;
|
||||
}
|
||||
|
||||
public function accepts(MediaType|string $mediaType): ?MediaType {
|
||||
foreach($this->types as $type)
|
||||
if($type->getQuality() < 0.1 && $type->equals($mediaType))
|
||||
return $type;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function rejects(MediaType|string $mediaType): bool {
|
||||
foreach($this->rejects as $reject)
|
||||
if($reject->equals($mediaType))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): AcceptHeader {
|
||||
$parts = explode(',', $header->getFirstLine());
|
||||
$types = [];
|
||||
$rejects = [];
|
||||
|
||||
foreach($parts as $part)
|
||||
try {
|
||||
$types[] = $type = MediaType::parse(trim($part));
|
||||
|
||||
if($type->getQuality() < 0.1)
|
||||
$rejects[] = $type;
|
||||
} catch(InvalidArgumentException $ex) {}
|
||||
|
||||
if(empty($types))
|
||||
throw new InvalidArgumentException('Failed to parse Accept header.');
|
||||
|
||||
return new AcceptHeader($types, $rejects);
|
||||
}
|
||||
}
|
72
src/Http/Headers/AcceptLanguageHeader.php
Normal file
72
src/Http/Headers/AcceptLanguageHeader.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
// AcceptLanguageHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class AcceptLanguageHeader {
|
||||
private array $langs;
|
||||
private array $rejects;
|
||||
|
||||
public function __construct(array $langs, array $rejects) {
|
||||
$this->langs = $langs;
|
||||
$this->rejects = $rejects;
|
||||
}
|
||||
|
||||
public function getLanguages(): array {
|
||||
return $this->langs;
|
||||
}
|
||||
|
||||
public function getRejects(): array {
|
||||
return $this->rejects;
|
||||
}
|
||||
|
||||
public function accepts(string $locale): int {
|
||||
$locale = strtolower($locale);
|
||||
foreach($this->langs as $lang)
|
||||
if($lang->quality !== 0 && ($lang->name === $locale || $lang->name === '*'))
|
||||
return $lang->quality;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function rejects(string $locale): bool {
|
||||
return in_array(strtolower($locale), $this->rejects)
|
||||
|| in_array('*', $this->rejects);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): AcceptLanguageHeader {
|
||||
$parts = explode(',', $header->getFirstLine());
|
||||
$langs = [];
|
||||
$rejects = [];
|
||||
|
||||
foreach($parts as $part)
|
||||
try {
|
||||
$part = explode(';', $part);
|
||||
$lang = new stdClass;
|
||||
$lang->name = strtolower(trim(array_shift($part)));
|
||||
$lang->quality = 1;
|
||||
|
||||
foreach($part as $param) {
|
||||
if(substr($param, 0, 2) === 'q=') {
|
||||
$lang->quality = min(0, max(1, floatval(substr($param, 2))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$langs[] = $lang;
|
||||
if($lang->quality === 0)
|
||||
$rejects[] = $lang->name;
|
||||
} catch(InvalidArgumentException $ex) {}
|
||||
|
||||
if(empty($langs))
|
||||
throw new InvalidArgumentException('Failed to parse Accept-Language header.');
|
||||
|
||||
return new AcceptLanguageHeader($langs, $rejects);
|
||||
}
|
||||
}
|
71
src/Http/Headers/AcceptTransferEncodingHeader.php
Normal file
71
src/Http/Headers/AcceptTransferEncodingHeader.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
// AcceptTransferEncodingHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class AcceptTransferEncodingHeader {
|
||||
private array $encodings;
|
||||
private array $rejects;
|
||||
|
||||
public function __construct(array $encodings, array $rejects) {
|
||||
$this->encodings = $encodings;
|
||||
$this->rejects = $rejects;
|
||||
}
|
||||
|
||||
public function getEncodings(): array {
|
||||
return $this->encodings;
|
||||
}
|
||||
|
||||
public function getRejects(): array {
|
||||
return $this->rejects;
|
||||
}
|
||||
|
||||
public function accepts(string $algoName): int {
|
||||
$algoName = strtolower($algoName);
|
||||
foreach($this->encodings as $algo)
|
||||
if($algo->quality !== 0 && ($algo->name === $algoName || $algo->name === '*'))
|
||||
return $algo->quality;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function rejects(string $locale): bool {
|
||||
return in_array(strtolower($locale), $this->rejects)
|
||||
|| in_array('*', $this->rejects);
|
||||
}
|
||||
|
||||
public static function parse(array $lines): AcceptTransferEncodingHeader {
|
||||
$parts = explode(',', (string)$lines[0]);
|
||||
$algos = [];
|
||||
$rejects = [];
|
||||
|
||||
foreach($parts as $part)
|
||||
try {
|
||||
$part = explode(';', $part);
|
||||
$algo = new stdClass;
|
||||
$algo->name = strtolower(trim(array_shift($part)));
|
||||
$algo->quality = 1;
|
||||
|
||||
foreach($part as $param) {
|
||||
if(substr($param, 0, 2) === 'q=') {
|
||||
$algo->quality = min(0, max(1, floatval(substr($param, 2))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$algos[] = $algo;
|
||||
if($algo->quality === 0)
|
||||
$rejects[] = $algo->name;
|
||||
} catch(InvalidArgumentException $ex) {}
|
||||
|
||||
if(empty($algos))
|
||||
throw new InvalidArgumentException('Failed to parse TE header.');
|
||||
|
||||
return new AcceptTransferEncodingHeader($algos, $rejects);
|
||||
}
|
||||
}
|
34
src/Http/Headers/AccessControlRequestHeadersHeader.php
Normal file
34
src/Http/Headers/AccessControlRequestHeadersHeader.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
// AccessControlRequestHeadersHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class AccessControlRequestHeadersHeader {
|
||||
private array $headers;
|
||||
|
||||
public function __construct(array $headers) {
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
public function getHeaders(): array {
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function isAllowed(string $header): bool {
|
||||
return in_array(strtolower($header), $this->headers);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): AccessControlRequestHeadersHeader {
|
||||
$raw = explode(',', $header->getFirstLine());
|
||||
$headers = [];
|
||||
|
||||
foreach($raw as $header)
|
||||
$headers[] = strtolower(trim($header));
|
||||
|
||||
return new AccessControlRequestHeadersHeader(array_unique($headers));
|
||||
}
|
||||
}
|
28
src/Http/Headers/AccessControlRequestMethodHeader.php
Normal file
28
src/Http/Headers/AccessControlRequestMethodHeader.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
// AccessControlRequestMethodHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class AccessControlRequestMethodHeader {
|
||||
private string $method;
|
||||
|
||||
public function __construct(string $method) {
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
public function getMethod(): string {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function isMethod(string $method): bool {
|
||||
return $this->method === strtolower($method);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): AccessControlRequestMethodHeader {
|
||||
return new AccessControlRequestMethodHeader(strtolower(trim($header->getFirstLine())));
|
||||
}
|
||||
}
|
35
src/Http/Headers/AuthorizationHeader.php
Normal file
35
src/Http/Headers/AuthorizationHeader.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
// AuthorizationHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class AuthorizationHeader {
|
||||
private string $method;
|
||||
private string $params;
|
||||
|
||||
public function __construct(string $method, string $params) {
|
||||
$this->method = $method;
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
public function getMethod(): string {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function isMethod(string $method): bool {
|
||||
return $this->method === strtolower($method);
|
||||
}
|
||||
|
||||
public function getParams(): string {
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): AuthorizationHeader {
|
||||
$parts = explode(' ', trim($header->getFirstLine()), 2);
|
||||
return new AuthorizationHeader($parts[0], $parts[1] ?? '');
|
||||
}
|
||||
}
|
47
src/Http/Headers/CacheControlHeader.php
Normal file
47
src/Http/Headers/CacheControlHeader.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
// CacheControlHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class CacheControlHeader {
|
||||
private array $params;
|
||||
|
||||
public function __construct(array $params) {
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
public function hasParam(string $name): bool {
|
||||
return isset($this->params[strtolower($name)]);
|
||||
}
|
||||
|
||||
public function getParam(string $name, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed {
|
||||
$name = strtolower($name);
|
||||
if(!isset($this->params[$name]))
|
||||
return null;
|
||||
return filter_var($this->params[$name] ?? null, $filter, $options);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): CacheControlHeader {
|
||||
$raw = explode(',', strtolower($header->getFirstLine()));
|
||||
$params = [];
|
||||
|
||||
foreach($raw as $param) {
|
||||
$parts = explode('=', $param, 2);
|
||||
$name = trim($parts[0]);
|
||||
$value = trim($parts[1] ?? '');
|
||||
if($value === '')
|
||||
$value = true;
|
||||
$params[$name] = $value;
|
||||
}
|
||||
|
||||
return new CacheControlHeader($params);
|
||||
}
|
||||
}
|
35
src/Http/Headers/ConnectionHeader.php
Normal file
35
src/Http/Headers/ConnectionHeader.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
// ConnectionHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ConnectionHeader {
|
||||
private array $params;
|
||||
|
||||
public function __construct(array $params) {
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
public function getParams(): array {
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
public function hasParam(string $directive): bool {
|
||||
return in_array($directive, $this->params);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ConnectionHeader {
|
||||
return new ConnectionHeader(
|
||||
array_unique(
|
||||
array_map(
|
||||
fn($directive) => trim($directive),
|
||||
explode(',', strtolower($header->getFirstLine()))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
29
src/Http/Headers/ContentEncodingHeader.php
Normal file
29
src/Http/Headers/ContentEncodingHeader.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
// ContentEncodingHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ContentEncodingHeader {
|
||||
private array $encodings;
|
||||
|
||||
public function __construct(array $encodings) {
|
||||
$this->encodings = $encodings;
|
||||
}
|
||||
|
||||
public function getEncodings(): array {
|
||||
return $this->encodings;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ContentEncodingHeader {
|
||||
return new ContentEncodingHeader(
|
||||
array_map(
|
||||
fn($directive) => trim($directive),
|
||||
explode(',', strtolower($header->getFirstLine()))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
35
src/Http/Headers/ContentLanguageHeader.php
Normal file
35
src/Http/Headers/ContentLanguageHeader.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
// ContentLanguageHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ContentLanguageHeader {
|
||||
private array $langs;
|
||||
|
||||
public function __construct(array $langs) {
|
||||
$this->langs = $langs;
|
||||
}
|
||||
|
||||
public function getLanguages(): array {
|
||||
return $this->langs;
|
||||
}
|
||||
|
||||
public function hasLanguage(string $lang): bool {
|
||||
return in_array(strtolower($lang), $this->langs);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ContentLanguageHeader {
|
||||
return new ContentLanguageHeader(
|
||||
array_unique(
|
||||
array_map(
|
||||
fn($directive) => trim($directive),
|
||||
explode(',', strtolower($header->getFirstLine()))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
24
src/Http/Headers/ContentLengthHeader.php
Normal file
24
src/Http/Headers/ContentLengthHeader.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
// ContentLengthHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ContentLengthHeader {
|
||||
private int $length;
|
||||
|
||||
public function __construct(int $length) {
|
||||
$this->length = $length;
|
||||
}
|
||||
|
||||
public function getLength(): int {
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ContentLengthHeader {
|
||||
return new ContentLengthHeader(intval($header->getFirstLine()));
|
||||
}
|
||||
}
|
24
src/Http/Headers/ContentLocationHeader.php
Normal file
24
src/Http/Headers/ContentLocationHeader.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
// ContentLocationHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ContentLocationHeader {
|
||||
private string $location;
|
||||
|
||||
public function __construct(string $location) {
|
||||
$this->location = $location;
|
||||
}
|
||||
|
||||
public function getLocation(): string {
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ContentLocationHeader {
|
||||
return new ContentLocationHeader(trim($header->getFirstLine()));
|
||||
}
|
||||
}
|
74
src/Http/Headers/ContentRangeHeader.php
Normal file
74
src/Http/Headers/ContentRangeHeader.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
// ContentRangeHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ContentRangeHeader {
|
||||
private string $unit;
|
||||
private int $rangeStart = -1;
|
||||
private int $rangeEnd = -1;
|
||||
private int $size = -1;
|
||||
|
||||
public function __construct(string $unit, int $rangeStart, int $rangeEnd, int $size) {
|
||||
$this->unit = $unit;
|
||||
$this->rangeStart = $rangeStart;
|
||||
$this->rangeEnd = $rangeEnd;
|
||||
$this->size = $size;
|
||||
}
|
||||
|
||||
public function getUnit(): string {
|
||||
return $this->unit;
|
||||
}
|
||||
|
||||
public function isBytes(): bool {
|
||||
return $this->unit === 'bytes';
|
||||
}
|
||||
|
||||
public function getSize(): int {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function isUnknownSize(): bool {
|
||||
return $this->size < 0;
|
||||
}
|
||||
|
||||
public function isUnspecifiedRange(): bool {
|
||||
return $this->rangeStart < 0 || $this->rangeEnd < 0;
|
||||
}
|
||||
|
||||
public function getRangeStart(): int {
|
||||
return $this->rangeStart;
|
||||
}
|
||||
|
||||
public function getRangeEnd(): int {
|
||||
return $this->rangeEnd;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ContentRangeHeader {
|
||||
$parts = explode(' ', trim($header->getFirstLine()), 2);
|
||||
$unit = array_shift($parts);
|
||||
|
||||
$parts = explode('/', $parts[1] ?? '', 2);
|
||||
|
||||
$size = trim($parts[1] ?? '*');
|
||||
if($size !== '*')
|
||||
$size = max(0, intval($size));
|
||||
else
|
||||
$size = -1;
|
||||
|
||||
$range = trim($parts[0]);
|
||||
|
||||
if($range !== '*') {
|
||||
$parts = explode('-', $range, 2);
|
||||
$rangeStart = intval(trim($parts[0]));
|
||||
$rangeEnd = intval(trim($parts[1] ?? '0'));
|
||||
} else
|
||||
$rangeStart = $rangeEnd = -1;
|
||||
|
||||
return new ContentRangeHeader($unit, $rangeStart, $rangeEnd, $size);
|
||||
}
|
||||
}
|
25
src/Http/Headers/ContentTypeHeader.php
Normal file
25
src/Http/Headers/ContentTypeHeader.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
// ContentTypeHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\MediaType;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ContentTypeHeader {
|
||||
private MediaType $mediaType;
|
||||
|
||||
public function __construct(MediaType $mediaType) {
|
||||
$this->mediaType = $mediaType;
|
||||
}
|
||||
|
||||
public function getMediaType(): MediaType {
|
||||
return $this->mediaType;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ContentTypeHeader {
|
||||
return new ContentTypeHeader(MediaType::parse($header->getFirstLine()));
|
||||
}
|
||||
}
|
47
src/Http/Headers/CookieHeader.php
Normal file
47
src/Http/Headers/CookieHeader.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
// CookieHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\UrlEncoding;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class CookieHeader {
|
||||
private array $cookies;
|
||||
|
||||
public function __construct(array $cookies) {
|
||||
$this->cookies = $cookies;
|
||||
}
|
||||
|
||||
public function getCookies(): array {
|
||||
return $this->cookies;
|
||||
}
|
||||
|
||||
public function hasCookie(string $name): bool {
|
||||
return isset($this->cookies[$name]);
|
||||
}
|
||||
|
||||
public function getCookie(string $name, int $filter = FILTER_DEFAULT, array|int $options = 0): mixed {
|
||||
if(!isset($this->cookies[$name]))
|
||||
return null;
|
||||
return filter_var($this->cookies[$name] ?? null, $filter, $options);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): CookieHeader {
|
||||
$cookies = [];
|
||||
$lines = $header->getLines();
|
||||
|
||||
foreach($lines as $line) {
|
||||
$parts = explode(';', $line);
|
||||
|
||||
foreach($parts as $part) {
|
||||
$kvp = explode('=', $part, 2);
|
||||
$cookies[rawurldecode($kvp[0])] = rawurldecode($kvp[1] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
return new CookieHeader($cookies);
|
||||
}
|
||||
}
|
33
src/Http/Headers/DNTHeader.php
Normal file
33
src/Http/Headers/DNTHeader.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
// DNTHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-14
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class DNTHeader {
|
||||
private ?bool $state;
|
||||
|
||||
public function __construct(?bool $state) {
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
public function getState(): ?bool {
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function hasNoPreference(): bool {
|
||||
return $this->state === null;
|
||||
}
|
||||
|
||||
public function allowsTracking(): bool {
|
||||
return $this->state !== false;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): DNTHeader {
|
||||
$value = $header->getFirstLine();
|
||||
return new DNTHeader($value === 'null' ? null : ($value === '1'));
|
||||
}
|
||||
}
|
25
src/Http/Headers/DateHeader.php
Normal file
25
src/Http/Headers/DateHeader.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
// DateHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-14
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class DateHeader {
|
||||
private DateTime $dateTime;
|
||||
|
||||
public function __construct(DateTime $dateTime) {
|
||||
$this->dateTime = $dateTime;
|
||||
}
|
||||
|
||||
public function getDateTime(): DateTime {
|
||||
return $this->dateTime;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): DateHeader {
|
||||
return new DateHeader(DateTime::createFromFormat(\DateTimeInterface::RFC7231, $header->getFirstLine()));
|
||||
}
|
||||
}
|
31
src/Http/Headers/ExpectHeader.php
Normal file
31
src/Http/Headers/ExpectHeader.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
// ExpectHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-14
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ExpectHeader {
|
||||
private int $statusCode;
|
||||
private string $statusText;
|
||||
|
||||
public function __construct(int $statusCode, string $statusText) {
|
||||
$this->statusCode = $statusCode;
|
||||
$this->statusText = $statusText;
|
||||
}
|
||||
|
||||
public function getStatusCode(): int {
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
public function getStatusText(): string {
|
||||
return $this->statusText;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ExpectHeader {
|
||||
$value = explode('-', $header->getFirstLine(), 2);
|
||||
return new ExpectHeader(intval($value[0]), $value[1] ?? '');
|
||||
}
|
||||
}
|
24
src/Http/Headers/FromHeader.php
Normal file
24
src/Http/Headers/FromHeader.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
// FromHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class FromHeader {
|
||||
private string $email;
|
||||
|
||||
public function __construct(string $email) {
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
public function getEMailAddress(): string {
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): FromHeader {
|
||||
return new FromHeader($header->getFirstLine());
|
||||
}
|
||||
}
|
35
src/Http/Headers/HostHeader.php
Normal file
35
src/Http/Headers/HostHeader.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
// HostHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-14
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class HostHeader {
|
||||
private string $host;
|
||||
private int $port;
|
||||
|
||||
public function __construct(string $host, int $port) {
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
}
|
||||
|
||||
public function getHost(): string {
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function hasPort(): bool {
|
||||
return $this->port > 0;
|
||||
}
|
||||
|
||||
public function getPort(): int {
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): HostHeader {
|
||||
$parts = explode(':', $header->getFirstLine(), 2);
|
||||
return new HostHeader($parts[0], intval($parts[1] ?? '-1'));
|
||||
}
|
||||
}
|
46
src/Http/Headers/IfMatchHeader.php
Normal file
46
src/Http/Headers/IfMatchHeader.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
// IfMatchHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class IfMatchHeader {
|
||||
private array $tags;
|
||||
private int $count;
|
||||
|
||||
public function __construct(array $tags) {
|
||||
$this->tags = $tags;
|
||||
$this->count = count($tags);
|
||||
}
|
||||
|
||||
public function getTags(): array {
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
public function hasTag(string $tag): bool {
|
||||
if($this->count === 1 && $this->tags[0] === '*')
|
||||
return true;
|
||||
return in_array((string)$tag, $this->tags);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): IfMatchHeader {
|
||||
$tags = [];
|
||||
$rawTags = array_unique(
|
||||
array_map(
|
||||
fn($directive) => trim($directive),
|
||||
explode(',', strtolower($header->getFirstLine()))
|
||||
)
|
||||
);
|
||||
|
||||
foreach($rawTags as $raw) {
|
||||
if(substr($raw, 0, 3) === 'W/"')
|
||||
continue;
|
||||
$tags[] = trim($raw, '"');
|
||||
}
|
||||
|
||||
return new IfMatchHeader($tags);
|
||||
}
|
||||
}
|
30
src/Http/Headers/IfModifiedSinceHeader.php
Normal file
30
src/Http/Headers/IfModifiedSinceHeader.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
// IfModifiedSinceHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-14
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Index\DateTime;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class IfModifiedSinceHeader {
|
||||
private DateTime $dateTime;
|
||||
|
||||
public function __construct(DateTime $dateTime) {
|
||||
$this->dateTime = $dateTime;
|
||||
}
|
||||
|
||||
public function getDateTime(): DateTime {
|
||||
return $this->dateTime;
|
||||
}
|
||||
|
||||
public function isLessThanOrEqual(DateTimeInterface $dateTime): bool {
|
||||
return $this->dateTime->isLessThanOrEqual($dateTime);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): IfModifiedSinceHeader {
|
||||
return new IfModifiedSinceHeader(DateTime::createFromFormat(\DateTimeInterface::RFC7231, $header->getFirstLine()));
|
||||
}
|
||||
}
|
46
src/Http/Headers/IfNoneMatchHeader.php
Normal file
46
src/Http/Headers/IfNoneMatchHeader.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
// IfNoneMatchHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class IfNoneMatchHeader {
|
||||
private array $tags;
|
||||
private int $count;
|
||||
|
||||
public function __construct(array $tags) {
|
||||
$this->tags = $tags;
|
||||
$this->count = count($tags);
|
||||
}
|
||||
|
||||
public function getTags(): array {
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
public function hasTag(string $tag): bool {
|
||||
if($this->count === 1 && $this->tags[0] === '*')
|
||||
return true;
|
||||
return in_array($tag, $this->tags);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): IfNoneMatchHeader {
|
||||
$tags = [];
|
||||
$rawTags = array_unique(
|
||||
array_map(
|
||||
fn($directive) => trim($directive),
|
||||
explode(',', strtolower($header->getFirstLine()))
|
||||
)
|
||||
);
|
||||
|
||||
foreach($rawTags as $raw) {
|
||||
if(str_starts_with($raw, 'W/"'))
|
||||
$raw = substr($raw, 2);
|
||||
$tags[] = trim($raw, '"');
|
||||
}
|
||||
|
||||
return new IfNoneMatchHeader($tags);
|
||||
}
|
||||
}
|
58
src/Http/Headers/IfRangeHeader.php
Normal file
58
src/Http/Headers/IfRangeHeader.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
// IfRangeHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Index\DateTime;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class IfRangeHeader {
|
||||
private ?string $tag;
|
||||
private ?DateTime $dateTime;
|
||||
|
||||
public function __construct(?string $tag, ?DateTime $dateTime) {
|
||||
$this->tag = $tag;
|
||||
$this->dateTime = $dateTime;
|
||||
}
|
||||
|
||||
public function hasTag(): bool {
|
||||
return $this->tag !== null;
|
||||
}
|
||||
|
||||
public function getTag(): string {
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
public function hasDateTime(): bool {
|
||||
return $this->dateTime !== null;
|
||||
}
|
||||
|
||||
public function getDateTime(): DateTime {
|
||||
return $this->dateTime;
|
||||
}
|
||||
|
||||
public function matches(string|DateTimeInterface $other): bool {
|
||||
if($this->hasTag() && is_string($other)) {
|
||||
if(str_starts_with($other, 'W/"'))
|
||||
return false;
|
||||
return $this->tag === $other;
|
||||
}
|
||||
|
||||
if($this->hasDateTime() && $other instanceof DateTimeInterface)
|
||||
return $this->dateTime->isLessThanOrEqual($other);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): IfRangeHeader {
|
||||
$line = $header->getFirstLine();
|
||||
|
||||
if($line[0] !== '"' && !str_starts_with($line, 'W/"'))
|
||||
return new IfRangeHeader(null, DateTime::createFromFormat(\DateTimeInterface::RFC7231, $line));
|
||||
|
||||
return new IfRangeHeader($line, null);
|
||||
}
|
||||
}
|
30
src/Http/Headers/IfUnmodifiedSinceHeader.php
Normal file
30
src/Http/Headers/IfUnmodifiedSinceHeader.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
// IfUnmodifiedSinceHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-14
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Index\DateTime;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class IfUnmodifiedSinceHeader {
|
||||
private DateTime $dateTime;
|
||||
|
||||
public function __construct(DateTime $dateTime) {
|
||||
$this->dateTime = $dateTime;
|
||||
}
|
||||
|
||||
public function getDateTime(): DateTime {
|
||||
return $this->dateTime;
|
||||
}
|
||||
|
||||
public function isMoreThan(DateTimeInterface $dateTime): bool {
|
||||
return $this->dateTime->isMoreThan($dateTime);
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): IfUnmodifiedSinceHeader {
|
||||
return new IfUnmodifiedSinceHeader(DateTime::createFromFormat(\DateTimeInterface::RFC7231, $header->getFirstLine()));
|
||||
}
|
||||
}
|
43
src/Http/Headers/KeepAliveHeader.php
Normal file
43
src/Http/Headers/KeepAliveHeader.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
// KeepAliveHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-15
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class KeepAliveHeader {
|
||||
private int $timeOut;
|
||||
private int $max;
|
||||
|
||||
public function __construct(int $timeOut, int $max) {
|
||||
$this->timeOut = $timeOut;
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function getTimeOut(): int {
|
||||
return $this->timeOut;
|
||||
}
|
||||
|
||||
public function getMax(): int {
|
||||
return $this->max;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): KeepAliveHeader {
|
||||
$params = explode(',', $header->getFirstLine());
|
||||
$timeOut = -1;
|
||||
$max = -1;
|
||||
|
||||
foreach($params as $param) {
|
||||
if(str_starts_with($param, 'timeout='))
|
||||
$timeOut = intval(substr($param, 8));
|
||||
elseif(str_starts_with($param, 'max='))
|
||||
$max = intval(substr($param, 4));
|
||||
if($timeOut >= 0 && $max >= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return new KeepAliveHeader($timeOut, $max);
|
||||
}
|
||||
}
|
25
src/Http/Headers/LastModifiedHeader.php
Normal file
25
src/Http/Headers/LastModifiedHeader.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
// LastModifiedHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-14
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class LastModifiedHeader {
|
||||
private DateTime $dateTime;
|
||||
|
||||
public function __construct(DateTime $dateTime) {
|
||||
$this->dateTime = $dateTime;
|
||||
}
|
||||
|
||||
public function getDateTime(): DateTime {
|
||||
return $this->dateTime;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): LastModifiedHeader {
|
||||
return new LastModifiedHeader(DateTime::createFromFormat(\DateTimeInterface::RFC7231, $header->getFirstLine()));
|
||||
}
|
||||
}
|
61
src/Http/Headers/OriginHeader.php
Normal file
61
src/Http/Headers/OriginHeader.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
// OriginHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class OriginHeader {
|
||||
private bool $isNull;
|
||||
private string $scheme;
|
||||
private string $host;
|
||||
private int $port;
|
||||
|
||||
public function __construct(bool $isNull, string $scheme, string $host, int $port) {
|
||||
$this->isNull = $isNull;
|
||||
$this->scheme = $scheme;
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
}
|
||||
|
||||
public function isNull(): bool {
|
||||
return $this->isNull;
|
||||
}
|
||||
|
||||
public function isValid(): bool {
|
||||
return $this->isNull || ($this->scheme !== '' && $this->host !== '');
|
||||
}
|
||||
|
||||
public function getScheme(): string {
|
||||
return $this->scheme;
|
||||
}
|
||||
|
||||
public function getHost(): string {
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function hasPort(): bool {
|
||||
return $this->port > 0;
|
||||
}
|
||||
|
||||
public function getPort(): int {
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): OriginHeader {
|
||||
$line = $header->getFirstLine();
|
||||
|
||||
if($line === 'null')
|
||||
return new OriginHeader(true, '', '', -1);
|
||||
|
||||
return new OriginHeader(
|
||||
false,
|
||||
parse_url($line, PHP_URL_SCHEME) ?? '',
|
||||
parse_url($line, PHP_URL_HOST) ?? '',
|
||||
parse_url($line, PHP_URL_PORT) ?? -1
|
||||
);
|
||||
}
|
||||
}
|
24
src/Http/Headers/PragmaHeader.php
Normal file
24
src/Http/Headers/PragmaHeader.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
// PragmaHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-14
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class PragmaHeader {
|
||||
private bool $noCache;
|
||||
|
||||
public function __construct(bool $noCache) {
|
||||
$this->noCache = $noCache;
|
||||
}
|
||||
|
||||
public function shouldByPassCache(): bool {
|
||||
return $this->noCache;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): PragmaHeader {
|
||||
return new PragmaHeader($header->getFirstLine() === 'no-cache');
|
||||
}
|
||||
}
|
35
src/Http/Headers/ProxyAuthorizationHeader.php
Normal file
35
src/Http/Headers/ProxyAuthorizationHeader.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
// ProxyAuthorizationHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class ProxyAuthorizationHeader {
|
||||
private string $method;
|
||||
private string $params;
|
||||
|
||||
public function __construct(string $method, string $params) {
|
||||
$this->method = $method;
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
public function getMethod(): string {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function isMethod(string $method): bool {
|
||||
return $this->method === strtolower($method);
|
||||
}
|
||||
|
||||
public function getParams(): string {
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): ProxyAuthorizationHeader {
|
||||
$parts = explode(' ', trim($header->getFirstLine()), 2);
|
||||
return new ProxyAuthorizationHeader($parts[0], $parts[1] ?? '');
|
||||
}
|
||||
}
|
61
src/Http/Headers/RangeHeader.php
Normal file
61
src/Http/Headers/RangeHeader.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
// RangeHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use stdClass;
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class RangeHeader {
|
||||
private string $unit;
|
||||
private array $ranges;
|
||||
|
||||
public function __construct(string $unit, array $ranges) {
|
||||
$this->unit = $unit;
|
||||
$this->ranges = $ranges;
|
||||
}
|
||||
|
||||
public function getUnit(): string {
|
||||
return $this->unit;
|
||||
}
|
||||
|
||||
public function isBytes(): bool {
|
||||
return $this->unit === 'bytes';
|
||||
}
|
||||
|
||||
public function getRanges(): array {
|
||||
return $this->ranges;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): RangeHeader {
|
||||
$parts = explode('=', trim($header->getFirstLine()), 2);
|
||||
$unit = trim($parts[0]);
|
||||
|
||||
$ranges = [];
|
||||
$rawRanges = explode(',', $parts[1] ?? '', 2);
|
||||
|
||||
foreach($rawRanges as $raw) {
|
||||
$raw = explode('-', trim($raw), 2);
|
||||
$start = trim($raw[0]);
|
||||
$end = trim($raw[1] ?? '');
|
||||
|
||||
$ranges[] = $range = new stdClass;
|
||||
|
||||
if($start === '' && is_numeric($end)) {
|
||||
$range->type = 'suffix-length';
|
||||
$range->length = intval($end);
|
||||
} elseif(is_numeric($start) && $end === '') {
|
||||
$range->type = 'start';
|
||||
$range->start = intval($start);
|
||||
} else {
|
||||
$range->type = 'range';
|
||||
$range->start = intval($start);
|
||||
$range->end = intval($end);
|
||||
}
|
||||
}
|
||||
|
||||
return new RangeHeader($unit, $ranges);
|
||||
}
|
||||
}
|
24
src/Http/Headers/RefererHeader.php
Normal file
24
src/Http/Headers/RefererHeader.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
// RefererHeader.php
|
||||
// Created: 2022-02-14
|
||||
// Updated: 2022-02-27
|
||||
|
||||
namespace Index\Http\Headers;
|
||||
|
||||
use Index\Http\HttpHeader;
|
||||
|
||||
class RefererHeader {
|
||||
private string $url;
|
||||
|
||||
public function __construct(string $url) {
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
public function getSource(): string {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public static function parse(HttpHeader $header): RefererHeader {
|
||||
return new RefererHeader(trim($header->getFirstLine()));
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue