diff --git a/Documentation/docs/cli/index.md b/Documentation/docs/cli/index.md index 6ce33a15..d9cf653f 100644 --- a/Documentation/docs/cli/index.md +++ b/Documentation/docs/cli/index.md @@ -1,38 +1,38 @@ -# CLI Toolkit -Gishiki uses a CLI toolkit to speed up development and code generation. - -The executable resides under the ./vendor/bin/ directory, and is called -gishiki. - -It accept an arbitrary number of arguments, but the first one is the action -you want to perform: - -```sh -./vendor/bin/gishiki [param 1] [param2] [param3] #and so on..... -``` - -The number of parameters following the action depends on the action you want to -perform. - - -## Application Creation -To bootstrap a new application the command is fixed: - -```sh -./vendor/bin/gishiki init -``` - -This will create a basic and empty application that uses an sqlite3 database, -has a randomly-generated RSA private key, and an empty SQLite3 database. - -The application can be executed immediately if ext-pdo_sqlite is installed. - - -## Controller Creation -To bootstrap a new controller the command requires controller name: - -```sh -./vendor/bin/gishiki new-controller ControllerName -``` - +# CLI Toolkit +Gishiki uses a CLI toolkit to speed up development and code generation. + +The executable resides under the ./vendor/bin/ directory, and is called +gishiki. + +It accept an arbitrary number of arguments, but the first one is the action +you want to perform: + +```sh +./vendor/bin/gishiki [param 1] [param2] [param3] #and so on..... +``` + +The number of parameters following the action depends on the action you want to +perform. + + +## Application Creation +To bootstrap a new application the command is fixed: + +```sh +./vendor/bin/gishiki init +``` + +This will create a basic and empty application that uses an sqlite3 database, +has a randomly-generated RSA private key, and an empty SQLite3 database. + +The application can be executed immediately if ext-pdo_sqlite is installed. + + +## Controller Creation +To bootstrap a new controller the command requires controller name: + +```sh +./vendor/bin/gishiki new-controller ControllerName +``` + This will create a basic controller with a small example piece of code. \ No newline at end of file diff --git a/Documentation/docs/usage/CRUD.md b/Documentation/docs/usage/CRUD.md index 58b9cbbe..1a42329c 100644 --- a/Documentation/docs/usage/CRUD.md +++ b/Documentation/docs/usage/CRUD.md @@ -1,68 +1,68 @@ -# CRUD -The acronym __CRUD__ stands for Create, Read, Update and Delete: those are names -of main operations you will be allowed (directly or indirectly) to perform on -databases that are supporting your application. - -__Notice that:__ if you only need read permissions from a database such as -PostgreSQL or MySQL, you do __NOT__ need to use an user with full access. - -## Create -The creation of a new *document*/*row* either starts from a __CollectionInterface__, -such as __SerializableCollection__ or a native PHP array. - -The function that has to be called is __create__ that also requires the name of -the *table*/*collection* to be affected: - -```php -use Gishiki\Database\DatabaseManager; - -$connection = DatabaseManager::retrieve('connectionName'); - -$idOfNewDocument = $connection->create('tbname', new SerializableCollection([ - 'name' => $name, - 'surname' => $surname, - 'nickname' => $nickname, - 'password' => $hash //it is NOT good to store plain passwords -])); -``` - -Where the name of the connection is the same name in the application [configuration](configuration.md). - -## Delete -To delete a restricted set of *documents*/*rows* from a *table*/*collection* -you have to call, on the desired database connection the __delete__ function. - -The delete function needs the name of the table/collection to be affected and -a valid instance of SelectionCriteria: - -```php -use Gishiki\Database\DatabaseManager; -use Gishiki\Database\Runtime\SelectionCriteria; -use Gishiki\Database\Runtime\FieldRelation; - -$connection = DatabaseManager::retrieve('connectionName'); - -$connection->delete('tbname', SelectionCriteria::select([ - 'nickname' => $nickname - ])->OrWhere('email', FieldRelation::EQUAL, $email) - ); -``` - -You can also delete __EVERY__ *documents*/*rows* from a *table*/*collection* -using the __deleteAll__ function. - -The delete function only needs the name of the table/collection to be affected: - -```php -use Gishiki\Database\DatabaseManager; -use Gishiki\Database\Runtime\SelectionCriteria; -use Gishiki\Database\Runtime\FieldRelation; - -$connection = DatabaseManager::retrieve('connectionName'); - -$connection->deleteAll('tbname'); -``` - -__Note that:__ calling the delete function, passing an empty SelectionCriteria -object has the same effect of calling deleteAll, however deleteAll will perform +# CRUD +The acronym __CRUD__ stands for Create, Read, Update and Delete: those are names +of main operations you will be allowed (directly or indirectly) to perform on +databases that are supporting your application. + +__Notice that:__ if you only need read permissions from a database such as +PostgreSQL or MySQL, you do __NOT__ need to use an user with full access. + +## Create +The creation of a new *document*/*row* either starts from a __CollectionInterface__, +such as __SerializableCollection__ or a native PHP array. + +The function that has to be called is __create__ that also requires the name of +the *table*/*collection* to be affected: + +```php +use Gishiki\Database\DatabaseManager; + +$connection = DatabaseManager::retrieve('connectionName'); + +$idOfNewDocument = $connection->create('tbname', new SerializableCollection([ + 'name' => $name, + 'surname' => $surname, + 'nickname' => $nickname, + 'password' => $hash //it is NOT good to store plain passwords +])); +``` + +Where the name of the connection is the same name in the application [configuration](configuration.md). + +## Delete +To delete a restricted set of *documents*/*rows* from a *table*/*collection* +you have to call, on the desired database connection the __delete__ function. + +The delete function needs the name of the table/collection to be affected and +a valid instance of SelectionCriteria: + +```php +use Gishiki\Database\DatabaseManager; +use Gishiki\Database\Runtime\SelectionCriteria; +use Gishiki\Database\Runtime\FieldRelation; + +$connection = DatabaseManager::retrieve('connectionName'); + +$connection->delete('tbname', SelectionCriteria::select([ + 'nickname' => $nickname + ])->OrWhere('email', FieldRelation::EQUAL, $email) + ); +``` + +You can also delete __EVERY__ *documents*/*rows* from a *table*/*collection* +using the __deleteAll__ function. + +The delete function only needs the name of the table/collection to be affected: + +```php +use Gishiki\Database\DatabaseManager; +use Gishiki\Database\Runtime\SelectionCriteria; +use Gishiki\Database\Runtime\FieldRelation; + +$connection = DatabaseManager::retrieve('connectionName'); + +$connection->deleteAll('tbname'); +``` + +__Note that:__ calling the delete function, passing an empty SelectionCriteria +object has the same effect of calling deleteAll, however deleteAll will perform a little better! \ No newline at end of file diff --git a/Documentation/docs/usage/database.md b/Documentation/docs/usage/database.md index acef06c2..912aa468 100644 --- a/Documentation/docs/usage/database.md +++ b/Documentation/docs/usage/database.md @@ -1,53 +1,53 @@ -# Database - -Gishiki is developed to reflect the MVC pattern: this means that the data lifecycle -is a foundamental characteristic within the framework! - -Data persistence, coherence and integrity is managed by the database manager. - -To connect a database manager you have to edit the active [configuration](configuration.md). - -There isn't a limit to the number of database connection, but each one __MUST__ -have a name, and there __CANNOT__ be two connections with the same name. - - -## Connecting Database - -A database connection have the following form: - -``` -adapter://adapter_manageable_conenction_query -``` - -where the connection query is a string that the adapter can parse. - -### MongoDB - -A MongoDB connection can be enstabilished by using the mongodb adapter bundled -with Gishiki. - -The MongoDB adapter uses the mongodb php native extension: Composer calls it -[ext-mongodb](https://pecl.php.net/package/mongodb): - -``` -mongodb://username:password@host:port/dbname -``` - - -## Differences between databases - -Each database manager has different characteristics: Gishiki aims to preserve -strong points of each one, but miracles are not possibles: everything comes to -a price. - -Following are __RULES__ you __MUST__ follow when designing database tables. - - - The name must be the plural form of the name of object to store - - The name must be written in underscore_case with no UPPER characters - - The unique id field (when possible) must be called _id - - -## Operations on Databases - -To understand how to interact with the database you have to read the [CRUD](CRUD.md) +# Database + +Gishiki is developed to reflect the MVC pattern: this means that the data lifecycle +is a foundamental characteristic within the framework! + +Data persistence, coherence and integrity is managed by the database manager. + +To connect a database manager you have to edit the active [configuration](configuration.md). + +There isn't a limit to the number of database connection, but each one __MUST__ +have a name, and there __CANNOT__ be two connections with the same name. + + +## Connecting Database + +A database connection have the following form: + +``` +adapter://adapter_manageable_conenction_query +``` + +where the connection query is a string that the adapter can parse. + +### MongoDB + +A MongoDB connection can be enstabilished by using the mongodb adapter bundled +with Gishiki. + +The MongoDB adapter uses the mongodb php native extension: Composer calls it +[ext-mongodb](https://pecl.php.net/package/mongodb): + +``` +mongodb://username:password@host:port/dbname +``` + + +## Differences between databases + +Each database manager has different characteristics: Gishiki aims to preserve +strong points of each one, but miracles are not possibles: everything comes to +a price. + +Following are __RULES__ you __MUST__ follow when designing database tables. + + - The name must be the plural form of the name of object to store + - The name must be written in underscore_case with no UPPER characters + - The unique id field (when possible) must be called _id + + +## Operations on Databases + +To understand how to interact with the database you have to read the [CRUD](CRUD.md) chapter of this tutorial. \ No newline at end of file diff --git a/Documentation/docs/usage/response.md b/Documentation/docs/usage/response.md index 3cc05154..c61f61d3 100644 --- a/Documentation/docs/usage/response.md +++ b/Documentation/docs/usage/response.md @@ -1,189 +1,189 @@ -# Response - -The __Gishiki\HttpKernel\Response__ class is used to fully represent an HTTP response. - -The Response class is PSR-7 conformant and follows that specification sheet. - -Each [Request](request.md) triggers the generation of a response. - -That response is automatically sent back to the client at the end of the -application lifetime. - -The main target of an application is __editing__ that response before the departure -of that response. - -An HTTP response is made of two parts: - - - Response __header__ - - Response __body__ - -Those parts and steps to generate them are described later on this document. - - -## Response Header - -Every HTTP response __MUST__ contains an header. - -An HTTP header have a bare minimum structure that comprises the HTTP revision, -the status code and the message associated to the status code. - -Since each status code have its own predefined status phrase (like 404 not found, -500 internal server error, 200 OK and so on) when the status code is changed the -status phrase is automatically changed by the framework. - -That can be done calling the __withStatus__ function: - -```php -use Gishiki\Core\Route; -use Gishiki\HttpKernel\Request; -use Gishiki\HttpKernel\Response; -use Gishiki\Algorithms\Collections\SerializableCollection; - -Route::any(Route::NOT_FOUND, - function (Request $request, Response &$response, SerializableCollection &$arguments) -{ - //the response code - phrase will be 404 - Not Found - $response->withStatus(404); -}); -``` - -You can manually change the status phrase, but you are discouraged from doing such -thing with standard status code! - -What you can do is using it to send a strong signal to a bad client: - -```php -use Gishiki\Core\Route; -use Gishiki\HttpKernel\Request; -use Gishiki\HttpKernel\Response; -use Gishiki\Algorithms\Collections\SerializableCollection; - -Route::any("/complex", - function (Request $request, Response &$response, SerializableCollection &$arguments) -{ - //numberOfRequests is the number of requests that a client has sent today - if ($numberOfRequests > 5) { - //perform the complex operation (may stress the system) - action(); - } else { - //stop flooding my servers! - $response->withStatus(666, 'FUCK YOU!'); - } -}); -``` - -Sorry for the bad language, that was only intended to help me to give you a (real :D) -example of usage. - - -## Response Header Details - -Each response can contains a lot of details about itselfs like the length of the -content or the type of the content. - -Each 'response detail' is a collection of values binded to a key which is the name -of the property. - -In order to edit the value of a property you have to use the __withHeader__ function: - -```php -use Gishiki\Core\Route; -use Gishiki\HttpKernel\Request; -use Gishiki\HttpKernel\Response; -use Gishiki\Algorithms\Collections\SerializableCollection; - -Route::any("/complex", - function (Request $request, Response &$response, SerializableCollection &$arguments) -{ - $request->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); -}); -``` - -If you are unsure on how to use this feature you should read more about http response [header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers). - - -## Response Body - -The body of the response is the main part of the response: the __content__ of the -response. - -For example if the response is an html content the response body is what the -user calls *the webpage*. - -To directly modify the response body you can use the __write__ function: - -```php -use Gishiki\Core\Route; -use Gishiki\HttpKernel\Request; -use Gishiki\HttpKernel\Response; -use Gishiki\Algorithms\Collections\SerializableCollection; - -Route::any("/", - function (Request $request, Response &$response, SerializableCollection &$arguments) -{ - $content = << - - My webpage - - -

My webpage

-

Hello, this is my personal webpage!

- - -EOT; - - //write the response - $response->withHeader('Content-Type', 'text/html'); - $response->write($content); -}); -``` - -This is a simple example to Gishiki used to generate an html response, -however since Gishiki is built to be used as the foundation of RESTful services -the response body shoild be a JSON, XML, YAML etc... content. - -To generate a response body from a serializable data collection Gishiki provides -a function that automate this process: this function is called __setSerializedBody__ -and does more than just converting a collection to a fixed format: - -```php -use Gishiki\Core\Route; -use Gishiki\HttpKernel\Request; -use Gishiki\HttpKernel\Response; -use Gishiki\Algorithms\Collections\SerializableCollection; - -Route::any("/factorial/{int:integer}", - function (Request $request, Response &$response, SerializableCollection &$arguments) -{ - $x = $arguments->int; - $factorial = fact($x); - - $data = new SerializableCollection([ - 'timestamp' => time(), - 'result' => $factorial, - ]); - - $response->setSerializedBody($data); -}); -``` - -The given collection may be serialized to a JSON content, XML content or YAML content. -You may decide the content type by setting the header 'Content-Type' __BUT THAT IS A WASTE OF TIME__: -Gishiki __AUTOMAGICALLY__ uses the content type listening for client preferences. - -This means that a client is not enforced to be able to deserialize a specific -content type, but can choose the preferred content-type including it on the -http request header using the 'Accept' property! - -Following accept values are used to request a specific data serialization format: - - - 'text/yaml' -> YAML - - 'text/x-yaml' -> YAML - - 'application/yaml' -> YAML - - 'application/x-yaml' -> XML - - 'application/xml' -> XML - - 'text/xml' -> XML - - 'application/json' -> JSON - -Anything else triggers the default serialization format, which is JSON! +# Response + +The __Gishiki\HttpKernel\Response__ class is used to fully represent an HTTP response. + +The Response class is PSR-7 conformant and follows that specification sheet. + +Each [Request](request.md) triggers the generation of a response. + +That response is automatically sent back to the client at the end of the +application lifetime. + +The main target of an application is __editing__ that response before the departure +of that response. + +An HTTP response is made of two parts: + + - Response __header__ + - Response __body__ + +Those parts and steps to generate them are described later on this document. + + +## Response Header + +Every HTTP response __MUST__ contains an header. + +An HTTP header have a bare minimum structure that comprises the HTTP revision, +the status code and the message associated to the status code. + +Since each status code have its own predefined status phrase (like 404 not found, +500 internal server error, 200 OK and so on) when the status code is changed the +status phrase is automatically changed by the framework. + +That can be done calling the __withStatus__ function: + +```php +use Gishiki\Core\Route; +use Gishiki\HttpKernel\Request; +use Gishiki\HttpKernel\Response; +use Gishiki\Algorithms\Collections\SerializableCollection; + +Route::any(Route::NOT_FOUND, + function (Request $request, Response &$response, SerializableCollection &$arguments) +{ + //the response code - phrase will be 404 - Not Found + $response->withStatus(404); +}); +``` + +You can manually change the status phrase, but you are discouraged from doing such +thing with standard status code! + +What you can do is using it to send a strong signal to a bad client: + +```php +use Gishiki\Core\Route; +use Gishiki\HttpKernel\Request; +use Gishiki\HttpKernel\Response; +use Gishiki\Algorithms\Collections\SerializableCollection; + +Route::any("/complex", + function (Request $request, Response &$response, SerializableCollection &$arguments) +{ + //numberOfRequests is the number of requests that a client has sent today + if ($numberOfRequests > 5) { + //perform the complex operation (may stress the system) + action(); + } else { + //stop flooding my servers! + $response->withStatus(666, 'FUCK YOU!'); + } +}); +``` + +Sorry for the bad language, that was only intended to help me to give you a (real :D) +example of usage. + + +## Response Header Details + +Each response can contains a lot of details about itselfs like the length of the +content or the type of the content. + +Each 'response detail' is a collection of values binded to a key which is the name +of the property. + +In order to edit the value of a property you have to use the __withHeader__ function: + +```php +use Gishiki\Core\Route; +use Gishiki\HttpKernel\Request; +use Gishiki\HttpKernel\Response; +use Gishiki\Algorithms\Collections\SerializableCollection; + +Route::any("/complex", + function (Request $request, Response &$response, SerializableCollection &$arguments) +{ + $request->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); +}); +``` + +If you are unsure on how to use this feature you should read more about http response [header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers). + + +## Response Body + +The body of the response is the main part of the response: the __content__ of the +response. + +For example if the response is an html content the response body is what the +user calls *the webpage*. + +To directly modify the response body you can use the __write__ function: + +```php +use Gishiki\Core\Route; +use Gishiki\HttpKernel\Request; +use Gishiki\HttpKernel\Response; +use Gishiki\Algorithms\Collections\SerializableCollection; + +Route::any("/", + function (Request $request, Response &$response, SerializableCollection &$arguments) +{ + $content = << + + My webpage + + +

My webpage

+

Hello, this is my personal webpage!

+ + +EOT; + + //write the response + $response->withHeader('Content-Type', 'text/html'); + $response->write($content); +}); +``` + +This is a simple example to Gishiki used to generate an html response, +however since Gishiki is built to be used as the foundation of RESTful services +the response body shoild be a JSON, XML, YAML etc... content. + +To generate a response body from a serializable data collection Gishiki provides +a function that automate this process: this function is called __setSerializedBody__ +and does more than just converting a collection to a fixed format: + +```php +use Gishiki\Core\Route; +use Gishiki\HttpKernel\Request; +use Gishiki\HttpKernel\Response; +use Gishiki\Algorithms\Collections\SerializableCollection; + +Route::any("/factorial/{int:integer}", + function (Request $request, Response &$response, SerializableCollection &$arguments) +{ + $x = $arguments->int; + $factorial = fact($x); + + $data = new SerializableCollection([ + 'timestamp' => time(), + 'result' => $factorial, + ]); + + $response->setSerializedBody($data); +}); +``` + +The given collection may be serialized to a JSON content, XML content or YAML content. +You may decide the content type by setting the header 'Content-Type' __BUT THAT IS A WASTE OF TIME__: +Gishiki __AUTOMAGICALLY__ uses the content type listening for client preferences. + +This means that a client is not enforced to be able to deserialize a specific +content type, but can choose the preferred content-type including it on the +http request header using the 'Accept' property! + +Following accept values are used to request a specific data serialization format: + + - 'text/yaml' -> YAML + - 'text/x-yaml' -> YAML + - 'application/yaml' -> YAML + - 'application/x-yaml' -> XML + - 'application/xml' -> XML + - 'text/xml' -> XML + - 'application/json' -> JSON + +Anything else triggers the default serialization format, which is JSON! diff --git a/Documentation/mkdocs.yml b/Documentation/mkdocs.yml index a3920d60..f6117888 100755 --- a/Documentation/mkdocs.yml +++ b/Documentation/mkdocs.yml @@ -1,30 +1,30 @@ -site_name: Gishiki - -pages: -- Home: index.md -- Installation: - - Environment: installation/index.md - - Basic PHP: installation/basic_php.md - - PHP 7.0 & nginx: installation/php_7_nginx.md - - Basic nginx: installation/basic_nginx.md - - Basic apache: installation/basic_apache.md - - Basic lighttpd: installation/basic_lighttpd.md - - Basic IIS: installation/basic_iis.md - - Composer: installation/composer.md - - PaaS: installation/paas.md -- API: apidoc.md -- Usage: - - Configuration: usage/configuration.md - - Router: usage/routing.md - - Request: usage/request.md - - Response: usage/response.md - - Logger: usage/logging.md - - Database: usage/database.md - - CRUD: usage/CRUD.md -- CLI: - - CLI Toolkit: cli/index.md - -- About: - - License: license.md - -theme: readthedocs +site_name: Gishiki + +pages: +- Home: index.md +- Installation: + - Environment: installation/index.md + - Basic PHP: installation/basic_php.md + - PHP 7.0 & nginx: installation/php_7_nginx.md + - Basic nginx: installation/basic_nginx.md + - Basic apache: installation/basic_apache.md + - Basic lighttpd: installation/basic_lighttpd.md + - Basic IIS: installation/basic_iis.md + - Composer: installation/composer.md + - PaaS: installation/paas.md +- API: apidoc.md +- Usage: + - Configuration: usage/configuration.md + - Router: usage/routing.md + - Request: usage/request.md + - Response: usage/response.md + - Logger: usage/logging.md + - Database: usage/database.md + - CRUD: usage/CRUD.md +- CLI: + - CLI Toolkit: cli/index.md + +- About: + - License: license.md + +theme: readthedocs diff --git a/Gishiki/Algorithms/Base64.php b/Gishiki/Algorithms/Base64.php index 18b9d3cb..fc738e71 100644 --- a/Gishiki/Algorithms/Base64.php +++ b/Gishiki/Algorithms/Base64.php @@ -1,100 +1,100 @@ - - */ -abstract class Base64 -{ - /** - * Create the Base64 binary-safe representation of the given message. - * - * The given message can be a binary unsafe string. - * - * Example of usage: - * - * //this is the binary unsafe message - * $message = " ... "; - * - * //print the result - * var_dump(Base64::encode($message)); - * - * - * @param string $message the binary-unsafe message - * @param bool $urlSafe the generated result doesn't contains special characters - * - * @return string the binary-safe representation of the given message - * - * @throws \InvalidArgumentException the given message is not represented as a string - */ - public static function encode($message, $urlSafe = true) - { - //check for the message type - if (!is_string($message)) { - throw new \InvalidArgumentException('the binary usafe content must be given as a string'); - } - - //get the base64 url unsafe - $encoded = base64_encode($message); - - //return the url safe version if requested - return ($urlSafe) ? rtrim(strtr($encoded, '+/=', '-_~'), '~') : $encoded; - } - - /** - * Get the binary-unsafe representation of the given base64-encoded message. - * - * This function is compatible with the php standard base64_encode and the - * framework Base64::encode( ... ). - * - * Example of usage: - * - * //this is the binary unsafe message - * $message = " ... "; - * - * //print the input string (binary unsafe) - * var_dump(Base64::decode(Base64::encode($message))); - * - * - * @param string $message a message base64 encoded - * - * @return string the message in a binary-unsafe format - * - * @throws \InvalidArgumentException the given message is not represented as a string - */ - public static function decode($message) - { - //check for the message type - if (!is_string($message)) { - throw new \InvalidArgumentException('the base64 of a string is represented as another string'); - } - - //is the base64 encoded in an URL safe format? - $url_safe = (strlen($message) % 4) || (strpos($message, '_') !== false) || (strpos($message, '~') !== false); - - //get the base64 encoded valid string and return the decode result - $validBase64 = ($url_safe) ? - str_pad(strtr($message, '-_~', '+/='), strlen($message) + 4 - (strlen($message) % 4), '=', STR_PAD_RIGHT) - : $message; - - return base64_decode($validBase64); - } -} + + */ +abstract class Base64 +{ + /** + * Create the Base64 binary-safe representation of the given message. + * + * The given message can be a binary unsafe string. + * + * Example of usage: + * + * //this is the binary unsafe message + * $message = " ... "; + * + * //print the result + * var_dump(Base64::encode($message)); + * + * + * @param string $message the binary-unsafe message + * @param bool $urlSafe the generated result doesn't contains special characters + * + * @return string the binary-safe representation of the given message + * + * @throws \InvalidArgumentException the given message is not represented as a string + */ + public static function encode($message, $urlSafe = true) + { + //check for the message type + if (!is_string($message)) { + throw new \InvalidArgumentException('the binary usafe content must be given as a string'); + } + + //get the base64 url unsafe + $encoded = base64_encode($message); + + //return the url safe version if requested + return ($urlSafe) ? rtrim(strtr($encoded, '+/=', '-_~'), '~') : $encoded; + } + + /** + * Get the binary-unsafe representation of the given base64-encoded message. + * + * This function is compatible with the php standard base64_encode and the + * framework Base64::encode( ... ). + * + * Example of usage: + * + * //this is the binary unsafe message + * $message = " ... "; + * + * //print the input string (binary unsafe) + * var_dump(Base64::decode(Base64::encode($message))); + * + * + * @param string $message a message base64 encoded + * + * @return string the message in a binary-unsafe format + * + * @throws \InvalidArgumentException the given message is not represented as a string + */ + public static function decode($message) + { + //check for the message type + if (!is_string($message)) { + throw new \InvalidArgumentException('the base64 of a string is represented as another string'); + } + + //is the base64 encoded in an URL safe format? + $url_safe = (strlen($message) % 4) || (strpos($message, '_') !== false) || (strpos($message, '~') !== false); + + //get the base64 encoded valid string and return the decode result + $validBase64 = ($url_safe) ? + str_pad(strtr($message, '-_~', '+/='), strlen($message) + 4 - (strlen($message) % 4), '=', STR_PAD_RIGHT) + : $message; + + return base64_decode($validBase64); + } +} diff --git a/Gishiki/Algorithms/Collections/CollectionInterface.php b/Gishiki/Algorithms/Collections/CollectionInterface.php index 56a7814d..838aab1f 100644 --- a/Gishiki/Algorithms/Collections/CollectionInterface.php +++ b/Gishiki/Algorithms/Collections/CollectionInterface.php @@ -1,93 +1,93 @@ - - */ -interface CollectionInterface extends \ArrayAccess, \Countable -{ - /** - * Set collection item. - * - * @param string $key The data key - * @param mixed $value The data value - */ - public function set($key, $value); - - /** - * Get collection item for key. - * - * @param string $key The data key - * @param mixed $default The default value to return if data key does not exist - * - * @return mixed The key's value, or the default value - */ - public function get($key, $default = null); - - /** - * Add item to collection. - * - * @param array $items Key-value array of data to append to this collection - */ - public function replace(array $items); - - /** - * Get all items in collection. - * - * @return array The collection's source data - */ - public function all(); - - /** - * Does this collection have a given key? - * - * @param string $key The data key - * - * @return bool - */ - public function has($key); - - /** - * Remove item from collection. - * - * @param string $key The data key - */ - public function remove($key); - - /** - * Remove all items from collection. - */ - public function clear(); - - /** - * Get collection keys. - * - * @return array The collection's source data keys - */ - public function keys(); - - /** - * Check if the collection contains elements. - * - * @return bool true if the collection is empty - */ - public function empty(); -} + + */ +interface CollectionInterface extends \ArrayAccess, \Countable +{ + /** + * Set collection item. + * + * @param string $key The data key + * @param mixed $value The data value + */ + public function set($key, $value); + + /** + * Get collection item for key. + * + * @param string $key The data key + * @param mixed $default The default value to return if data key does not exist + * + * @return mixed The key's value, or the default value + */ + public function get($key, $default = null); + + /** + * Add item to collection. + * + * @param array $items Key-value array of data to append to this collection + */ + public function replace(array $items); + + /** + * Get all items in collection. + * + * @return array The collection's source data + */ + public function all(); + + /** + * Does this collection have a given key? + * + * @param string $key The data key + * + * @return bool + */ + public function has($key); + + /** + * Remove item from collection. + * + * @param string $key The data key + */ + public function remove($key); + + /** + * Remove all items from collection. + */ + public function clear(); + + /** + * Get collection keys. + * + * @return array The collection's source data keys + */ + public function keys(); + + /** + * Check if the collection contains elements. + * + * @return bool true if the collection is empty + */ + public function empty(); +} diff --git a/Gishiki/Algorithms/Collections/DeserializationException.php b/Gishiki/Algorithms/Collections/DeserializationException.php index 170b9493..086e5c16 100644 --- a/Gishiki/Algorithms/Collections/DeserializationException.php +++ b/Gishiki/Algorithms/Collections/DeserializationException.php @@ -1,39 +1,39 @@ - - */ -class DeserializationException extends Exception -{ - /** - * Create the deserialization-related exception. - * - * @param string $message the error message - * @param int $errorCode the json error code - */ - public function __construct($message, $errorCode) - { - parent::__construct($message, $errorCode); - } -} + + */ +class DeserializationException extends Exception +{ + /** + * Create the deserialization-related exception. + * + * @param string $message the error message + * @param int $errorCode the json error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } +} diff --git a/Gishiki/Algorithms/Collections/GenericCollection.php b/Gishiki/Algorithms/Collections/GenericCollection.php index b064f505..a6823a34 100644 --- a/Gishiki/Algorithms/Collections/GenericCollection.php +++ b/Gishiki/Algorithms/Collections/GenericCollection.php @@ -1,229 +1,229 @@ - - */ -class GenericCollection implements CollectionInterface, \IteratorAggregate -{ - /** - * @var array The source data. - */ - protected $data = []; - - /** - * Create new collection from the given properties collection. - * - * @param array $items Pre-populate collection of key => value - * - * @throws \InvalidArgumentException an invalid collection was given - */ - public function __construct($items = []) - { - //check if the given items list is a valid items list - if (!is_array($items)) { - throw new \InvalidArgumentException('The collection of properties and nested data must be expressed as an array'); - } - - foreach ($items as $key => $value) { - $this->set($key, $value); - } - } - - /** - * Get the currently managed collection as a native array. - * - * @return array the current collection - */ - public function __invoke() - { - return $this->data; - } - - /** - * Get an element of the collection as it would be an object property. - * - * Return null if the array doesn't contain the given key - * - * @param int|string $key the index of the array element to be accessed - * - * @return mixed the requested array element or NULL - */ - public function __get($key) - { - return $this->get($key); - } - - /** - * Set an element of the collection as it would be an object property. - * - * @param string $key the of the property to be modified - * @param mixed $value the value to be assigned to the property - */ - public function __set($key, $value) - { - $this->set($key, $value); - } - - /*************************************************************************** - * Collection interface - **************************************************************************/ - - public function set($key, $value) - { - $this->data[$key] = $value; - } - - public function get($key, $default = null) - { - return $this->has($key) ? $this->data[$key] : $default; - } - - public function replace(array $items) - { - foreach ($items as $key => $value) { - $this->set($key, $value); - } - } - - /** - * Get the representation of the current collection as an associative array. - * - * @return array the collection as an associative array - */ - public function all() - { - return $this->data; - } - - /** - * {@inheritdoc} - */ - public function keys() - { - return array_keys($this->data); - } - - /** - * {@inheritdoc} - */ - public function has($key) - { - return array_key_exists($key, $this->data); - } - - /** - * {@inheritdoc} - */ - public function remove($key) - { - unset($this->data[$key]); - } - - /** - * {@inheritdoc} - */ - public function clear() - { - $this->data = []; - } - - /** - * {@inheritdoc} - */ - public function empty() - { - return empty($this->data); - } - - /*************************************************************************** - * ArrayAccess interface - **************************************************************************/ - - /** - * Does this collection have a given key? - * - * @param string $key The data key - * - * @return bool - */ - public function offsetExists($key) - { - return $this->has($key); - } - - /** - * Get collection item for key. - * - * @param string $key The data key - * - * @return mixed The key's value, or the default value - */ - public function offsetGet($key) - { - return $this->get($key); - } - - /** - * Set collection item. - * - * @param string $key The data key - * @param mixed $value The data value - */ - public function offsetSet($key, $value) - { - $this->set($key, $value); - } - - /** - * Remove item from collection. - * - * @param string $key The data key - */ - public function offsetUnset($key) - { - $this->remove($key); - } - - /** - * Get number of items in collection. - * - * @return int - */ - public function count() - { - return count($this->data); - } - - /*************************************************************************** - * IteratorAggregate interface - **************************************************************************/ - - /** - * Get collection iterator. - * - * @return \ArrayIterator - */ - public function getIterator() - { - return new \ArrayIterator($this->data); - } -} + + */ +class GenericCollection implements CollectionInterface, \IteratorAggregate +{ + /** + * @var array The source data. + */ + protected $data = []; + + /** + * Create new collection from the given properties collection. + * + * @param array $items Pre-populate collection of key => value + * + * @throws \InvalidArgumentException an invalid collection was given + */ + public function __construct($items = []) + { + //check if the given items list is a valid items list + if (!is_array($items)) { + throw new \InvalidArgumentException('The collection of properties and nested data must be expressed as an array'); + } + + foreach ($items as $key => $value) { + $this->set($key, $value); + } + } + + /** + * Get the currently managed collection as a native array. + * + * @return array the current collection + */ + public function __invoke() + { + return $this->data; + } + + /** + * Get an element of the collection as it would be an object property. + * + * Return null if the array doesn't contain the given key + * + * @param int|string $key the index of the array element to be accessed + * + * @return mixed the requested array element or NULL + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Set an element of the collection as it would be an object property. + * + * @param string $key the of the property to be modified + * @param mixed $value the value to be assigned to the property + */ + public function __set($key, $value) + { + $this->set($key, $value); + } + + /*************************************************************************** + * Collection interface + **************************************************************************/ + + public function set($key, $value) + { + $this->data[$key] = $value; + } + + public function get($key, $default = null) + { + return $this->has($key) ? $this->data[$key] : $default; + } + + public function replace(array $items) + { + foreach ($items as $key => $value) { + $this->set($key, $value); + } + } + + /** + * Get the representation of the current collection as an associative array. + * + * @return array the collection as an associative array + */ + public function all() + { + return $this->data; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->data); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return array_key_exists($key, $this->data); + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + unset($this->data[$key]); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->data = []; + } + + /** + * {@inheritdoc} + */ + public function empty() + { + return empty($this->data); + } + + /*************************************************************************** + * ArrayAccess interface + **************************************************************************/ + + /** + * Does this collection have a given key? + * + * @param string $key The data key + * + * @return bool + */ + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * Get collection item for key. + * + * @param string $key The data key + * + * @return mixed The key's value, or the default value + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Set collection item. + * + * @param string $key The data key + * @param mixed $value The data value + */ + public function offsetSet($key, $value) + { + $this->set($key, $value); + } + + /** + * Remove item from collection. + * + * @param string $key The data key + */ + public function offsetUnset($key) + { + $this->remove($key); + } + + /** + * Get number of items in collection. + * + * @return int + */ + public function count() + { + return count($this->data); + } + + /*************************************************************************** + * IteratorAggregate interface + **************************************************************************/ + + /** + * Get collection iterator. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->data); + } +} diff --git a/Gishiki/Algorithms/Collections/SerializableCollection.php b/Gishiki/Algorithms/Collections/SerializableCollection.php index 42a87b08..30a4cf0b 100644 --- a/Gishiki/Algorithms/Collections/SerializableCollection.php +++ b/Gishiki/Algorithms/Collections/SerializableCollection.php @@ -1,194 +1,194 @@ - - */ -class SerializableCollection extends GenericCollection -{ - /******************************************** - * Serializators * - ********************************************/ - const JSON = 0; - const XML = 1; - const YAML = 2; - - /** - * Create serializable data collection from the given array. - * - * @param array $data the collection of properties - * - * @throws \InvalidArgumentException an invalid collection was given - */ - public function __construct($data = array()) - { - if (is_array($data)) { - parent::__construct($data); - } elseif ($data instanceof \Gishiki\Algorithms\Collections\CollectionInterface) { - $this->data = $data->all(); - } - } - - /** - * Serialize the current data collection. - * - * @param int $format an integer representing one of the allowed formats - * @throw \InvalidArgumentException the serialization format is invalid - * @throw SerializationException the error occurred while serializing the collection in json format - * - * @return string the collection serialized - */ - public function serialize($format = self::JSON) - { - if (!is_integer($format)) { - throw new \InvalidArgumentException('Invalid serialization format'); - } - - $result = ''; - switch ($format) { - - case self::JSON: - //try json encoding - $result = json_encode($this->all(), JSON_PRETTY_PRINT); - - //and check for the result - if (json_last_error() != JSON_ERROR_NONE) { - throw new SerializationException('The given data cannot be serialized in JSON content', 2); - } - break; - - case self::XML: - $xml = new \Gishiki\Algorithms\XmlDomConstructor('1.0', 'utf-8'); - $xml->xmlStandalone = true; - $xml->formatOutput = true; - $xml->fromMixed($this->all()); - $xml->normalizeDocument(); - $result = str_replace('standalone="yes"?>', 'standalone="yes"?>', $xml->saveXML()); - $result .= "\n"; - break; - - case self::YAML: - $result = \Symfony\Component\Yaml\Yaml::dump($this->all()); - break; - - default: - throw new SerializationException('Invalid serialization format selected', 7); - } - - return $result; - } - - /** - * Deserialize the given data collectionand create a serializable data collection. - * - * @param string|array|CollectionInterface $message the string containing the serialized data or the array of data - * @param int $format an integer representing one of the allowed formats - * - * @return SerializableCollection the deserialization result - * - * @throws DeserializationException the error preventing the data deserialization - */ - public static function deserialize($message, $format = self::JSON) - { - if ((is_array($message)) || ($message instanceof CollectionInterface)) { - return new self($message); - } elseif ($format === self::JSON) { - if (!is_string($message)) { - throw new DeserializationException('The given content is not a valid JSON content', 3); - } - - //try decoding the string - $nativeSerialization = json_decode($message, true, 512); - - //and check for the result - if (json_last_error() != JSON_ERROR_NONE) { - throw new DeserializationException('The given string is not a valid JSON content', 1); - } - - //the deserialization result MUST be an array - $serializationResult = (is_array($nativeSerialization)) ? $nativeSerialization : []; - - //return the deserialization result if everything went right - return new self($serializationResult); - } elseif ($format === self::XML) { - if (!is_string($message)) { - throw new DeserializationException('The given content is not a valid XML content', 3); - } - - //resolve CDATA - $messageParsed = preg_replace_callback('//', function ($matches) { - return trim(htmlspecialchars($matches[1])); - }, $message); - - //load the xml from the cleaned string - libxml_use_internal_errors(true); - $xml = simplexml_load_string($messageParsed); - - if ((count(libxml_get_errors()) > 0)/* || (!$xml)*/) { - //clear the error list to avoid interferences - libxml_clear_errors(); - - throw new DeserializationException('The given content is not a valid XML content', 3); - } - - //use the json engine to deserialize the object - $string = json_encode($xml); - $nativeSerialization = json_decode($string, true); - - //return the deserialization result - return new self($nativeSerialization); - } elseif ($format === self::YAML) { - if (!is_string($message)) { - throw new DeserializationException('The given content is not a valid YAML content', 8); - } - - //attempt to deserialize the yaml file - $nativeSerialization = null; - try { - $nativeSerialization = \Symfony\Component\Yaml\Yaml::parse($message, true, true); - } catch (\Symfony\Component\Yaml\Exception\ParseException $ex) { - throw new DeserializationException('The given YAML content cannot be deserialized', 9); - } - - //check for the result type - if (!is_array($nativeSerialization)) { - throw new DeserializationException('The YAML deserialization result cannot be used to build a collection', 10); - } - - //return the deserialization result - return new self($nativeSerialization); - } - - //impossible to serialize the message - throw new DeserializationException('It is impossible to deserialize the given message', 2); - } - - /** - * Get the serialization result using the default format. - * - * @return string the serialization result - */ - public function __toString() - { - //use the default serializator - return $this->serialize(); - } -} + + */ +class SerializableCollection extends GenericCollection +{ + /******************************************** + * Serializators * + ********************************************/ + const JSON = 0; + const XML = 1; + const YAML = 2; + + /** + * Create serializable data collection from the given array. + * + * @param array $data the collection of properties + * + * @throws \InvalidArgumentException an invalid collection was given + */ + public function __construct($data = array()) + { + if (is_array($data)) { + parent::__construct($data); + } elseif ($data instanceof \Gishiki\Algorithms\Collections\CollectionInterface) { + $this->data = $data->all(); + } + } + + /** + * Serialize the current data collection. + * + * @param int $format an integer representing one of the allowed formats + * @throw \InvalidArgumentException the serialization format is invalid + * @throw SerializationException the error occurred while serializing the collection in json format + * + * @return string the collection serialized + */ + public function serialize($format = self::JSON) + { + if (!is_integer($format)) { + throw new \InvalidArgumentException('Invalid serialization format'); + } + + $result = ''; + switch ($format) { + + case self::JSON: + //try json encoding + $result = json_encode($this->all(), JSON_PRETTY_PRINT); + + //and check for the result + if (json_last_error() != JSON_ERROR_NONE) { + throw new SerializationException('The given data cannot be serialized in JSON content', 2); + } + break; + + case self::XML: + $xml = new \Gishiki\Algorithms\XmlDomConstructor('1.0', 'utf-8'); + $xml->xmlStandalone = true; + $xml->formatOutput = true; + $xml->fromMixed($this->all()); + $xml->normalizeDocument(); + $result = str_replace('standalone="yes"?>', 'standalone="yes"?>', $xml->saveXML()); + $result .= "\n"; + break; + + case self::YAML: + $result = \Symfony\Component\Yaml\Yaml::dump($this->all()); + break; + + default: + throw new SerializationException('Invalid serialization format selected', 7); + } + + return $result; + } + + /** + * Deserialize the given data collectionand create a serializable data collection. + * + * @param string|array|CollectionInterface $message the string containing the serialized data or the array of data + * @param int $format an integer representing one of the allowed formats + * + * @return SerializableCollection the deserialization result + * + * @throws DeserializationException the error preventing the data deserialization + */ + public static function deserialize($message, $format = self::JSON) + { + if ((is_array($message)) || ($message instanceof CollectionInterface)) { + return new self($message); + } elseif ($format === self::JSON) { + if (!is_string($message)) { + throw new DeserializationException('The given content is not a valid JSON content', 3); + } + + //try decoding the string + $nativeSerialization = json_decode($message, true, 512); + + //and check for the result + if (json_last_error() != JSON_ERROR_NONE) { + throw new DeserializationException('The given string is not a valid JSON content', 1); + } + + //the deserialization result MUST be an array + $serializationResult = (is_array($nativeSerialization)) ? $nativeSerialization : []; + + //return the deserialization result if everything went right + return new self($serializationResult); + } elseif ($format === self::XML) { + if (!is_string($message)) { + throw new DeserializationException('The given content is not a valid XML content', 3); + } + + //resolve CDATA + $messageParsed = preg_replace_callback('//', function ($matches) { + return trim(htmlspecialchars($matches[1])); + }, $message); + + //load the xml from the cleaned string + libxml_use_internal_errors(true); + $xml = simplexml_load_string($messageParsed); + + if ((count(libxml_get_errors()) > 0)/* || (!$xml)*/) { + //clear the error list to avoid interferences + libxml_clear_errors(); + + throw new DeserializationException('The given content is not a valid XML content', 3); + } + + //use the json engine to deserialize the object + $string = json_encode($xml); + $nativeSerialization = json_decode($string, true); + + //return the deserialization result + return new self($nativeSerialization); + } elseif ($format === self::YAML) { + if (!is_string($message)) { + throw new DeserializationException('The given content is not a valid YAML content', 8); + } + + //attempt to deserialize the yaml file + $nativeSerialization = null; + try { + $nativeSerialization = \Symfony\Component\Yaml\Yaml::parse($message, true, true); + } catch (\Symfony\Component\Yaml\Exception\ParseException $ex) { + throw new DeserializationException('The given YAML content cannot be deserialized', 9); + } + + //check for the result type + if (!is_array($nativeSerialization)) { + throw new DeserializationException('The YAML deserialization result cannot be used to build a collection', 10); + } + + //return the deserialization result + return new self($nativeSerialization); + } + + //impossible to serialize the message + throw new DeserializationException('It is impossible to deserialize the given message', 2); + } + + /** + * Get the serialization result using the default format. + * + * @return string the serialization result + */ + public function __toString() + { + //use the default serializator + return $this->serialize(); + } +} diff --git a/Gishiki/Algorithms/Collections/SerializationException.php b/Gishiki/Algorithms/Collections/SerializationException.php index 1b2db808..eb0eab5c 100644 --- a/Gishiki/Algorithms/Collections/SerializationException.php +++ b/Gishiki/Algorithms/Collections/SerializationException.php @@ -1,39 +1,39 @@ - - */ -final class SerializationException extends Exception -{ - /** - * Create the serialization-related exception. - * - * @param string $message the error message - * @param int $errorCode the json error code - */ - public function __construct($message, $errorCode) - { - parent::__construct($message, $errorCode); - } -} + + */ +final class SerializationException extends Exception +{ + /** + * Create the serialization-related exception. + * + * @param string $message the error message + * @param int $errorCode the json error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } +} diff --git a/Gishiki/Algorithms/Collections/StackCollection.php b/Gishiki/Algorithms/Collections/StackCollection.php index 4cbab15c..5c01491e 100644 --- a/Gishiki/Algorithms/Collections/StackCollection.php +++ b/Gishiki/Algorithms/Collections/StackCollection.php @@ -1,125 +1,125 @@ - - */ -class StackCollection extends GenericCollection -{ - /** - * @var int the number of maximum elements, or a negative number for no limits - */ - protected $limit; - - /** - * StackCollection constructor. - * - * @param int $limit the number of maximum numbers (or a negative number for no limits) - * @param array $items collection of values to initialize the stack with - * @throws \InvalidArgumentException an invalid collection was given - */ - public function __construct($items = [], $limit = -1) - { - //check if the given items list is a valid items list - if (!is_array($items)) { - throw new \InvalidArgumentException('The collection of properties and nested data must be expressed as an array'); - } - - if (!is_integer($limit)) { - throw new \InvalidArgumentException('The maximum amount of items must be expressed with an integer number'); - } - - foreach ($items as $value) { - $this->push($value); - } - - //stack can only contain this many items - $this->limit = $limit; - } - - /** - * Insert an element on the top of the FIFO structure. - * - * @param mixed $item the element to be inserted - * @throws StackException the stack is on its limit - */ - public function push($item) - { - //trap for stack overflow - if (($this->limit > 0) && (count($this->data) >= $this->limit)) { - throw new StackException('The stack collection is full', 1); - } - - //prepend item to the start of the array - array_unshift($this->data, $item); - } - - /** - * Remove the element at the top of the stack and return it. - * - * @return mixed the element at the top of the stack - * @throws StackException the stack is empty - */ - public function pop() - { - if ($this->empty()) { - //trap for stack underflow - throw new StackException('The stack collection is empty', 2); - } - - //pop item from the start of the array - return array_shift($this->data); - } - - /** - * Reverse the order of the whole structure. - */ - public function reverse() - { - $this->data = array_reverse($this->data); - } - - /** - * Get the element at the top of the stack. - * - * @return mixed the last pushed element - */ - public function top() - { - return current($this->data); - } - - /** - * @throws StackException Invalid function in a stack - */ - public function set($key, $value) - { - throw new StackException('The stack collection cannot be modified calling the set function', 3); - } - - /** - * @throws StackException Invalid function in a stack - */ - public function get($key, $default = null) - { - throw new StackException('The FIFO stack collection order cannot be violated calling the get function', 3); - } + + */ +class StackCollection extends GenericCollection +{ + /** + * @var int the number of maximum elements, or a negative number for no limits + */ + protected $limit; + + /** + * StackCollection constructor. + * + * @param int $limit the number of maximum numbers (or a negative number for no limits) + * @param array $items collection of values to initialize the stack with + * @throws \InvalidArgumentException an invalid collection was given + */ + public function __construct($items = [], $limit = -1) + { + //check if the given items list is a valid items list + if (!is_array($items)) { + throw new \InvalidArgumentException('The collection of properties and nested data must be expressed as an array'); + } + + if (!is_integer($limit)) { + throw new \InvalidArgumentException('The maximum amount of items must be expressed with an integer number'); + } + + foreach ($items as $value) { + $this->push($value); + } + + //stack can only contain this many items + $this->limit = $limit; + } + + /** + * Insert an element on the top of the FIFO structure. + * + * @param mixed $item the element to be inserted + * @throws StackException the stack is on its limit + */ + public function push($item) + { + //trap for stack overflow + if (($this->limit > 0) && (count($this->data) >= $this->limit)) { + throw new StackException('The stack collection is full', 1); + } + + //prepend item to the start of the array + array_unshift($this->data, $item); + } + + /** + * Remove the element at the top of the stack and return it. + * + * @return mixed the element at the top of the stack + * @throws StackException the stack is empty + */ + public function pop() + { + if ($this->empty()) { + //trap for stack underflow + throw new StackException('The stack collection is empty', 2); + } + + //pop item from the start of the array + return array_shift($this->data); + } + + /** + * Reverse the order of the whole structure. + */ + public function reverse() + { + $this->data = array_reverse($this->data); + } + + /** + * Get the element at the top of the stack. + * + * @return mixed the last pushed element + */ + public function top() + { + return current($this->data); + } + + /** + * @throws StackException Invalid function in a stack + */ + public function set($key, $value) + { + throw new StackException('The stack collection cannot be modified calling the set function', 3); + } + + /** + * @throws StackException Invalid function in a stack + */ + public function get($key, $default = null) + { + throw new StackException('The FIFO stack collection order cannot be violated calling the get function', 3); + } } \ No newline at end of file diff --git a/Gishiki/Algorithms/Collections/StackException.php b/Gishiki/Algorithms/Collections/StackException.php index 8504ded7..9dbc96bf 100644 --- a/Gishiki/Algorithms/Collections/StackException.php +++ b/Gishiki/Algorithms/Collections/StackException.php @@ -1,36 +1,36 @@ - - */ -final class StackException extends Exception -{ - /** - * Create the stack-related exception. - * - * @param string $message the error message - * @param int $errorCode the json error code - */ - public function __construct($message, $errorCode) - { - parent::__construct($message, $errorCode); - } + + */ +final class StackException extends Exception +{ + /** + * Create the stack-related exception. + * + * @param string $message the error message + * @param int $errorCode the json error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } } \ No newline at end of file diff --git a/Gishiki/Algorithms/Manipulation.php b/Gishiki/Algorithms/Strings/Manipulation.php old mode 100755 new mode 100644 similarity index 95% rename from Gishiki/Algorithms/Manipulation.php rename to Gishiki/Algorithms/Strings/Manipulation.php index 5023eb7e..ebaed549 --- a/Gishiki/Algorithms/Manipulation.php +++ b/Gishiki/Algorithms/Strings/Manipulation.php @@ -15,12 +15,12 @@ limitations under the License. *****************************************************************************/ -namespace Gishiki\Algorithms; +namespace Gishiki\Algorithms\Strings; /** * An helper class for string manipulation. * - * Benato Denis + * @author Benato Denis */ abstract class Manipulation { @@ -85,7 +85,7 @@ public static function getBetween($string, $start, $end) /** * Interpolate a PHP string: - * perform a sobstitution of {{name}} with the value of the $params['name']. + * perform a substitution of {{name}} with the value of the $params['name']. * * Note: $params['name'] can be an object that implements __toString() * diff --git a/Gishiki/Algorithms/Strings/SimpleLexer.php b/Gishiki/Algorithms/Strings/SimpleLexer.php new file mode 100644 index 00000000..ae3f1c19 --- /dev/null +++ b/Gishiki/Algorithms/Strings/SimpleLexer.php @@ -0,0 +1,128 @@ + + */ +abstract class SimpleLexer +{ + /** + * Check if the given string can be validates as a valid email address. + * + * @param string $str the string to be validated + * @return bool true if the given string is a valid email address, false otherwise + */ + static function isEmail($str) : bool + { + return self::isString($str) && filter_var($str, FILTER_VALIDATE_EMAIL); + } + + /** + * Check if the given parameter is a valid PHP string. + * + * @param string $str the parameter to be validated + * @return bool true if and only if the given parameter is a valid string + */ + static function isString($str) : bool + { + return is_string($str); + } + + /** + * Check if the given string can be evaluated in a valid floating point number. + * + * @param string $str the string to be validated + * @return bool true if the given string is a valid float, false otherwise + */ + static function isFloat($str) : bool + { + if (!self::isString($str)) { + return false; + } + + $fountDot = false; + + for ($i = 0; $i < strlen($str); $i++) { + if (($i != 0) && (($str[$i] == '-') || ($str[$i] == '+'))) { + return false; + } else if (($str[$i] == '.') && ($fountDot == true)) { + return false; + } else if (strpos("+-.0123456789", $str[$i]) === false) { + return false; + } + + //update $fountDot value + $fountDot = ($str[$i] == '.') ? true : $fountDot; + } + + return $str[strlen($str) - 1] != '.'; + } + + /** + * Check if the given string can be evaluated in a valid unsigned integer number. + * + * @param string $str the string to be validated + * @return bool true if the given string is a valid unsigned integer, false otherwise + */ + static public function isUnsignedInteger($str) : bool + { + if (!self::isString($str)) { + return false; + } + + //check the entire string + for ($i = 0; $i < strlen($str); $i++) { + if (strpos("0123456789", $str[$i]) === false) { + return false; + } + } + + return true; + } + + /** + * Check if the given string can be evaluated in a valid signed integer number. + * + * @param string $str the string to be validated + * @return bool true if the given string is a valid signed integer, false otherwise + */ + static public function isSignedInteger($str) : bool + { + if (!self::isString($str)) { + return false; + } + + //check the 1st character + if (strpos("+-0123456789", $str[0]) === false) { + return false; + } + + //check from the 2nd character afterward + for ($i = 1; $i < strlen($str); $i++) { + if (strpos("0123456789", $str[$i]) === false) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/Gishiki/Algorithms/XmlDomConstructor.php b/Gishiki/Algorithms/XmlDomConstructor.php index be543a96..4f852dbd 100644 --- a/Gishiki/Algorithms/XmlDomConstructor.php +++ b/Gishiki/Algorithms/XmlDomConstructor.php @@ -1,80 +1,80 @@ - - text - - hello - world - - - \verbatim - * - * - * Array should then look like: - \verbatim - array( - "nodes" => array( - "node" => array( - 0 => "text", - 1 => array( - "field" => array ( - 0 => "hello", - 1 => "world", - ), - ), - ), - ), - ); - \endverbatim - * - * @param mixed $mixed An array or string - * @param DOMElement $domElement the element from where the array will be construct to - */ - public function fromMixed($mixed, \DOMElement $domElement = null) - { - $domElement = is_null($domElement) ? $this : $domElement; - - if (is_array($mixed)) { - foreach ($mixed as $index => $mixedElement) { - if (is_int($index)) { - if ($index == 0) { - $node = $domElement; - } else { - $node = $this->createElement($domElement->tagName); - $domElement->parentNode->appendChild($node); - } - } else { - $node = $this->createElement($index); - $domElement->appendChild($node); - } - - $this->fromMixed($mixedElement, $node); - } - } else { - $domElement->appendChild($this->createTextNode($mixed)); - } - } -} + + text + + hello + world + + + \verbatim + * + * + * Array should then look like: + \verbatim + array( + "nodes" => array( + "node" => array( + 0 => "text", + 1 => array( + "field" => array ( + 0 => "hello", + 1 => "world", + ), + ), + ), + ), + ); + \endverbatim + * + * @param array|string $mixed An array or string + * @param \DOMElement $domElement the element from where the array will be construct to + */ + public function fromMixed($mixed, \DOMElement $domElement = null) + { + $domElement = is_null($domElement) ? $this : $domElement; + + if (is_array($mixed)) { + foreach ($mixed as $index => $mixedElement) { + if (is_int($index)) { + if ($index == 0) { + $node = $domElement; + } else { + $node = $this->createElement($domElement->tagName); + $domElement->parentNode->appendChild($node); + } + } else { + $node = $this->createElement($index); + $domElement->appendChild($node); + } + + $this->fromMixed($mixedElement, $node); + } + } else { + $domElement->appendChild($this->createTextNode($mixed)); + } + } +} diff --git a/Gishiki/CLI/Console.php b/Gishiki/CLI/Console.php deleted file mode 100644 index 5fe3f861..00000000 --- a/Gishiki/CLI/Console.php +++ /dev/null @@ -1,174 +0,0 @@ - - */ -abstract class Console -{ - /** - * @var int the color of the text, look at ConsoleColor - */ - protected static $foregroundColor = ConsoleColor::OFF; - - /** - * @var int the color of the background, look at ConsoleColor - */ - protected static $backgroundColor = ConsoleColor::OFF; - - /** - * @var bool TRUE only if colors have to be enabled - */ - protected static $enableColors = false; - - /** - * Enable or disable colors support. - * - * @param bool $enable TRUE enable colors, FALSE disable them - */ - public static function colorsEnable($enable) - { - self::$enableColors = boolval($enable); - } - - /** - * Check whether colors support is enabled. - * - * @return bool TRUE with colors enabled, FALSE otherwise - */ - public static function colorsEnabled() - { - return self::$enableColors; - } - - /** - * Write to the standard output without printing a newline. - * - * @param mixed $what what will be printed out - */ - public static function write($what) - { - $str = ''; - - switch (strtolower(gettype($what))) { - case 'boolean': - $str = ($what) ? 'true' : 'false'; - break; - - case 'null': - $str = 'null'; - break; - - case 'array': - foreach ($what as $element) { - self::write($element); - } - break; - - default: - $str = ''.$what; - } - - //do not paint newlines - $lines = explode("\n", trim($str, "\t\r\0\x0B")); - - //remove a possible empty string - if (strlen($lines[count($lines) - 1]) == 0) { - unset($lines[count($lines) - 1]); - } - - for ($lineIndex = 0; $lineIndex < count($lines); ++$lineIndex) { - //color the text if necessary - if (self::colorsEnabled()) { - printf("\033[".self::$backgroundColor."m\033[".self::$foregroundColor.'m'); - } - - //write the plain-text string - printf($lines[$lineIndex]); - - //color the text if necessary - if (self::colorsEnabled()) { - printf("\033[0m"); - } - - //print the newline without colors - if (($lineIndex != count($lines) - 1) || (substr($str, -1) == "\n")) { - printf("\n"); - } - } - } - - /** - * Write to the standard output printing a newline afterward. - * - * @param mixed $what what will be printed out - */ - public static function writeLine($what) - { - self::write($what); - - self::write("\n"); - } - - /** - * Change the text color/style of the console. - * Look at the class ConsoleColor for a list ov available colors. - * - * @param int $color the console color code to be used - * - * @throws \InvalidArgumentException the given color is not valid - */ - public static function setForegroundColor($color) - { - if ((!is_int($color)) || (($color != ConsoleColor::OFF) && (($color < 1) || ($color > 8)) && (($color < 30) || ($color > 37)))) { - throw new \InvalidArgumentException('Invalid text color'); - } - - self::$foregroundColor = $color; - } - - /** - * Change the background color of the console. - * Look at the class ConsoleColor for a list ov available colors. - * - * @param int $color the console color code to be used - * - * @throws \InvalidArgumentException the given color is not valid - */ - public static function setBackgroundColor($color) - { - if ((!is_int($color)) || (($color != ConsoleColor::OFF) && (($color < 40) || ($color > 47)))) { - throw new \InvalidArgumentException('Invalid text color'); - } - - self::$backgroundColor = $color; - } - - /** - * Reset the foreground and background colors - * of the console to default values. - */ - public static function resetColors() - { - self::$foregroundColor = self::setForegroundColor(ConsoleColor::OFF); - self::$backgroundColor = self::setBackgroundColor(ConsoleColor::OFF); - } -} diff --git a/Gishiki/CLI/ConsoleColor.php b/Gishiki/CLI/ConsoleColor.php deleted file mode 100644 index 6333bd7c..00000000 --- a/Gishiki/CLI/ConsoleColor.php +++ /dev/null @@ -1,53 +0,0 @@ - - */ -abstract class ConsoleColor -{ - const OFF = 0; - - const TEXT_BOLD = 1; - const TEXT_ITALIC = 3; - const TEXT_UNDERLINE = 4; - const TEXT_BLINK = 5; - const TEXT_INVERSE = 7; - const TEXT_HIDDEN = 8; - - const TEXT_BLACK = 30; - const TEXT_RED = 31; - const TEXT_GREEN = 32; - const TEXT_YELLOW = 33; - const TEXT_BLUE = 34; - const TEXT_MAGENTA = 35; - const TEXT_CYAN = 36; - const TEXT_WHITE = 37; - - const BACKGROUND_BLACK = 40; - const BACKGROUND_RED = 41; - const BACKGROUND_GREEN = 42; - const BACKGROUND_YELLOW = 43; - const BACKGROUND_BLUE = 44; - const BACKGROUND_MAGENTA = 45; - const BACKGROUND_CYAN = 46; - const BACKGROUND_WHITE = 47; -} diff --git a/Gishiki/Core/Environment.php b/Gishiki/Core/Environment.php index 89ea80b0..9dc644b0 100755 --- a/Gishiki/Core/Environment.php +++ b/Gishiki/Core/Environment.php @@ -1,305 +1,283 @@ - - */ - final class Environment extends GenericCollection - { - /** - * Create a mock / fake environment from the given data. - * - * The given data is organized as the $_SERVER variable is - * - * @param array $userData Array of custom environment keys and values - * - * @return Environment - */ - public static function mock(array $userData = [], $selfRegister = false, $loadApplication = false) - { - $data = array_merge([ - 'SERVER_PROTOCOL' => 'HTTP/1.1', - 'REQUEST_METHOD' => 'GET', - 'SCRIPT_NAME' => '', - 'REQUEST_URI' => '', - 'QUERY_STRING' => '', - 'SERVER_NAME' => 'localhost', - 'SERVER_PORT' => 80, - 'HTTP_HOST' => 'localhost', - 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8', - 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', - 'HTTP_USER_AGENT' => 'Unknown', - 'REMOTE_ADDR' => '127.0.0.1', - 'REQUEST_TIME' => time(), - 'REQUEST_TIME_FLOAT' => microtime(true), - ], $userData); - - return new self($data, $selfRegister, $loadApplication); - } - - /** each environment has its configuration */ - private $configuration; - - /** this is the currently active environment */ - private static $currentEnvironment; - - /** - * Setup a new environment instance used to fulfill the client request. - * - * @param bool $selfRegister TRUE if the environment must be assigned as the currently valid one - */ - public function __construct(array $userData = [], $selfRegister = false, $loadApplication = false) - { - //call the collection constructor of this own class - parent::__construct($userData); - - //register the current environment - if ($selfRegister) { - self::registerEnvironment($this); - } - - if ($loadApplication) { - //load the server configuration - $this->loadConfiguration(); - } - } - - public static function getValueFromEnvironment(array $collection) - { - foreach ($collection as &$value) { - //check for sobstitution - if ((is_string($value)) && ((strpos($value, '{{@') === 0) && (strpos($value, '}}') !== false))) { - if (($toReplace = Manipulation::getBetween($value, '{{@', '}}')) != '') { - $value = getenv($toReplace); - if ($value !== false) { - $value = $value; - } elseif (defined($toReplace)) { - $value = constant($toReplace); - } - } - } elseif (is_array($value)) { - $value = self::getValueFromEnvironment($value); - } elseif ($value instanceof GenericCollection) { - $value = self::getValueFromEnvironment($value->all()); - } - } - - return $collection; - } - - /** - * Read the application configuration (settings.json) and return the - * parsing result. - * - * @return array the application configuration - */ - public static function getApplicationSettings() - { - //get the json encoded application settings - $config = file_get_contents(APPLICATION_DIR.'settings.json'); - - //parse the settings file - $appConfiguration = SerializableCollection::deserialize($config)->all(); - - //complete settings - $appComplConfig = self::getValueFromEnvironment($appConfiguration); - - //return the application configuration - return $appComplConfig; - } - - /** - * Check if the application to be executed exists, is valid and has the - * configuration file. - * - * @return bool the application existence - */ - public static function applicationExists() - { - //return the existence of an application directory and a configuratio file - return file_exists(APPLICATION_DIR.'settings.json'); - } - - /** - * Register the currently active environment. - * - * @param Environment $env the currently active environment - */ - public function registerEnvironment(Environment &$env) - { - //register the currently active environment - self::$currentEnvironment = $env; - } - - /** - * Fullfill the request made by the client. - */ - public function fulfillRequest() - { - //get current request... - $currentRequest = Request::createFromEnvironment(self::$currentEnvironment); - - //...and serve it - $response = new Response(); - - try { - //trigger the exception if data is malformed! - $currentRequest->getDeserializedBody(); - - //...and serve it - $response = Route::run($currentRequest); - } catch (\RuntimeException $ex) { - $response = $response->withStatus(400); - $response = $response->write($ex->getMessage()); - } - - //send response to the client - $response->send(); - } - - /** - * Return the currenlty active environment used to run the controller. - * - * @return Environment the current environment - */ - public static function getCurrentEnvironment() - { - //return the currently active environment - return self::$currentEnvironment; - } - - /** - * Load the framework configuration from the config file and return it in an - * format kwnown to the framework. - */ - private function loadConfiguration() - { - //get the security configuration of the current application - $config = []; - if (self::applicationExists()) { - $config = self::getApplicationSettings(); - //General Configuration - $this->configuration = [ - //get general environment configuration - 'DEVELOPMENT_ENVIRONMENT' => (isset($config['general']['development'])) ? $config['general']['development'] : false, - 'LOG_DEFAULT' => (isset($config['general']['autolog'])) ? $config['general']['autolog'] : null, - - //Security Settings - 'SECURITY' => [ - 'MASTER_SYMMETRIC_KEY' => $config['security']['serverPassword'], - 'MASTER_ASYMMETRIC_KEY' => $config['security']['serverKey'], - ], - - //Logger connections - 'LOGGERS' => (array_key_exists('loggers', $config)) ? $config['loggers'] : [], - - //Database connection - 'CONNECTIONS' => (array_key_exists('connections', $config)) ? $config['connections'] : [], - ]; - } - - //check for the environment configuration - if ($this->configuration['DEVELOPMENT_ENVIRONMENT']) { - ini_set('display_errors', 1); - error_reporting(E_ALL); - } else { - ini_set('display_errors', 0); - error_reporting(0); - } - - //connect every logger instance - foreach ($this->configuration['LOGGERS'] as $connectionName => &$connectionDetails) { - LoggerManager::connect($connectionName, $connectionDetails); - } - - //set the default logger connection - LoggerManager::setDefault($this->configuration['LOG_DEFAULT']); - - //connect every db connection - foreach ($this->configuration['CONNECTIONS'] as $connection) { - DatabaseManager::connect($connection['name'], $connection['query']); - } - } - - /** - * Return the configuration property. - * - * @param string $property the requested configuration property - * - * @return the requested configuration property or NULL - */ - public function getConfigurationProperty($property) - { - switch (strtoupper($property)) { - case 'DEVELOPMENT': - return $this->configuration['DEVELOPMENT_ENVIRONMENT']; - - case 'LOG_CONNECTION_STRING': - return $this->configuration['LOG_DEFAULT']; - - case 'DATA_CONNECTIONS': - return $this->configuration['DATABASE_CONNECTIONS']; - - case 'MASTER_ASYMMETRIC_KEY': - return $this->configuration['SECURITY']['MASTER_ASYMMETRIC_KEY']; - - case 'MASTER_SYMMETRIC_KEY': - return $this->configuration['SECURITY']['MASTER_SYMMETRIC_KEY']; - - case 'RESOURCE_DIR': - case 'RESOURCE_DIRECTORY': - return APPLICATION_DIR.'Resources'.DIRECTORY_SEPARATOR; - - case 'MODEL_DIR': - return APPLICATION_DIR.'Models'; - - case 'VIEW_DIR': - case 'VIEW_DIRECTORY': - return APPLICATION_DIR.'Views'.DIRECTORY_SEPARATOR; - - case 'CONTROLLER_DIR': - case 'CONTROLLER_DIRECTORY': - return APPLICATION_DIR.'Controllers'.DIRECTORY_SEPARATOR; - - case 'KEYS_DIR': - case 'KEYS_DIRECTORY': - case 'ASYMMETRIC_KEYS': - return APPLICATION_DIR.'Keyring'.DIRECTORY_SEPARATOR; - - case 'APPLICATION_DIR': - case 'APPLICATION_DIRECTORY': - return APPLICATION_DIR; - - default: - return; - } - } - } -} + + */ + final class Environment extends GenericCollection + { + /** + * @var mixed each environment has its configuration + */ + private $configuration; + + /** + * @var Environment this is the currently active environment + */ + private static $currentEnvironment; + + /** + * Setup a new environment instance used to fulfill the client request. + * + * @param array $userData the filtered $_SERVER array + * @param bool $selfRegister TRUE if the environment must be assigned as the currently valid one + * @param bool $loadApplication loads the entire application configuration if TRUE + */ + public function __construct(array $userData = [], $selfRegister = false, $loadApplication = false) + { + //call the collection constructor of this own class + parent::__construct($userData); + + //register the current environment + if ($selfRegister) { + self::registerEnvironment($this); + } + + if ($loadApplication) { + //load the server configuration + $this->loadConfiguration(); + } + } + + public static function getValueFromEnvironment(array $collection) + { + foreach ($collection as &$value) { + //check for substitution + if ((is_string($value)) && ((strpos($value, '{{@') === 0) && (strpos($value, '}}') !== false))) { + if (($toReplace = Manipulation::getBetween($value, '{{@', '}}')) != '') { + $value = getenv($toReplace); + if ($value !== false) { + $value = $value; + } elseif (defined($toReplace)) { + $value = constant($toReplace); + } + } + } elseif (is_array($value)) { + $value = self::getValueFromEnvironment($value); + } elseif ($value instanceof GenericCollection) { + $value = self::getValueFromEnvironment($value->all()); + } + } + + return $collection; + } + + /** + * Read the application configuration (settings.json) and return the + * parsing result. + * + * @return array the application configuration + */ + public static function getApplicationSettings() + { + //get the json encoded application settings + $config = file_get_contents(APPLICATION_DIR.'settings.json'); + + //parse the settings file + $appConfiguration = SerializableCollection::deserialize($config)->all(); + + //complete settings + $appComplConfig = self::getValueFromEnvironment($appConfiguration); + + //return the application configuration + return $appComplConfig; + } + + /** + * Check if the application to be executed exists, is valid and has the + * configuration file. + * + * @return bool the application existence + */ + public static function applicationExists() + { + //return the existence of an application directory and a configuratio file + return file_exists(APPLICATION_DIR.'settings.json'); + } + + /** + * Register the currently active environment. + * + * @param Environment $env the currently active environment + */ + public function registerEnvironment(Environment &$env) + { + //register the currently active environment + self::$currentEnvironment = $env; + } + + /** + * Fulfill the request made by the client. + */ + public function fulfillRequest(Router &$application) + { + //get current request... + $currentRequest = ServerRequestFactory::fromGlobals( + $_SERVER, + $_GET, + $_POST, + $_COOKIE, + $_FILES + ); + + //...generate the response + try { + $response = $application->run($currentRequest); + } catch (\Exception $ex) { + $response = new Response(); + $response = $response->withStatus(400); + $response = $response->write($ex->getMessage()); + } + + //...and serve it + $emitter = new SapiStreamEmitter(); + $emitter->emit($response); + } + + /** + * Return the currently active environment used to run the controller. + * + * @return Environment the current environment + */ + public static function getCurrentEnvironment() + { + //return the currently active environment + return self::$currentEnvironment; + } + + /** + * Load the framework configuration from the config file and return it in an + * format known to the framework. + */ + private function loadConfiguration() + { + //get the security configuration of the current application + $config = []; + if (self::applicationExists()) { + $config = self::getApplicationSettings(); + //General Configuration + $this->configuration = [ + //get general environment configuration + 'DEVELOPMENT_ENVIRONMENT' => (isset($config['general']['development'])) ? $config['general']['development'] : false, + 'LOG_DEFAULT' => (isset($config['general']['autolog'])) ? $config['general']['autolog'] : null, + + //Security Settings + 'SECURITY' => [ + 'MASTER_SYMMETRIC_KEY' => $config['security']['serverPassword'], + 'MASTER_ASYMMETRIC_KEY' => $config['security']['serverKey'], + ], + + //Logger connections + 'LOGGERS' => (array_key_exists('loggers', $config)) ? $config['loggers'] : [], + + //Database connection + 'CONNECTIONS' => (array_key_exists('connections', $config)) ? $config['connections'] : [], + ]; + } + + //check for the environment configuration + if ($this->configuration['DEVELOPMENT_ENVIRONMENT']) { + ini_set('display_errors', 1); + error_reporting(E_ALL); + } else { + ini_set('display_errors', 0); + error_reporting(0); + } + + //connect every logger instance + foreach ($this->configuration['LOGGERS'] as $connectionName => &$connectionDetails) { + LoggerManager::connect($connectionName, $connectionDetails); + } + + //set the default logger connection + LoggerManager::setDefault($this->configuration['LOG_DEFAULT']); + + //connect every db connection + foreach ($this->configuration['CONNECTIONS'] as $connection) { + DatabaseManager::connect($connection['name'], $connection['query']); + } + } + + /** + * Return the configuration property. + * + * @param string $property the requested configuration property + * + * @return the requested configuration property or NULL + */ + public function getConfigurationProperty($property) + { + switch (strtoupper($property)) { + case 'DEVELOPMENT': + return $this->configuration['DEVELOPMENT_ENVIRONMENT']; + + case 'LOG_CONNECTION_STRING': + return $this->configuration['LOG_DEFAULT']; + + case 'DATA_CONNECTIONS': + return $this->configuration['DATABASE_CONNECTIONS']; + + case 'MASTER_ASYMMETRIC_KEY': + return $this->configuration['SECURITY']['MASTER_ASYMMETRIC_KEY']; + + case 'MASTER_SYMMETRIC_KEY': + return $this->configuration['SECURITY']['MASTER_SYMMETRIC_KEY']; + + case 'RESOURCE_DIR': + case 'RESOURCE_DIRECTORY': + return APPLICATION_DIR.'Resources'.DIRECTORY_SEPARATOR; + + case 'MODEL_DIR': + return APPLICATION_DIR.'Models'; + + case 'VIEW_DIR': + case 'VIEW_DIRECTORY': + return APPLICATION_DIR.'Views'.DIRECTORY_SEPARATOR; + + case 'CONTROLLER_DIR': + case 'CONTROLLER_DIRECTORY': + return APPLICATION_DIR.'Controllers'.DIRECTORY_SEPARATOR; + + case 'KEYS_DIR': + case 'KEYS_DIRECTORY': + case 'ASYMMETRIC_KEYS': + return APPLICATION_DIR.'Keyring'.DIRECTORY_SEPARATOR; + + case 'APPLICATION_DIR': + case 'APPLICATION_DIRECTORY': + return APPLICATION_DIR; + + default: + return; + } + } + } +} diff --git a/Gishiki/Core/Exception.php b/Gishiki/Core/Exception.php index 36b4a3cb..5a5957d9 100755 --- a/Gishiki/Core/Exception.php +++ b/Gishiki/Core/Exception.php @@ -1,64 +1,64 @@ - - */ -class Exception extends \Exception -{ - /** - * Create a base exception and save the log of what's happening. - * - * @param string $message the error message - * @param int $errorCode the error code - */ - public function __construct($message, $errorCode) - { - //perform a basic Exception constructor call - parent::__construct($message, $errorCode, null); - - //retrieve the default logger instance - $logger = (!is_null(Environment::getCurrentEnvironment())) ? LoggerManager::retrieve() : null; - - //write the log of the exception - $this->reportOnLog($logger); - } - - /** - * Write the log message using the passed logger. - * - * @param $logger the PSR-3 logger instance to be used - */ - protected function reportOnLog($logger = null) - { - if (!is_null($logger)) { - //log the exception - $logger->error(get_called_class(). - ' thrown at: '.$this->getFile(). - ': '.$this->getLine(). - ' with message('.$this->getCode(). - '): '.$this->getMessage() - ); - } - } -} + + */ +class Exception extends \Exception +{ + /** + * Create a base exception and save the log of what's happening. + * + * @param string $message the error message + * @param int $errorCode the error code + */ + public function __construct($message, $errorCode) + { + //perform a basic Exception constructor call + parent::__construct($message, $errorCode, null); + + //retrieve the default logger instance + $logger = (!is_null(Environment::getCurrentEnvironment())) ? LoggerManager::retrieve() : null; + + //write the log of the exception + $this->reportOnLog($logger); + } + + /** + * Write the log message using the passed logger. + * + * @param $logger the PSR-3 logger instance to be used + */ + protected function reportOnLog($logger = null) + { + if (!is_null($logger)) { + //log the exception + $logger->error(get_called_class(). + ' thrown at: '.$this->getFile(). + ': '.$this->getLine(). + ' with message('.$this->getCode(). + '): '.$this->getMessage() + ); + } + } +} diff --git a/Gishiki/Core/MVC/Controller.php b/Gishiki/Core/MVC/Controller.php deleted file mode 100755 index 935b0592..00000000 --- a/Gishiki/Core/MVC/Controller.php +++ /dev/null @@ -1,132 +0,0 @@ - - */ -class Controller -{ - /** - * Execute the given controller. - * - * @param string $action the name of the controller and action to be used - * @param Request $request the request to serve - * @param Response $response the response to the given request - * @param GenericCollection $arguments the list of passed arguments - * - * @throws \InvalidArgumentException the given action identifier does not identify a valid action - */ - public static function execute($action, Request &$request, Response &$response, GenericCollection &$arguments) - { - //check for bad action - if ((!is_string($action)) || ((strpos($action, '@') === false) && (strpos($action, '->') === false))) { - throw new \InvalidArgumentException("The name of the controller to be executed must be expressed as: 'action@controller' or 'controller->action'"); - } - - //get the name of the action and the controller to be executed - $controller = (strpos($action, '@') !== false) ? explode('@', $action) : explode('->', $action); - - //check for bad names - if ((strlen($controller[0]) <= 0) || (strlen($controller[1]) <= 0)) { - throw new \InvalidArgumentException('The name of the action to be taken and controller to be selectad cannot be empty names'); - } - - if (strpos($action, '->') !== false) { - $temp = $controller[1]; - $controller[1] = $controller[0]; - $controller[0] = $temp; - } - - $controllerName = $controller[1]; - $controllerAction = $controller[0]; - - //and re-check for the given controller name - if (!class_exists($controllerName)) { - throw new \InvalidArgumentException('The given controller ('.$controllerName.') doesn\'t identify a valid controller'); - } - - //reflect the given controller class - $reflectedController = new \ReflectionClass($controllerName); - - //and create a new instance of it - $controllerMethod = $reflectedController->newInstanceArgs([&$request, &$response, &$arguments]); - - //reflect the requested action - $reflected_action = new \ReflectionMethod($controllerName, $controllerAction); - $reflected_action->setAccessible(true); //can invoke private methods :) - - //and execute it - $reflected_action->invoke($controllerMethod); - } - - /*************************************************************************** - * * - * Controller * - * * - **************************************************************************/ - - /** - * This is a clone of the request the client have send to this server. - * - * @var Request the request the controller must fulfill - */ - protected $request; - - /** - * This is the respone that will be sent back to the client from this server. - * - * @var Response the response the controller must generate - */ - protected $response; - - /** - * This is the collection of arguments passed to the URI. - * - * @var GenericCollection the collection of arguments passed to the URI - */ - protected $arguments; - - /** - * Create a new controller that will fulfill the given request filling the given response. - * - * @param Request $controllerRequest the request arrived from the client - * @param Response $controllerResponse the response to be given to the client - * @param GenericCollection $controllerArguments the collection of catched URI params - */ - public function __construct(Request &$controllerRequest, Response &$controllerResponse, GenericCollection &$controllerArguments) - { - //save the request - $this->request = $controllerRequest; - - //save the response - $this->response = $controllerResponse; - - //save the arguments collection - $this->arguments = $controllerArguments; - } -} diff --git a/Gishiki/Core/MVC/Controller/Controller.php b/Gishiki/Core/MVC/Controller/Controller.php new file mode 100644 index 00000000..f54daf45 --- /dev/null +++ b/Gishiki/Core/MVC/Controller/Controller.php @@ -0,0 +1,155 @@ + + */ +abstract class Controller +{ + /** + * This is a clone of the request the client have send to this server. + * + * @var RequestInterface the request the controller must fulfill + */ + protected $request; + + /** + * This is the response that will be sent back to the client from this server. + * + * @var ResponseInterface the response the controller must generate + */ + protected $response; + + /** + * This is the collection of arguments passed to the URI. + * + * @var GenericCollection the collection of arguments passed to the URI + */ + protected $arguments; + + /** + * @var array an array containing specified plugin collection as instantiated objects + */ + protected $plugins; + + /** + * Create a new controller that will fulfill the given request filling the given response. + * + * __Warning:__ you should *never* attempt to use another construction in your controllers, + * unless it calls parent::__construct(), and it doesn't accept arguments + * + * @param RequestInterface $controllerRequest the request arrived from the client + * @param ResponseInterface $controllerResponse the response to be given to the client + * @param GenericCollection $controllerArguments the collection of matched URI params + * @param array $plugins the array containing passed plugins + * @throws ControllerException the error preventing the controller creation + */ + public function __construct(RequestInterface &$controllerRequest, ResponseInterface &$controllerResponse, GenericCollection &$controllerArguments, array &$plugins) + { + //save the request + $this->request = $controllerRequest; + + //save the response + $this->response = $controllerResponse; + + //save the arguments collection + $this->arguments = $controllerArguments; + + //load middleware collection + $this->plugins = []; + foreach ($plugins as $pluginKey => &$pluginValue) { + try { + $reflectedMiddleware = new \ReflectionClass($pluginValue); + $this->plugins[$pluginKey] = $reflectedMiddleware->newInstanceArgs([&$this->request, &$this->response]); + } catch (\ReflectionException $ex) { + throw new ControllerException("Invalid plugin class", 1); + } + } + } + + /** + * Get the HTTP response. + * + * @return ResponseInterface the HTTP response + */ + public function &getResponse() : ResponseInterface + { + return $this->response; + } + + /** + * Get the HTTP request. + * + * @return RequestInterface the HTTP request + */ + public function &getRequest() : RequestInterface + { + return $this->request; + } + + /** + * Execute a function of any plugin that has been bind to this controller: + * + * + * class MyPlugin extends Plugin { + * public function doSomethingSpecial($arg1, $arg2) { + * + * } + * } + * + * //inside the controller: + * $this->doSomethingSpecial($name, $surname); + * + * + * @param string $name the name of the called function + * @param array $arguments the list of passed arguments as an array + * @return mixed the value returned from the function + * @throws ControllerException the function doesn't exists in any plugin + */ + public function __call($name, array $arguments) + { + $returnValue = null; + $executed = false; + + foreach($this->plugins as &$plugin) { + try { + $reflectedFunction = new \ReflectionMethod($plugin, $name); + $reflectedFunction->setAccessible(true); + $returnValue = $reflectedFunction->invokeArgs($plugin, $arguments); + + $executed = true; + } catch (\ReflectionException $ex) { + //there is nothing to be catched. Invoked method is not in this plugin + } + } + + if (!$executed) { + throw new ControllerException('None of loaded plugins implements '.$name.' function', 0); + } + + return $returnValue; + } +} diff --git a/Gishiki/Core/MVC/Controller/ControllerException.php b/Gishiki/Core/MVC/Controller/ControllerException.php new file mode 100644 index 00000000..b5ee64a1 --- /dev/null +++ b/Gishiki/Core/MVC/Controller/ControllerException.php @@ -0,0 +1,39 @@ + + */ +class ControllerException extends Exception +{ + /** + * Create the controller-related exception. + * + * @param string $message the error message + * @param int $errorCode the controller error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } +} \ No newline at end of file diff --git a/Gishiki/Core/MVC/Controller/Plugin.php b/Gishiki/Core/MVC/Controller/Plugin.php new file mode 100644 index 00000000..00646b6e --- /dev/null +++ b/Gishiki/Core/MVC/Controller/Plugin.php @@ -0,0 +1,74 @@ + + */ +abstract class Plugin +{ + /** + * @var RequestInterface reference to the HTTP request + */ + protected $request; + + /** + * @var ResponseInterface reference to the HTTP response + */ + protected $response; + + /** + * Plugin constructor. + * + * __Warning:__ you should *never* attempt to use another construction in your plugin, + * unless it calls parent::__construct() + * + * @param RequestInterface $request the HTTP request + * @param ResponseInterface $response the HTTP response + */ + public function __construct(RequestInterface &$request, ResponseInterface &$response) + { + $this->request = $request; + $this->response = $response; + } + + /** + * Get the HTTP response. + * + * @return ResponseInterface the HTTP response + */ + public function &getResponse() : ResponseInterface + { + return $this->response; + } + + /** + * Get the HTTP request. + * + * @return RequestInterface the HTTP request + */ + public function &getRequest() : RequestInterface + { + return $this->request; + } +} \ No newline at end of file diff --git a/Gishiki/Core/MVC/Controller/Plugins/RequestDeserializer.php b/Gishiki/Core/MVC/Controller/Plugins/RequestDeserializer.php new file mode 100644 index 00000000..f9abf07e --- /dev/null +++ b/Gishiki/Core/MVC/Controller/Plugins/RequestDeserializer.php @@ -0,0 +1,167 @@ + + */ +final class RequestDeserializer extends Plugin +{ + /** + * List of request body parsers (e.g., url-encoded, JSON, XML, multipart). + * + * @var \Callable[] + */ + protected $bodyParsers = []; + + /** + * Register media type parser. + * + * @param string[] $mediaTypes A HTTP media type (excluding content-type + * params) + * @param callable $callable A callable that returns parsed contents for + * media type + */ + public function registerMediaTypeParser(array $mediaTypes, callable $callable) + { + if ($callable instanceof \Closure) { + $callable = $callable->bindTo($this); + } + + foreach ($mediaTypes as $mediaType) { + $this->bodyParsers[(string)$mediaType] = &$callable; + } + } + + /** + * Deserializer constructor: + * setup the middleware importing deserializers + * + * @param RequestInterface $request the HTTP request + * @param ResponseInterface $response the HTTP response + */ + public function __construct(RequestInterface &$request, ResponseInterface &$response) + { + //this is important, NEVER forget! + parent::__construct($request, $response); + + $this->registerMediaTypeParser([ + 'text/yaml', + 'text/x-yaml', + 'application/yaml', + 'application/x-yaml', + ], function ($input) : SerializableCollection { + return SerializableCollection::deserialize($input, SerializableCollection::YAML); + }); + + $this->registerMediaTypeParser([ + 'application/json', + 'text/json', + ], function ($input) : SerializableCollection { + return SerializableCollection::deserialize($input, SerializableCollection::JSON); + }); + + $this->registerMediaTypeParser([ + 'text/xml', + 'application/xml', + ], function ($input) : SerializableCollection { + return SerializableCollection::deserialize($input, SerializableCollection::XML); + }); + + $this->registerMediaTypeParser([ + 'application/x-www-form-urlencoded', + 'multipart/form-data', + ], function ($input) : SerializableCollection { + $data = []; + parse_str($input, $data); + return new SerializableCollection($data); + }); + } + + /** + * Get request content type. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null The request content type, if known + */ + public function getRequestContentType() + { + $result = $this->getRequest()->getHeader('Content-Type'); + return $result ? $result[0] : null; + } + + /** + * Get request media type, if known. + * + * Note: This method is not part of the PSR-7 standard. + * + * @return string|null The request media type, minus content-type params + */ + public function getRequestMediaType() + { + $contentType = $this->getRequestContentType(); + if ($contentType) { + $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); + return strtolower($contentTypeParts[0]); + } + + return null; + } + + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content. + * + * @return SerializableCollection The deserialized body parameters, if any. + * These will typically be an array or object + * + * @throws DeserializationException if the request body is invalid + */ + public function getRequestDeserialized() : SerializableCollection + { + $body = (string)$this->getRequest()->getBody(); + $mediaType = $this->getRequestMediaType(); + + $bodyParsed = null; + + if ((strlen($mediaType) > 0) && (array_key_exists($mediaType, $this->bodyParsers))) { + $bodyParsed = $this->bodyParsers[$mediaType]($body); + } + + if (!($bodyParsed instanceof SerializableCollection)) { + throw new DeserializationException("Malformed data", 100); + } + + return $bodyParsed; + } +} \ No newline at end of file diff --git a/Gishiki/Core/MVC/Controller/Plugins/ResponseAssembler.php b/Gishiki/Core/MVC/Controller/Plugins/ResponseAssembler.php new file mode 100644 index 00000000..038966d8 --- /dev/null +++ b/Gishiki/Core/MVC/Controller/Plugins/ResponseAssembler.php @@ -0,0 +1,73 @@ + + */ +class ResponseAssembler extends Plugin +{ + + /** + * @var SerializableCollection the data to be filled + */ + protected $data; + + /** + * Assembler constructor: + * setup the response ready to be filled nd used + * + * @param RequestInterface $request the HTTP request + * @param ResponseInterface $response the HTTP response + */ + public function __construct(RequestInterface &$request, ResponseInterface &$response) + { + //this is important, NEVER forget! + parent::__construct($request, $response); + + $this->data = new SerializableCollection(); + } + + /** + * Execute the given task in order to fulfill the given request + * + * @param \Closure $callable the task to be ran + * @return mixed the value returned from the task + */ + public function assemblyWith(\Closure $callable) + { + return $callable($this->getRequest(), $this->getResponse(), $this->assembly()); + } + + /** + * Get what has been built by successive calls to assemblyWith. + * + * @return SerializableCollection what has been build + */ + public function &assembly() : SerializableCollection + { + return $this->data; + } +} \ No newline at end of file diff --git a/Gishiki/Core/MVC/Controller/Plugins/ResponseSerializer.php b/Gishiki/Core/MVC/Controller/Plugins/ResponseSerializer.php new file mode 100644 index 00000000..1adcb02e --- /dev/null +++ b/Gishiki/Core/MVC/Controller/Plugins/ResponseSerializer.php @@ -0,0 +1,117 @@ + + */ +final class ResponseSerializer extends Plugin +{ + const ALLOWED_TYPES = [ + 'text/yaml' => SerializableCollection::YAML, + 'text/x-yaml' => SerializableCollection::YAML, + 'application/yaml' => SerializableCollection::YAML, + 'application/x-yaml' => SerializableCollection::YAML, + 'application/json' => SerializableCollection::JSON, + 'text/json' => SerializableCollection::JSON, + 'text/xml' => SerializableCollection::XML, + 'application/xml' => SerializableCollection::XML, + ]; + + const DEFAULT_TYPE = 'application/json'; + + /** + * Serializer constructor: + * setup the plugin importing serializers + * + * @param RequestInterface $request the HTTP request + * @param ResponseInterface $response the HTTP response + */ + public function __construct(RequestInterface &$request, ResponseInterface &$response) + { + //this is important, NEVER forget! + parent::__construct($request, $response); + } + + /** + * Get the best accepted serialization format. + * + * If a compatible one is not provided than the default one is returned. + * + * @return string the accepted content type OR the default one + */ + public function getRequestAcceptedType() : string + { + $candidates = $this->getRequest()->getHeader('Accepted'); + + foreach ($candidates as $candidate) { + $contentTypeParts = preg_split('/\s*[;,]\s*/', $candidate); + $candidateMimeType = strtolower($contentTypeParts[0]); + + if (array_key_exists($candidateMimeType, self::ALLOWED_TYPES)) { + return $candidateMimeType; + } + } + + return self::DEFAULT_TYPE; + } + + /** + * Set the used serialization format. + * + * This function should be called only once by setResponseSerialized(). + * + * @param string $type the used serialization format + */ + public function setResponseContentType($type) + { + $this->getResponse()->withHeader('Content-Type', $type); + } + + /** + * Serialize given data using the best suitable serialization format. + * + * Write the result to the response body and place the format into the + * Content-Type HTTP header. + * + * @param SerializableCollection $data the data to be serialized + */ + public function setResponseSerialized(SerializableCollection $data) + { + //get a valid accepted format + $encodedType = $this->getRequestAcceptedType(); + + //get the serialization format + $format = self::ALLOWED_TYPES[$encodedType]; + + //serialize and send response.... + $this->getResponse()->getBody()->write( + $data->serialize($format) + ); + + //...telling the client bout the used serialization format + $this->setResponseContentType($encodedType); + } +} \ No newline at end of file diff --git a/Gishiki/Core/Route.php b/Gishiki/Core/Route.php deleted file mode 100755 index ab69600c..00000000 --- a/Gishiki/Core/Route.php +++ /dev/null @@ -1,588 +0,0 @@ - - */ - final class Route - { - /** - * This is the list of added routes. - * - * @var array a collection of routes - */ - private static $routes = []; - - /** - * This is the list of added callback routes. - * - * @var array a collection of callback routes - */ - private static $callbacks = []; - - /** - * Add a route to the route redirection list. - * - * @param Route $route the route to be added - * - * @return Route the added route - */ - public static function &addRoute(Route &$route) - { - //add the given route to the routes list - ($route->isSpecialCallback() === false) ? - self::$routes[] = &$route : - self::$callbacks[] = &$route; - - return $route; - } - - /* - * Used when the router were unable to route the request to a suitable - * controller/action because the URI couldn't be matched. - */ - const NOT_FOUND = 0; - - /* - * Commonly used requests methods (aka HTTP/HTTPS verbs) - */ - const ANY = 'any'; //not an http verb, used internally - const GET = 'GET'; - const POST = 'POST'; - const DELETE = 'DELETE'; - const HEAD = 'HEAD'; - const PUT = 'PUT'; - const PATCH = 'PATCH'; - const OPTIONS = 'OPTIONS'; - - /** - * Convinient proxy function to call Route::addRoute( ... ). - * - * - * use \Gishiki\Core\Route; - * - * Route::any("/user/{id}", function ($params) { - * //perform your amazing magic here - * }); - * - * - * - * - * @see \Gishiki\Core\Route\addRoute - * - * @param string $uri the URI that will bring to the function execution - * @param function $function the function executed when the URL is called - */ - public static function &any($uri, $function) - { - $route = new self($uri, $function, [ - self::ANY, - ]); - self::addRoute($route); - - return $route; - } - - /** - * Convinient proxy function to call Route::addRoute( ... ). - * - * - * use \Gishiki\Core\Route; - * - * Route::match([Route::GET, Route::POST], "/user/{id}", function ($params) { - * //perform your amazing magic here - * }); - * - * //you can also route an error: - * Route::match([Route::GET, Route::POST], Route::NOT_FOUND, function ($params) { - * //perform your failback amazing magic here! - * }); - * - * - * @see \Gishiki\Core\Route\addRoute - * - * @param string $uri the URI that will bring to the function execution - * @param function $function the function executed when the URL is called - */ - public static function &match($methods, $uri, $function) - { - if ((!is_array($methods)) || (count($methods) <= 0)) { - throw new \InvalidArgumentException('The collection of allowed methods must be given as a non-null array of strings'); - } - - $route = new self($uri, $function, $methods); - self::addRoute($route); - - return $route; - } - - /** - * Convinient proxy function to call Route::addRoute( ... ). - * - * - * use \Gishiki\Core\Route; - * - * Route::get("/user/{id}", function ($params) { - * //perform your amazing magic here - * }); - * - * //you can also route an error: - * Route::get(Route::NOT_FOUND, function ($params) { - * //perform your failback amazing magic here! - * }); - * - * - * @see \Gishiki\Core\Route\addRoute - * - * @param string $uri the URI that will bring to the function execution - * @param function $function the function executed when the URL is called - */ - public static function &get($uri, $function) - { - $route = new self($uri, $function, [self::GET]); - self::addRoute($route); - - return $route; - } - - /** - * Convinient proxy function to call Route::addRoute( ... ). - * - * - * use \Gishiki\Core\Route; - * - * Route::post("/user/{id}", function ($params) { - * //perform your amazing magic here - * }); - * //you can also route an error: - * Route::post(Route::NOT_FOUND, function ($params) { - * //perform your failback amazing magic here! - * }); - * - * - * @see \Gishiki\Core\Route\addRoute - * - * @param string $uri the URI that will bring to the function execution - * @param function $function the function executed when the URL is called - */ - public static function &post($uri, $function) - { - $route = new self($uri, $function, [self::POST]); - self::addRoute($route); - - return $route; - } - - /** - * Convinient proxy function to call Route::addRoute( ... ). - * - * - * use \Gishiki\Core\Route; - * - * Route::put("/user/{id}", function ($params) { - * //perform your amazing magic here - * }); - * //you can also route an error: - * Route::put(Route::NOT_FOUND, function ($params) { - * //perform your failback amazing magic here! - * }); - * - * - * @see \Gishiki\Core\Route\addRoute - * - * @param string $uri the URI that will bring to the function execution - * @param function $function the function executed when the URL is called - */ - public static function &put($uri, $function) - { - $route = new self($uri, $function, [self::PUT]); - self::addRoute($route); - - return $route; - } - - /** - * Convinient proxy function to call Route::addRoute( ... ). - * - * - * use \Gishiki\Core\Route; - * - * Route::delete("/user/{id}", function ($params) { - * //perform your amazing magic here - * }); - * - * //you can also route an error: - * Route::delete(Route::NOT_FOUND, function ($params) { - * //perform your failback amazing magic here! - * }); - * - * - * @see \Gishiki\Core\Route\addRoute - * - * @param string $uri the URI that will bring to the function execution - * @param function $function the function executed when the URL is called - */ - public static function &delete($uri, $function) - { - $route = new self($uri, $function, [self::DELETE]); - self::addRoute($route); - - return $route; - } - - /** - * Convinient proxy function to call Route::addRoute( ... ). - * - * - * use \Gishiki\Core\Route; - * - * Route::head("/user/{id}", function ($params) { - * //perform your amazing magic here - * }); - * - * - * @see \Gishiki\Core\Route\addRoute - * - * @param string $uri the URI that will bring to the function execution - * @param function $function the function executed when the URL is called - */ - public static function &head($uri, $function) - { - $route = new self($uri, $function, [self::HEAD]); - self::addRoute($route); - - return $route; - } - - /** - * Run the router and serve the current request. - * - * This function is __CALLED INTERNALLY__ and, therefore - * it __MUST NOT__ be called! - * - * @param Request $reqestToFulfill the request to be served/fulfilled - * - * @return Response $reqestToFulfill the request to be served/fulfilled - */ - public static function run(Request &$reqestToFulfill) - { - //derive the response from the current request - $response = Response::deriveFromRequest($reqestToFulfill); - $decodedUri = urldecode($reqestToFulfill->getUri()->getPath()); - $reversedParams = null; - - //this is the route that reference the action to be taken - $actionRuote = null; - - //test/try matching every route - foreach (self::$routes as $currentRoute) { - //build a collection from the current reverser URI (of detect the match failure) - $reversedParams = $currentRoute->matchURI($decodedUri, $reqestToFulfill->getMethod()); - if (is_object($reversedParams)) { - //execute the requested action! - $actionRuote = &$currentRoute; - - //stop searching for a suitable URI to be matched against the current one - break; - } - } - - //oh.... seems like we have a 404 Not Found.... - if (!is_object($actionRuote)) { - $response = $response->withStatus(404); - - foreach (self::$callbacks as $currentRoute) { - //check for a valid callback - if (is_object($currentRoute->matchURI(self::NOT_FOUND, $reqestToFulfill->getMethod()))) { - //flag the execution of this failback action! - $actionRuote = $currentRoute; - - //found what I was looking for, break the foreach - break; - } - } - } - - //execute the router call - $request = clone $reqestToFulfill; - $deductedParams = (is_object($reversedParams)) ? $reversedParams : new SerializableCollection(); - (is_object($actionRuote)) ? - $actionRuote($request, $response, $deductedParams) : null; - - //this function have to return a response - return $response; - } - - /*********************************************************************** - * - * NON-Static class members - * - **********************************************************************/ - - /** - * @var string the URI for the current route - */ - private $URI; - - /** - * @var mixed the anonymous function to be executed or the name of the action@controller - */ - private $action; - - /** - * @var array the list of allowed methods to be routed using the route URI - */ - private $methods; - - /** - * Create route instance that should be registered to the valid routes - * list:. - * - * - * $my_route = new Route("/user/{username}", function () { - * //make good things here - * }); - * - * Route::addRoute($my_route); - * - * - * @param string $uri the URI to be matched in order to take the given action - * @param Closure|string $action the action to be performed on URI match - * @param array $methods the list of allowed method for the current route - */ - public function __construct($uri, $action, array $methods = [self::GET, self::DELETE, self::POST, self::PUT, self::HEAD]) - { - //build-up the current route - $this->URI = (is_string($uri)) ? '/'.trim($uri, '/') : $uri; - $this->action = $action; - $this->methods = $methods; - } - - /** - * Return the list of methods allowed to be routed with the given URI. - * - * The return value is an array of allowed method (as strings): - * - * //this is an example: - * array( - * 'GET', - * 'DELETE' - * ); - * - * - * @return array the list of allowed methods - */ - public function getMethods() - { - return $this->methods; - } - - /** - * Get the type of the current route. - * - * The route type can be an integer for special callbacks - * (for example NOT_FOUND) or a boolean false for a valid string URI - * - * - * @return int|bool the callback type or false if it is a valid URI - */ - public function isSpecialCallback() - { - return (is_numeric($this->URI)) ? $this->URI : false; - } - - /** - * Attempt to match the given URI and mathod combination - * with the current route. - * - * @param string $uri the URI to be mtched - * @param string $method the used method - * - * @return GenericCollection|null the match result - */ - public function matchURI($uri, $method) - { - $reversedParams = null; - $methods = $this->getMethods(); - - if ($this->isSpecialCallback() === false) { - $regexData = $this->getRegex(); - - //try matching the regex against the currently requested URI - $matches = []; - - if (((in_array($method, $methods)) || (in_array(self::ANY, $methods))) && (preg_match($regexData['regex'], $uri, $matches))) { - $reversedUri = []; - $skipNum = 1; - foreach ($regexData['params'] as $currentKey => $currentMatchName) { - //get the value of the matched URI param - $value = $matches[$currentKey + $skipNum]; - - //filter the value of the matched URI param - switch ($regexData['param_types'][$currentKey]) { - case 'signed_integer': - $value = intval($value); - break; - - default: //should be used for 'email', 'default', etc. - $value = strval($value); - } - - //store the value of the matched URI param - $reversedUri[$currentMatchName] = $value; - $skipNum += $regexData['skipping_params'][$currentKey]; - } - - //build a collection from the current reverser URI - $reversedParams = new SerializableCollection($reversedUri); - } - } elseif (((in_array($method, $methods)) || (in_array(self::ANY, $methods))) && ($uri === $this->isSpecialCallback())) { - $reversedParams = new SerializableCollection(); - } - - return $reversedParams; - } - - /** - * Keeps a substitution table for regex and the relative groups. - * - * @var array the table of regex and regex groups - */ - private static $regexTable = [ - 'default' => ['[^\/]+', 0], - 'email' => ['([a-zA-Z0-9_\-.+]+)\@([a-zA-Z0-9-]+)\.([a-zA-Z]+)((\.([a-zA-Z]+))?)', 6], - 'signed_integer' => ['(\+|\-)?(\d)+', 2], - ]; - - /** - * build a regex out of the URI of the current Route and adds name of - * regex placeholders. - * - * Example: - * - * array( - * "regex" => "...", - * "params" => array("name", "surname") - * ) - * - * - * __Note:__ if the regex field of the returned array is an empty string, - * then the router is a special callback - * - * @return array the regex version of the URI and additional info - */ - public function getRegex() - { - //fix the URI - $regexURI = null; - - $paramArray = []; - $paramSkip = []; - $paramTypes = []; - - if ($this->isSpecialCallback() === false) { - //start building the regex - $regexURI = '/^'.preg_quote($this->URI, '/').'$/'; - - //this will contain the matched expressions placeholders - $params = []; - //detect if regex are involved in the furnished URI - if (preg_match_all("/\\\{([a-zA-Z]|\d|\_|\.|\:|\\\\)+\\\}/", $regexURI, $params)) { - //substitute a regex for each matching group: - foreach ($params[0] as $mathingGroup) { - //extract the regex to be used - $param = Manipulation::getBetween($mathingGroup, '\{', '\}'); - $regexId = explode('\\:', $param, 2); - - $currentRegex = ''; - if (count($regexId) == 2) { - $currentRegex = strval($regexId[1]); - } - - $param = $regexId[0]; - $regexTableId = 'default'; - switch (strtolower($currentRegex)) { - case 'mail': - case 'email': - $regexTableId = 'email'; - break; - - case 'number': - case 'integer': - case 'signed_integer': - $regexTableId = 'signed_integer'; - break; - - default: - $regexTableId = 'default'; - } - - $regexURI = str_replace($mathingGroup, '('.self::$regexTable[$regexTableId][0].')', $regexURI); - $paramArray[] = $param; - $paramTypes[] = $regexTableId; - $paramSkip[] = self::$regexTable[$regexTableId][1]; - } - } - } - - //return the built regex + additionals info - return [ - 'regex' => (!is_null($regexURI)) ? $regexURI : '', - 'params' => $paramArray, - 'param_types' => $paramTypes, - 'skipping_params' => $paramSkip, - ]; - } - - /** - * Execute the router callback, may it be a string (for action@controller) - * or an anonymous function. - * - * This function is called __AUTOMATICALLY__ by the framework when the - * route can be used to fulfill the given request. - * - * This function is provided for logical organization of the program and - * testing only! - * - * @param Request $request a copy of the request made to the application - * @param Response $response the action must fille, and what will be returned to the client - * @param GenericCollection $arguments a list of reversed URI parameters - */ - public function __invoke(Request &$request, Response &$response, GenericCollection &$arguments) - { - if (is_callable($this->action)) { - //execute the given action - call_user_func_array($this->action, [&$request, &$response, &$arguments]); - } elseif (is_string($this->action)) { - //execute the controller - Controller::execute($this->action, $request, $response, $arguments); - } - } - } -} diff --git a/Gishiki/Core/Router/Route.php b/Gishiki/Core/Router/Route.php new file mode 100644 index 00000000..7e0a46e1 --- /dev/null +++ b/Gishiki/Core/Router/Route.php @@ -0,0 +1,210 @@ + + */ +final class Route +{ + + const GET = 'GET'; + const POST = 'POST'; + const DELETE = 'DELETE'; + const HEAD = 'HEAD'; + const PUT = 'PUT'; + const PATCH = 'PATCH'; + const OPTIONS = 'OPTIONS'; + + + const OK = 200; + const NOT_FOUND = 404; + const NOT_ALLOWED = 405; + + /** + * @var array the route definition + */ + private $route = [ + "plugins" => [ + "deserializer" => DeserializerPlugin::class, + "serializer" => SerializerPlugin::class, + "assembler" => AssemblerPlugin::class, + ] + ]; + + /** + * Build a new route to be registered within a Gishiki\Core\Router instance. + * + * An usage example is: + * + * $route = new Route([ + * "verbs" => [ + * Route::GET + * ], + * "uri" => "/", + * "status" => Route::OK, + * "controller" => MyController::class, + * "action" => "index", + * ]); + * + * + * @param array $options The URI for the current route and more options + * + * @throws RouterException The route is malformed + */ + public function __construct(array $options) + { + foreach ($options as $key => $value) + { + if (is_string($key)) + { + if (strcmp(strtolower($key), "verbs") == 0) { + $this->route["verbs"] = $value; + } else if (strcmp(strtolower($key), "uri") == 0) { + $this->route["uri"] = $value; + } else if (strcmp(strtolower($key), "action") == 0) { + $this->route["action"] = $value; + } else if (strcmp(strtolower($key), "status") == 0) { + $this->route["status"] = $value; + } else if (strcmp(strtolower($key), "controller") == 0) { + $this->route["controller"] = $value; + } else if (strcmp(strtolower($key), "action") == 0) { + $this->route["action"] = $value; + } else if (strcmp(strtolower($key), "plugins") == 0) { + $this->route["plugins"] = $value; + } + } + } + + if (!is_string($this->route["uri"])) { + throw new RouterException("Invalid URI", 1); + } + + if (!is_array($this->route["verbs"])) { + throw new RouterException("Invalid HTTP Verbs", 2); + } + + if (!is_integer($this->route["status"])) { + throw new RouterException("Invalid HTTP Status code", 3); + } + + if (!is_string($this->route["controller"])) { + throw new RouterException("Invalid Controller: not a class name", 4); + } + + if (!class_exists($this->route["controller"])) { + throw new RouterException("Invalid Controller: class ".$this->route["controller"]." does't exists", 4); + } + + if (!is_string($this->route["action"])) { + throw new RouterException("Invalid Action: not a function name", 5); + } + + if (!method_exists($this->route["controller"], $this->route["action"])) { + throw new RouterException("Invalid Action: ".$this->route["action"]." is not a valid function of the ".$this->route["controller"]." class", 6); + } + + if (!is_array($this->route["plugins"])) { + throw new RouterException("Invalid plugin", 7); + } + + foreach ($this->route["plugins"] as $id => &$middleware) { + if ((!is_string($middleware)) || (!class_exists($middleware)) || (!is_subclass_of($middleware, Plugin::class))) { + throw new RouterException("The ".$id." plugin is not valid", 8); + } + } + } + + /** + * Execute the router callback, may it be a string (for controller->action) + * or an anonymous function. + * + * This function is called __AUTOMATICALLY__ by the framework when the + * route can be used to fulfill the given request. + * + * @param RequestInterface $request a copy of the request made to the application + * @param ResponseInterface $response the action must fille, and what will be returned to the client + * @param GenericCollection $arguments a list of reversed URI parameters + */ + public function __invoke(RequestInterface &$request, ResponseInterface &$response, GenericCollection &$arguments) + { + //import middleware + $plugins = $this->route["plugins"]; + + //start filling the response with the default status code + $response = $response->withStatus($this->getStatus()); + + //import controller name and action + $controllerName = $this->route["controller"]; + $controllerAction = $this->route["action"]; + + //reflect the given controller class + $reflectedController = new \ReflectionClass($controllerName); + + //and create a new instance of it + $controllerMethod = $reflectedController->newInstanceArgs([&$request, &$response, &$arguments, &$plugins]); + + //reflect the requested action + $reflected_action = new \ReflectionMethod($controllerName, $controllerAction); + $reflected_action->setAccessible(true); //can invoke private methods :) + + //and execute it + $reflected_action->invoke($controllerMethod); + } + + /** + * Get the URI mapped by this Route + * + * @return string the URI of this route + */ + public function getURI() : string + { + return $this->route["uri"]; + } + + /** + * Get the status code mapped by this Route + * + * @return integer the status code of this route + */ + public function getStatus() : int + { + return $this->route["status"]; + } + + /** + * Get the URI mapped by this Route + * + * @return array the list of HTTP verbs allowed + */ + public function getMethods() : array + { + return $this->route["verbs"]; + } +} diff --git a/Gishiki/Core/Router/Router.php b/Gishiki/Core/Router/Router.php new file mode 100644 index 00000000..aee3228d --- /dev/null +++ b/Gishiki/Core/Router/Router.php @@ -0,0 +1,221 @@ + + */ +final class Router +{ + /** + * @var array a list of registered Gishiki\Core\Route ordered my method to allow faster search + */ + private $routes = [ + Route::GET => [], + Route::POST => [], + Route::PUT => [], + Route::DELETE => [], + Route::HEAD => [], + Route::OPTIONS => [], + Route::PATCH => [] + ]; + + public function register(Route $route) + { + //put a reference to the object inside allowed methods for a faster search + foreach ($route->getMethods() as $method) { + if ((strcmp($method, Route::GET) == 0) || + (strcmp($method, Route::POST) == 0) || + (strcmp($method, Route::PUT) == 0) || + (strcmp($method, Route::DELETE) == 0) || + (strcmp($method, Route::HEAD) == 0) || + (strcmp($method, Route::OPTIONS) == 0) || + (strcmp($method, Route::PATCH) == 0)) + { + $this->routes[$method][] = &$route; + } + } + } + + /** + * Run the router and serve the current request. + * + * This function is __CALLED INTERNALLY__ and, therefore + * it __MUST NOT__ be called by the user! + * + * @param Request $requestToFulfill the request to be served/fulfilled + * + * @return Response the result + */ + public function run(Request &$requestToFulfill) + { + foreach ($this->routes[$requestToFulfill->getMethod()] as $currentRoute) { + $decodedUri = urldecode($requestToFulfill->getUri()->getPath()); + + $params = null; + + //if the current URL matches the current URI + if (self::matches($currentRoute->getURI(), $decodedUri, $params)) { + //derive the response from the current request + $response = new Response(); + + //execute the router call + $request = clone $requestToFulfill; + + //this will hold the parameters passed on the URL + $deductedParams = new GenericCollection($params); + + $currentRoute($request, $response, $deductedParams); + + //this function have to return a response + return $response; + } + } + } + + /** + * Check if a piece of URL matches a parameter of the given type. + * List of types: + * - 0 unsigned integer + * - 1 signed integer + * - 2 float + * - 3 string + * - 4 email + * + * @param $urlSplit string the piece of URL to be checked + * @param $type int the type of accepted parameter + * + * @return bool true on success, false otherwise + */ + private static function paramCheck($urlSplit, $type) : bool + { + switch ($type) + { + case 0: + return SimpleLexer::isUnsignedInteger($urlSplit); + + case 1: + return SimpleLexer::isSignedInteger($urlSplit); + + case 2: + return SimpleLexer::isFloat($urlSplit); + + case 3: + return SimpleLexer::isString($urlSplit); + + case 4: + return SimpleLexer::isEmail($urlSplit); + + default: + return false; + } + } + + /** + * Check weather a piece of an URL matches the corresponding piece of URI + * + * @param string $uriSplit the slice of URI to be checked + * @param string $urlSplit the slice of URL to be checked + * @param array $params used to register the correspondence (if any) + * @return bool true if the URL slice matches the URI slice, false otherwise + */ + private static function matchCheck($uriSplit, $urlSplit, array &$params) : bool + { + $result = false; + + if ((strlen($uriSplit) >= 7) && ($uriSplit[0] == '{') && ($uriSplit[strlen($uriSplit) - 1] == '}')) { + $uriSplitRev = substr($uriSplit, 1, strlen($uriSplit) - 2); + $uriSplitExploded = explode(':', $uriSplitRev); + $uriParamType = strtolower($uriSplitExploded[1]); + + $type = null; + + if (strcmp($uriParamType, 'uint') == 0) { + $type = 0; + } else if (strcmp($uriParamType, 'int') == 0) { + $type = 1; + } else if ((strcmp($uriParamType, 'str') == 0) || (strcmp($uriParamType, 'string') == 0)) { + $type = 3; + } else if (strcmp($uriParamType, 'float') == 0) { + $type = 2; + } else if ((strcmp($uriParamType, 'email') == 0) || (strcmp($uriParamType, 'mail') == 0)) { + $type = 4; + } + + //check the url piece against one of the given model + if (self::paramCheck($urlSplit, $type)) { + //matched url piece with the correct type: "1" checked against a string has to become 1 + $urlSplitCType = $urlSplit; + $urlSplitCType = (($type == 0) || ($type == 1)) ? intval($urlSplit) : $urlSplitCType; + $urlSplitCType = ($type == 2) ? floatval($urlSplit) : $urlSplitCType; + + $result = true; + $params[$uriSplitExploded[0]] = $urlSplitCType; + } + } else if (strcmp($uriSplit, $urlSplit) == 0) { + $result = true; + } + + return $result; + } + + /** + * Check if the given URL matches the route URI. + * $matchedExpr is given as an associative array: name => value + * + * @param string $uri the URI to be matched against the given URL + * @param string $url the URL to be matched + * @param mixed $matchedExpr an *empty* array + * @return bool true if the URL matches the URI, false otherwise + */ + public static function matches($uri, $url, &$matchedExpr) : bool + { + if ((!is_string($url)) || (strlen($url) <= 0)) { + throw new \InvalidArgumentException("The URL must be given as a non-empty string"); + } + + if ((!is_string($uri)) || (strlen($uri) <= 0)) { + throw new \InvalidArgumentException("The URI must be given as a non-empty string"); + } + + $matchedExpr = []; + $result = true; + + $urlSlices = explode('/', $url); + $uriSlices = explode('/', $uri); + + $slicesCount = count($uriSlices); + if ($slicesCount != count($urlSlices)) { + return false; + } + + for ($i = 0; ($i < $slicesCount) && ($result); $i++) { + //try matching the current URL slice with the current URI slice + $result = self::matchCheck($uriSlices[$i], $urlSlices[$i], $matchedExpr); + } + + return $result; + } +} \ No newline at end of file diff --git a/Gishiki/Core/Router/RouterException.php b/Gishiki/Core/Router/RouterException.php new file mode 100644 index 00000000..986e6a56 --- /dev/null +++ b/Gishiki/Core/Router/RouterException.php @@ -0,0 +1,40 @@ + + */ +class RouterException extends Exception +{ + /** + * Create the router-related exception. + * + * @param string $message the error message + * @param int $errorCode the router error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } +} \ No newline at end of file diff --git a/Gishiki/Database/Adapters/Mysql.php b/Gishiki/Database/Adapters/Mysql.php index 5e205135..27c3b859 100644 --- a/Gishiki/Database/Adapters/Mysql.php +++ b/Gishiki/Database/Adapters/Mysql.php @@ -1,89 +1,85 @@ - - */ -final class Mysql extends PDODatabase -{ - /** - * {@inheritdoc} - */ - protected function getPDODriverName() - { - return 'mysql'; - } - - protected function generateConnectionQuery($details) - { - if (!is_string($details)) { - throw new \InvalidArgumentException("connection information provided are invalid"); - } - - $user = null; - $password = null; - - $userPosition = strpos($details, 'user='); - if ($userPosition !== false) { - $firstUserCharPosition = $userPosition + strlen('user='); - $endingUserCharPosition = strpos($details, ';', $firstUserCharPosition); - $lasUserCharPosition = ($endingUserCharPosition !== false) ? - $endingUserCharPosition : strlen($details); - $user = substr($details, $firstUserCharPosition, $lasUserCharPosition - $firstUserCharPosition); - $details = ($endingUserCharPosition !== false) ? - str_replace('user=' . $user . ';', '', $details) : - str_replace('user=' . $user, '', $details); - } - - $passwordPosition = strpos($details, 'password='); - if ($passwordPosition !== false) { - $firstPassCharPosition = $passwordPosition + strlen('password='); - $endingPassCharPosition = strpos($details, ';', $firstPassCharPosition); - $lasPassCharPosition = ($endingPassCharPosition !== false) ? - $endingPassCharPosition : strlen($details); - $password = substr($details, $firstPassCharPosition, $lasPassCharPosition - $firstPassCharPosition); - $details = ($endingPassCharPosition !== false) ? - str_replace('password=' . $password . ';', '', $details) : - str_replace('password=' . $password, '', $details); - } - - return [ - $this->getPDODriverName().':'.$details, - $user, - $password, - null - ]; - } - - /** - * Get the query builder for SQLite. - * - * @return SQLiteQueryBuilder the query builder for the used pdo adapter - */ - protected function getQueryBuilder() - { - return new MySQLQueryBuilder(); - } - + + */ +final class Mysql extends PDODatabase +{ + /** + * {@inheritdoc} + */ + protected function getPDODriverName() + { + return 'mysql'; + } + + protected function generateConnectionQuery($details) + { + $user = null; + $password = null; + + $userPosition = strpos($details, 'user='); + if ($userPosition !== false) { + $firstUserCharPosition = $userPosition + strlen('user='); + $endingUserCharPosition = strpos($details, ';', $firstUserCharPosition); + $lasUserCharPosition = ($endingUserCharPosition !== false) ? + $endingUserCharPosition : strlen($details); + $user = substr($details, $firstUserCharPosition, $lasUserCharPosition - $firstUserCharPosition); + $details = ($endingUserCharPosition !== false) ? + str_replace('user=' . $user . ';', '', $details) : + str_replace('user=' . $user, '', $details); + } + + $passwordPosition = strpos($details, 'password='); + if ($passwordPosition !== false) { + $firstPassCharPosition = $passwordPosition + strlen('password='); + $endingPassCharPosition = strpos($details, ';', $firstPassCharPosition); + $lasPassCharPosition = ($endingPassCharPosition !== false) ? + $endingPassCharPosition : strlen($details); + $password = substr($details, $firstPassCharPosition, $lasPassCharPosition - $firstPassCharPosition); + $details = ($endingPassCharPosition !== false) ? + str_replace('password=' . $password . ';', '', $details) : + str_replace('password=' . $password, '', $details); + } + + return [ + $this->getPDODriverName().':'.$details, + $user, + $password, + null + ]; + } + + /** + * Get the query builder for SQLite. + * + * @return SQLiteQueryBuilder the query builder for the used pdo adapter + */ + protected function getQueryBuilder() + { + return new MySQLQueryBuilder(); + } + } \ No newline at end of file diff --git a/Gishiki/Database/Adapters/PDODatabase.php b/Gishiki/Database/Adapters/PDODatabase.php index 408e2ca0..b4bfccdc 100644 --- a/Gishiki/Database/Adapters/PDODatabase.php +++ b/Gishiki/Database/Adapters/PDODatabase.php @@ -1,387 +1,383 @@ - - */ -class PDODatabase implements RelationalDatabaseInterface -{ - /** - * Return the name of the PDO driver to be used for this database type. - * - * @return string the PDO driver name - */ - protected function getPDODriverName() - { - return ''; - } - - /** - * Generate a PDO connection string that will be used to connect a database. - * - * @param mixed $details information used to open a database connection with PDO - * @throws \InvalidArgumentException invalid connection details - * @return string the string to be passed to the PDO driver - */ - protected function generateConnectionQuery($details) - { - if (!is_string($details)) { - throw new \InvalidArgumentException("connection information provided are invalid"); - } - - return [ - $this->getPDODriverName().':'.$details, - null, - null, - null - ]; - } - - /** - * Get the query builder for the current RDBMS. - * - * @return SQLQueryBuilder the query builder for the used pdo adapter - */ - protected function getQueryBuilder() - { - return new SQLQueryBuilder(); - } - - /** - * @var bool TRUE only if the connection is alive - */ - protected $connected; - - /** - * @var \PDO the native pdo connection - */ - protected $connection; - - /** - * Create a new database connection using the given connection string. - * - * The connect function is automatically called. - * - * @param string $details the connection string - */ - public function __construct($details) - { - $this->connection = []; - $this->connected = false; - - //connect to the database - $this->connect($details); - } - - /** - * {@inheritdoc} - */ - public function connect($details) - { - //check for argument type - if ((!is_string($details)) || (strlen($details) <= 0)) { - throw new \InvalidArgumentException('The connection query must be given as a non-empty string'); - } - - //check for the pdo driver - if ((strlen($this->getPDODriverName()) > 0) && (!in_array($this->getPDODriverName(), \PDO::getAvailableDrivers()))) { - throw new DatabaseException('No '.$this->getPDODriverName().' PDO driver', 0); - } - - //open the connection - try { - $connectionInfo = $this->generateConnectionQuery($details); - - $this->connection = new \PDO($connectionInfo[0], $connectionInfo[1], $connectionInfo[2], $connectionInfo[3]); - $this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - - //the connection is opened - $this->connected = true; - } catch (\PDOException $ex) { - throw new DatabaseException('Error while opening the database connection:'.$ex->getMessage(), 1); - } - } - - /** - * {@inheritdoc} - */ - public function close() - { - $this->connection = []; - $this->connected = false; - } - - /** - * {@inheritdoc} - */ - public function createTable(Table $tb) - { - //check for closed database connection - if (!$this->connected()) { - throw new DatabaseException('The database connection must be opened before executing any operation', 2); - } - - //build the sql query - $queryBuilder = $this->getQueryBuilder()->createTableQuery($tb); - - //open a new statement and execute it - try { - //prepare a statement with that safe sql string - $stmt = $this->connection->prepare($queryBuilder->exportQuery()); - - //execute the statement resolving placeholders - $stmt->execute($queryBuilder->exportParams()); - } catch (\PDOException $ex) { - throw new DatabaseException('Error while performing the table creation operation: '.$ex->getMessage(), 7); - } - } - - /** - * {@inheritdoc} - */ - public function connected() - { - return $this->connected; - } - - /** - * {@inheritdoc} - */ - public function create($collection, $data) - { - //check for invalid database name - if ((!is_string($collection)) || (strlen($collection) <= 0)) { - throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); - } - - //check for invalid data collection - if ((!is_array($data)) && (!($data instanceof CollectionInterface))) { - throw new \InvalidArgumentException('The data to be written on the database must be given as a collection'); - } - - //check for closed database connection - if (!$this->connected()) { - throw new DatabaseException('The database connection must be opened before executing any operation', 2); - } - - //get an associative array of the input data - $adaptedData = ($data instanceof CollectionInterface) ? $data->all() : $data; - - //build the sql query - $queryBuilder = $this->getQueryBuilder()->insertQuery($collection, $adaptedData); - - //open a new statement and execute it - try { - //prepare a statement with that safe sql string - $stmt = $this->connection->prepare($queryBuilder->exportQuery()); - - //execute the statement resolving placeholders - $stmt->execute($queryBuilder->exportParams()); - - //as per documentation return the id of the last inserted row - return $this->connection->lastInsertId(); - } catch (\PDOException $ex) { - throw new DatabaseException('Error while performing the creation operation: '.$ex->getMessage(), 3); - } - } - - /** - * {@inheritdoc} - */ - public function update($collection, $data, SelectionCriteria $where) - { - //check for invalid database name - if ((!is_string($collection)) || (strlen($collection) <= 0)) { - throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); - } - - //check for invalid data collection - if ((!is_array($data)) && (!($data instanceof CollectionInterface))) { - throw new \InvalidArgumentException('The data to be written on the database must be given as a collection'); - } - - //check for closed database connection - if (!$this->connected()) { - throw new DatabaseException('The database connection must be opened before executing any operation', 2); - } - - //get an associative array of the input data - $adaptedData = ($data instanceof CollectionInterface) ? $data->all() : $data; - - //build the sql query - $queryBuilder = $this->getQueryBuilder()->updateQuery($collection, $adaptedData, $where); - - //open a new statement and execute it - try { - //prepare a statement with that safe sql string - $sql = $queryBuilder->exportQuery(); - $stmt = $this->connection->prepare($sql); - - //execute the statement resolving placeholders - $stmt->execute($queryBuilder->exportParams()); - - //return the number of affected rows - return $stmt->rowCount(); - } catch (\PDOException $ex) { - throw new DatabaseException('Error while performing the update operation: '.$ex->getMessage(), 4); - } - } - - /** - * {@inheritdoc} - */ - public function delete($collection, SelectionCriteria $where) - { - //check for invalid database name - if ((!is_string($collection)) || (strlen($collection) <= 0)) { - throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); - } - - //check for closed database connection - if (!$this->connected()) { - throw new DatabaseException('The database connection must be opened before executing any operation', 2); - } - - //build the sql query - $queryBuilder = $this->getQueryBuilder()->deleteQuery($collection, $where); - - //open a new statement and execute it - try { - //prepare a statement with that safe sql string - $stmt = $this->connection->prepare($queryBuilder->exportQuery()); - - //execute the statement resolving placeholders - $stmt->execute($queryBuilder->exportParams()); - - //return the number of affected rows - return $stmt->rowCount(); - } catch (\PDOException $ex) { - throw new DatabaseException('Error while performing the delete operation: '.$ex->getMessage(), 5); - } - } - - /** - * {@inheritdoc} - */ - public function deleteAll($collection) - { - //check for invalid database name - if ((!is_string($collection)) || (strlen($collection) <= 0)) { - throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); - } - - //check for closed database connection - if (!$this->connected()) { - throw new DatabaseException('The database connection must be opened before executing any operation', 2); - } - - //build the sql query - $queryBuilder = $this->getQueryBuilder()->deleteAllQuery($collection); - - //open a new statement and execute it - try { - //prepare a statement with that safe sql string - $stmt = $this->connection->prepare($queryBuilder->exportQuery()); - - //execute the statement resolving placeholders - $stmt->execute($queryBuilder->exportParams()); - - //return the number of affected rows - return $stmt->rowCount(); - } catch (\PDOException $ex) { - throw new DatabaseException('Error while performing the delete operation: '.$ex->getMessage(), 5); - } - } - - /** - * {@inheritdoc} - */ - public function read($collection, SelectionCriteria $where, ResultModifier $mod) - { - //check for invalid database name - if ((!is_string($collection)) || (strlen($collection) <= 0)) { - throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); - } - - //check for closed database connection - if (!$this->connected()) { - throw new DatabaseException('The database connection must be opened before executing any operation', 2); - } - - //build the sql query - $queryBuilder = $this->getQueryBuilder()->readQuery($collection, $where, $mod); - - //open a new statement and execute it - try { - //prepare a statement with that safe sql string - $stmt = $this->connection->prepare($queryBuilder->exportQuery()); - - //execute the statement resolving placeholders - $stmt->execute($queryBuilder->exportParams()); - - //return an associative array of data - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - } catch (\PDOException $ex) { - throw new DatabaseException('Error while performing the read operation: '.$ex->getMessage(), 6); - } - } - - /** - * {@inheritdoc} - */ - public function readSelective($collection, $fields, SelectionCriteria $where, ResultModifier $mod) - { - //check for invalid database name - if ((!is_string($collection)) || (strlen($collection) <= 0)) { - throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); - } - - //check for closed database connection - if (!$this->connected()) { - throw new DatabaseException('The database connection must be opened before executing any operation', 2); - } - - //build the sql query - $queryBuilder = $this->getQueryBuilder()->selectiveReadQuery($collection, $fields, $where, $mod); - - //open a new statement and execute it - try { - //prepare a statement with that safe sql string - $stmt = $this->connection->prepare($queryBuilder->exportQuery()); - - //execute the statement resolving placeholders - $stmt->execute($queryBuilder->exportParams()); - - //return the fetch result - return $stmt->fetchAll(\PDO::FETCH_ASSOC); - } catch (\PDOException $ex) { - throw new DatabaseException('Error while performing the read operation: '.$ex->getMessage(), 6); - } - } + + */ +class PDODatabase implements RelationalDatabaseInterface +{ + /** + * Return the name of the PDO driver to be used for this database type. + * + * @return string the PDO driver name + */ + protected function getPDODriverName() + { + return ''; + } + + /** + * Generate a PDO connection string that will be used to connect a database. + * + * @param mixed $details information used to open a database connection with PDO + * @throws \InvalidArgumentException invalid connection details + * @return string the string to be passed to the PDO driver + */ + protected function generateConnectionQuery($details) + { + return [ + $this->getPDODriverName().':'.$details, + null, + null, + null + ]; + } + + /** + * Get the query builder for the current RDBMS. + * + * @return SQLQueryBuilder the query builder for the used pdo adapter + */ + protected function getQueryBuilder() + { + return new SQLQueryBuilder(); + } + + /** + * @var bool TRUE only if the connection is alive + */ + protected $connected; + + /** + * @var \PDO the native pdo connection + */ + protected $connection; + + /** + * Create a new database connection using the given connection string. + * + * The connect function is automatically called. + * + * @param string $details the connection string + */ + public function __construct($details) + { + $this->connection = []; + $this->connected = false; + + //connect to the database + $this->connect($details); + } + + /** + * {@inheritdoc} + */ + public function connect($details) + { + //check for argument type + if ((!is_string($details)) || (strlen($details) <= 0)) { + throw new \InvalidArgumentException('The connection query must be given as a non-empty string'); + } + + //check for the pdo driver + if ((strlen($this->getPDODriverName()) > 0) && (!in_array($this->getPDODriverName(), \PDO::getAvailableDrivers()))) { + throw new DatabaseException('No '.$this->getPDODriverName().' PDO driver', 0); + } + + //open the connection + try { + $connectionInfo = $this->generateConnectionQuery($details); + + $this->connection = new \PDO($connectionInfo[0], $connectionInfo[1], $connectionInfo[2], $connectionInfo[3]); + $this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + //the connection is opened + $this->connected = true; + } catch (\PDOException $ex) { + throw new DatabaseException('Error while opening the database connection:'.$ex->getMessage(), 1); + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->connection = []; + $this->connected = false; + } + + /** + * {@inheritdoc} + */ + public function createTable(Table $tb) + { + //check for closed database connection + if (!$this->connected()) { + throw new DatabaseException('The database connection must be opened before executing any operation', 2); + } + + //build the sql query + $queryBuilder = $this->getQueryBuilder()->createTableQuery($tb); + + //open a new statement and execute it + try { + //prepare a statement with that safe sql string + $stmt = $this->connection->prepare($queryBuilder->exportQuery()); + + //execute the statement resolving placeholders + $stmt->execute($queryBuilder->exportParams()); + } catch (\PDOException $ex) { + throw new DatabaseException('Error while performing the table creation operation: '.$ex->getMessage(), 7); + } + } + + /** + * {@inheritdoc} + */ + public function connected() + { + return $this->connected; + } + + /** + * {@inheritdoc} + */ + public function create($collection, $data) + { + //check for invalid database name + if ((!is_string($collection)) || (strlen($collection) <= 0)) { + throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); + } + + //check for invalid data collection + if ((!is_array($data)) && (!($data instanceof CollectionInterface))) { + throw new \InvalidArgumentException('The data to be written on the database must be given as a collection'); + } + + //check for closed database connection + if (!$this->connected()) { + throw new DatabaseException('The database connection must be opened before executing any operation', 2); + } + + //get an associative array of the input data + $adaptedData = ($data instanceof CollectionInterface) ? $data->all() : $data; + + //build the sql query + $queryBuilder = $this->getQueryBuilder()->insertQuery($collection, $adaptedData); + + //open a new statement and execute it + try { + //prepare a statement with that safe sql string + $stmt = $this->connection->prepare($queryBuilder->exportQuery()); + + //execute the statement resolving placeholders + $stmt->execute($queryBuilder->exportParams()); + + //as per documentation return the id of the last inserted row + return $this->connection->lastInsertId(); + } catch (\PDOException $ex) { + throw new DatabaseException('Error while performing the creation operation: '.$ex->getMessage(), 3); + } + } + + /** + * {@inheritdoc} + */ + public function update($collection, $data, SelectionCriteria $where) + { + //check for invalid database name + if ((!is_string($collection)) || (strlen($collection) <= 0)) { + throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); + } + + //check for invalid data collection + if ((!is_array($data)) && (!($data instanceof CollectionInterface))) { + throw new \InvalidArgumentException('The data to be written on the database must be given as a collection'); + } + + //check for closed database connection + if (!$this->connected()) { + throw new DatabaseException('The database connection must be opened before executing any operation', 2); + } + + //get an associative array of the input data + $adaptedData = ($data instanceof CollectionInterface) ? $data->all() : $data; + + //build the sql query + $queryBuilder = $this->getQueryBuilder()->updateQuery($collection, $adaptedData, $where); + + //open a new statement and execute it + try { + //prepare a statement with that safe sql string + $sql = $queryBuilder->exportQuery(); + $stmt = $this->connection->prepare($sql); + + //execute the statement resolving placeholders + $stmt->execute($queryBuilder->exportParams()); + + //return the number of affected rows + return $stmt->rowCount(); + } catch (\PDOException $ex) { + throw new DatabaseException('Error while performing the update operation: '.$ex->getMessage(), 4); + } + } + + /** + * {@inheritdoc} + */ + public function delete($collection, SelectionCriteria $where) + { + //check for invalid database name + if ((!is_string($collection)) || (strlen($collection) <= 0)) { + throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); + } + + //check for closed database connection + if (!$this->connected()) { + throw new DatabaseException('The database connection must be opened before executing any operation', 2); + } + + //build the sql query + $queryBuilder = $this->getQueryBuilder()->deleteQuery($collection, $where); + + //open a new statement and execute it + try { + //prepare a statement with that safe sql string + $stmt = $this->connection->prepare($queryBuilder->exportQuery()); + + //execute the statement resolving placeholders + $stmt->execute($queryBuilder->exportParams()); + + //return the number of affected rows + return $stmt->rowCount(); + } catch (\PDOException $ex) { + throw new DatabaseException('Error while performing the delete operation: '.$ex->getMessage(), 5); + } + } + + /** + * {@inheritdoc} + */ + public function deleteAll($collection) + { + //check for invalid database name + if ((!is_string($collection)) || (strlen($collection) <= 0)) { + throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); + } + + //check for closed database connection + if (!$this->connected()) { + throw new DatabaseException('The database connection must be opened before executing any operation', 2); + } + + //build the sql query + $queryBuilder = $this->getQueryBuilder()->deleteAllQuery($collection); + + //open a new statement and execute it + try { + //prepare a statement with that safe sql string + $stmt = $this->connection->prepare($queryBuilder->exportQuery()); + + //execute the statement resolving placeholders + $stmt->execute($queryBuilder->exportParams()); + + //return the number of affected rows + return $stmt->rowCount(); + } catch (\PDOException $ex) { + throw new DatabaseException('Error while performing the delete operation: '.$ex->getMessage(), 5); + } + } + + /** + * {@inheritdoc} + */ + public function read($collection, SelectionCriteria $where, ResultModifier $mod) + { + //check for invalid database name + if ((!is_string($collection)) || (strlen($collection) <= 0)) { + throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); + } + + //check for closed database connection + if (!$this->connected()) { + throw new DatabaseException('The database connection must be opened before executing any operation', 2); + } + + //build the sql query + $queryBuilder = $this->getQueryBuilder()->readQuery($collection, $where, $mod); + + //open a new statement and execute it + try { + //prepare a statement with that safe sql string + $stmt = $this->connection->prepare($queryBuilder->exportQuery()); + + //execute the statement resolving placeholders + $stmt->execute($queryBuilder->exportParams()); + + //return an associative array of data + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } catch (\PDOException $ex) { + throw new DatabaseException('Error while performing the read operation: '.$ex->getMessage(), 6); + } + } + + /** + * {@inheritdoc} + */ + public function readSelective($collection, $fields, SelectionCriteria $where, ResultModifier $mod) + { + //check for invalid database name + if ((!is_string($collection)) || (strlen($collection) <= 0)) { + throw new \InvalidArgumentException('The name of the table must be given as a non-empty string'); + } + + //check for closed database connection + if (!$this->connected()) { + throw new DatabaseException('The database connection must be opened before executing any operation', 2); + } + + //build the sql query + $queryBuilder = $this->getQueryBuilder()->selectiveReadQuery($collection, $fields, $where, $mod); + + //open a new statement and execute it + try { + //prepare a statement with that safe sql string + $stmt = $this->connection->prepare($queryBuilder->exportQuery()); + + //execute the statement resolving placeholders + $stmt->execute($queryBuilder->exportParams()); + + //return the fetch result + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } catch (\PDOException $ex) { + throw new DatabaseException('Error while performing the read operation: '.$ex->getMessage(), 6); + } + } } \ No newline at end of file diff --git a/Gishiki/Database/Adapters/Pgsql.php b/Gishiki/Database/Adapters/Pgsql.php index f79d51f8..7588fc05 100644 --- a/Gishiki/Database/Adapters/Pgsql.php +++ b/Gishiki/Database/Adapters/Pgsql.php @@ -1,47 +1,47 @@ - - */ -final class Pgsql extends PDODatabase -{ - /** - * {@inheritdoc} - */ - protected function getPDODriverName() - { - return 'pgsql'; - } - - /** - * Get the query builder for PostgreSQL. - * - * @return PostgreSQLQueryBuilder the query builder for the used pdo adapter - */ - protected function getQueryBuilder() - { - return new PostgreSQLQueryBuilder(); - } - + + */ +final class Pgsql extends PDODatabase +{ + /** + * {@inheritdoc} + */ + protected function getPDODriverName() + { + return 'pgsql'; + } + + /** + * Get the query builder for PostgreSQL. + * + * @return PostgreSQLQueryBuilder the query builder for the used pdo adapter + */ + protected function getQueryBuilder() + { + return new PostgreSQLQueryBuilder(); + } + } \ No newline at end of file diff --git a/Gishiki/Database/Adapters/Sqlite.php b/Gishiki/Database/Adapters/Sqlite.php index 21cbd39a..24ecabe8 100644 --- a/Gishiki/Database/Adapters/Sqlite.php +++ b/Gishiki/Database/Adapters/Sqlite.php @@ -1,48 +1,48 @@ - - */ -final class Sqlite extends PDODatabase -{ - /** - * {@inheritdoc} - */ - protected function getPDODriverName() - { - return 'sqlite'; - } - - /** - * Get the query builder for SQLite. - * - * @return SQLiteQueryBuilder the query builder for the used pdo adapter - */ - protected function getQueryBuilder() - { - return new SQLiteQueryBuilder(); - } - -} + + */ +final class Sqlite extends PDODatabase +{ + /** + * {@inheritdoc} + */ + protected function getPDODriverName() + { + return 'sqlite'; + } + + /** + * Get the query builder for SQLite. + * + * @return SQLiteQueryBuilder the query builder for the used pdo adapter + */ + protected function getQueryBuilder() + { + return new SQLiteQueryBuilder(); + } + +} diff --git a/Gishiki/Database/Adapters/Utils/QueryBuilder/MySQLQueryBuilder.php b/Gishiki/Database/Adapters/Utils/QueryBuilder/MySQLQueryBuilder.php index b7d48f58..de79e6dc 100644 --- a/Gishiki/Database/Adapters/Utils/QueryBuilder/MySQLQueryBuilder.php +++ b/Gishiki/Database/Adapters/Utils/QueryBuilder/MySQLQueryBuilder.php @@ -1,36 +1,36 @@ - - */ -final class MySQLQueryBuilder extends SQLQueryBuilder -{ - /** - * @return MySQLWrapper the MySQL specialized query builder - */ - protected function getQueryBuilder() - { - return new MySQLWrapper(); - } + + */ +final class MySQLQueryBuilder extends SQLQueryBuilder +{ + /** + * @return MySQLWrapper the MySQL specialized query builder + */ + protected function getQueryBuilder() + { + return new MySQLWrapper(); + } } \ No newline at end of file diff --git a/Gishiki/Database/Adapters/Utils/QueryBuilder/PostgreSQLQueryBuilder.php b/Gishiki/Database/Adapters/Utils/QueryBuilder/PostgreSQLQueryBuilder.php index 3ec9e9c0..0fe10e8c 100644 --- a/Gishiki/Database/Adapters/Utils/QueryBuilder/PostgreSQLQueryBuilder.php +++ b/Gishiki/Database/Adapters/Utils/QueryBuilder/PostgreSQLQueryBuilder.php @@ -1,53 +1,53 @@ - - */ -final class PostgreSQLQueryBuilder extends SQLQueryBuilder -{ - /** - * @return PostgreSQLWrapper the SQLite specialized query builder - */ - protected function getQueryBuilder() - { - return new PostgreSQLWrapper(); - } - - public function insertQuery($collection, array $adaptedData) - { - $returning = 'id'; - $returning = (in_array('rowid', array_keys($adaptedData))) ? 'rowid' : $returning; - $returning = (in_array('_rowid', array_keys($adaptedData))) ? '_rowid' : $returning; - $returning = (in_array('id', array_keys($adaptedData))) ? 'id' : $returning; - $returning = (in_array('ID', array_keys($adaptedData))) ? 'ID' : $returning; - $returning = (in_array('_id', array_keys($adaptedData))) ? '_id' : $returning; - $returning = (in_array($collection.'_id', array_keys($adaptedData))) ? $collection.'_id' : $returning; - - //build the sql query - $queryBuilder = $this->getQueryBuilder(); - $queryBuilder->insertInto($collection)->values($adaptedData)->returning($returning); - - return $queryBuilder; - } + + */ +final class PostgreSQLQueryBuilder extends SQLQueryBuilder +{ + /** + * @return PostgreSQLWrapper the SQLite specialized query builder + */ + protected function getQueryBuilder() + { + return new PostgreSQLWrapper(); + } + + public function insertQuery($collection, array $adaptedData) + { + $returning = 'id'; + $returning = (in_array('rowid', array_keys($adaptedData))) ? 'rowid' : $returning; + $returning = (in_array('_rowid', array_keys($adaptedData))) ? '_rowid' : $returning; + $returning = (in_array('id', array_keys($adaptedData))) ? 'id' : $returning; + $returning = (in_array('ID', array_keys($adaptedData))) ? 'ID' : $returning; + $returning = (in_array('_id', array_keys($adaptedData))) ? '_id' : $returning; + $returning = (in_array($collection.'_id', array_keys($adaptedData))) ? $collection.'_id' : $returning; + + //build the sql query + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->insertInto($collection)->values($adaptedData)->returning($returning); + + return $queryBuilder; + } } \ No newline at end of file diff --git a/Gishiki/Database/Adapters/Utils/QueryBuilder/SQLQueryBuilder.php b/Gishiki/Database/Adapters/Utils/QueryBuilder/SQLQueryBuilder.php index 4ce64c3e..6e9f2c40 100644 --- a/Gishiki/Database/Adapters/Utils/QueryBuilder/SQLQueryBuilder.php +++ b/Gishiki/Database/Adapters/Utils/QueryBuilder/SQLQueryBuilder.php @@ -1,103 +1,103 @@ - - */ -class SQLQueryBuilder -{ - /** - * Return the SQL query builder specialized for the current database - * - * @return GenericSQL the specialized SQLQueryBuilder - */ - protected function getQueryBuilder() - { - return new GenericSQL(); - } - - public function createTableQuery(Table $tb) - { - //build the sql query - $queryBuilder = $this->getQueryBuilder(); - $queryBuilder->createTable($tb->getName())->definedAs($tb->getColumns()); - - return $queryBuilder; - } - - public function insertQuery($collection, array $adaptedData) - { - //build the sql query - $queryBuilder = $this->getQueryBuilder(); - $queryBuilder->insertInto($collection)->values($adaptedData); - - return $queryBuilder; - } - - public function updateQuery($collection, array $adaptedData, SelectionCriteria $where) - { - //build the sql query - $queryBuilder = $this->getQueryBuilder(); - $queryBuilder->update($collection)->set($adaptedData)->where($where); - - return $queryBuilder; - } - - public function deleteQuery($collection, SelectionCriteria $where) { - //build the sql query - $queryBuilder = $this->getQueryBuilder(); - $queryBuilder->deleteFrom($collection)->where($where); - - return $queryBuilder; - } - - public function deleteAllQuery($collection) - { - //build the sql query - $queryBuilder = $this->getQueryBuilder(); - $queryBuilder->deleteFrom($collection); - - return $queryBuilder; - } - - public function readQuery($collection, SelectionCriteria $where, ResultModifier $mod) - { - //build the sql query - $queryBuilder = $this->getQueryBuilder(); - $queryBuilder->selectAllFrom($collection)->where($where)->limitOffsetOrderBy($mod); - - return $queryBuilder; - } - - public function selectiveReadQuery($collection, $fields, SelectionCriteria $where, ResultModifier $mod) - { - //build the sql query - $queryBuilder = $this->getQueryBuilder(); - $queryBuilder->selectFrom($collection, $fields)->where($where)->limitOffsetOrderBy($mod); - - return $queryBuilder; - } + + */ +class SQLQueryBuilder +{ + /** + * Return the SQL query builder specialized for the current database + * + * @return GenericSQL the specialized SQLQueryBuilder + */ + protected function getQueryBuilder() + { + return new GenericSQL(); + } + + public function createTableQuery(Table $tb) + { + //build the sql query + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->createTable($tb->getName())->definedAs($tb->getColumns()); + + return $queryBuilder; + } + + public function insertQuery($collection, array $adaptedData) + { + //build the sql query + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->insertInto($collection)->values($adaptedData); + + return $queryBuilder; + } + + public function updateQuery($collection, array $adaptedData, SelectionCriteria $where) + { + //build the sql query + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->update($collection)->set($adaptedData)->where($where); + + return $queryBuilder; + } + + public function deleteQuery($collection, SelectionCriteria $where) { + //build the sql query + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->deleteFrom($collection)->where($where); + + return $queryBuilder; + } + + public function deleteAllQuery($collection) + { + //build the sql query + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->deleteFrom($collection); + + return $queryBuilder; + } + + public function readQuery($collection, SelectionCriteria $where, ResultModifier $mod) + { + //build the sql query + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->selectAllFrom($collection)->where($where)->limitOffsetOrderBy($mod); + + return $queryBuilder; + } + + public function selectiveReadQuery($collection, $fields, SelectionCriteria $where, ResultModifier $mod) + { + //build the sql query + $queryBuilder = $this->getQueryBuilder(); + $queryBuilder->selectFrom($collection, $fields)->where($where)->limitOffsetOrderBy($mod); + + return $queryBuilder; + } } \ No newline at end of file diff --git a/Gishiki/Database/Adapters/Utils/QueryBuilder/SQLiteQueryBuilder.php b/Gishiki/Database/Adapters/Utils/QueryBuilder/SQLiteQueryBuilder.php index 6a823f57..f035828a 100644 --- a/Gishiki/Database/Adapters/Utils/QueryBuilder/SQLiteQueryBuilder.php +++ b/Gishiki/Database/Adapters/Utils/QueryBuilder/SQLiteQueryBuilder.php @@ -1,36 +1,36 @@ - - */ -final class SQLiteQueryBuilder extends SQLQueryBuilder -{ - /** - * @return SQLiteWrapper the SQLite specialized query builder - */ - protected function getQueryBuilder() - { - return new SQLiteWrapper(); - } -} + + */ +final class SQLiteQueryBuilder extends SQLQueryBuilder +{ + /** + * @return SQLiteWrapper the SQLite specialized query builder + */ + protected function getQueryBuilder() + { + return new SQLiteWrapper(); + } +} diff --git a/Gishiki/Database/Adapters/Utils/SQLGenerator/GenericSQL.php b/Gishiki/Database/Adapters/Utils/SQLGenerator/GenericSQL.php index 991fe5ae..75c93022 100644 --- a/Gishiki/Database/Adapters/Utils/SQLGenerator/GenericSQL.php +++ b/Gishiki/Database/Adapters/Utils/SQLGenerator/GenericSQL.php @@ -1,425 +1,425 @@ - - * $passwordHasher = new Hashing\Hasher(Hashing\Algorithm::BCRYPT); - * - * $queryBuilder = new SQLQueryBuilder(); - * $queryBuilder->insertInto("users")->values([ - * "name" => "Mario", - * "surname" => "Rossi", - * "password" => $passwordHasher->hash("Mario's password"); // good bye rainbow tables! - * "sex" => 0, - * "height" => 1.70 - * ]); - * - * //INSERT INTO "users" (name, surname, password, sex, height) VALUES (?, ?, ?, ?, ?) - * $sql = $queryBuilder->exportQuery(); - * - * // array( "Mario", "Rossi", ".....", 0, 1.70 ) - * $params = $queryBuilder->exportParams(); - * - * - * @author Benato Denis - */ -class GenericSQL -{ - /** - * @var string the SQL query that contains placeholders - */ - protected $sql; - - /** - * @var array a list of values to be escaped and inserted in place of sql placeholders - */ - protected $params; - - /** - * Beautify an SQL query. - * - * @param string $query your ugly SQL query - * @return string your beautiful SQL query - */ - public static function beautify($query) - { - $count = 1; - while ($count > 0) { - $query = str_replace(' ', ' ', $query, $count); - } - - $count = 1; - while ($count > 0) { - $query = str_replace('( ', '(', $query, $count); - } - - $count = 1; - while ($count > 0) { - $query = str_replace(' )', ')', $query, $count); - } - - $count = 1; - while ($count > 0) { - $query = str_replace('?, ?', '?,?', $query, $count); - } - - $query = str_replace(' ,', ',', $query, $count); - - return trim($query); - } - - /** - * Append to the current SQL the given text. - * - * Placeholders are question marks: ? and the value must be registered using - * the appendToParams function. - * - * @param string $sql the SQL with '?' placeholders - */ - protected function appendToQuery($sql) - { - $this->sql .= $sql; - } - - /** - * This is a collection of raw values that the PDO will replace to ? - * on the SQL query. - * - * @param mixed $newParams an array of values or the value to be replaced - */ - protected function appendToParams($newParams) - { - //this is used to recreate an array that doesn't conflicts with the $this->params when merging them - if (gettype($newParams) == 'array') { - $temp = []; - - foreach ($newParams as $currentKey => $currentValue) { - $temp[$currentKey + count($this->params)] = $currentValue; - } - - $newParams = $temp; - } - //the result will be something like: - // [0] => x1, [1] => x2 - // [5] => x1, [6] => x2 - - (is_array($newParams)) ? $this->params = array_merge($this->params, $newParams) : array_push($this->params, $newParams); - } - - /** - * Initialize an empty SQL query. - */ - public function __construct() - { - $this->sql = ''; - $this->params = []; - } - - /** - * Add UPDATE %tablename% to the SQL query. - * - * @param string $table the name of the table to be updated - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &update($table) - { - $this->appendToQuery('UPDATE '.$table.' '); - - //chain functions calls - return $this; - } - - /** - * Add SET col1 = ?, col2 = ?, col3 = ? to the SQL query. - * - * @param array $values an associative array of columns => value to be changed - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &set(array $values) - { - $this->appendToQuery('SET '); - - //create the sql placeholder resolver - $first = true; - foreach ($values as $columnName => $columnValue) { - $this->appendToParams($columnValue); - if (!$first) { - $this->appendToQuery(', '); - } - $this->appendToQuery($columnName.' = ?'); - - $first = false; - } - - $this->appendToQuery(' '); - - //chain functions calls - return $this; - } - - /** - * Add CREATE TABLE IF NOT EXISTS %tablename% to the SQL query. - * - * @param string $tableName the name of the table - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\SQLiteWrapper the updated sql builder - */ - public function &createTable($tableName) - { - $this->appendToQuery('CREATE TABLE IF NOT EXISTS '.$tableName.' '); - - //chain functions calls - return $this; - } - - /** - * Add DROP TABLE IF EXISTS %tablename% to the SQL query. - * - * @param string $tableName the name of the table - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\SQLiteWrapper the updated sql builder - */ - public function &dropTable($tableName) - { - $this->appendToQuery('DROP TABLE IF EXISTS '.$tableName.' '); - - //chain functions calls - return $this; - } - - /** - * Add WHERE col1 = ? OR col2 <= ? ....... to the SQL query. - * - * @param SelectionCriteria $where the selection criteria - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &where(SelectionCriteria $where) - { - //execute the private function 'export' - $exportMethod = new \ReflectionMethod($where, 'export'); - $exportMethod->setAccessible(true); - $resultExported = $exportMethod->invoke($where); - - if (count($resultExported['historic']) > 0) { - $this->appendToQuery('WHERE '); - - $first = true; - foreach ($resultExported['historic'] as $current) { - $conjunction = ''; - - $arrayIndex = $current & (~SelectionCriteria::AND_Historic_Marker); - $arrayConjunction = ''; - - //default is an OR - $conjunction = (!$first) ? ' OR ' : ' '; - $arrayConjunction = 'or'; - - //change to AND where necessary - if (($current & (SelectionCriteria::AND_Historic_Marker)) != 0) { - $conjunction = (!$first) ? ' AND ' : ' '; - $arrayConjunction = 'and'; - } - - $fieldName = $resultExported['criteria'][$arrayConjunction][$arrayIndex][0]; - $fieldRelation = $resultExported['criteria'][$arrayConjunction][$arrayIndex][1]; - $fieldValue = $resultExported['criteria'][$arrayConjunction][$arrayIndex][2]; - - //assemble the query - $qmarks = ''; - $parentOpen = ''; - $parentClose = ''; - if (is_array($fieldValue)) { - $qmarks = str_repeat(' ?,', count($fieldValue) - 1); - $parentOpen = '('; - $parentClose = ')'; - } - $this->appendToQuery($conjunction.$fieldName.' '.$fieldRelation.' '.$parentOpen.$qmarks.' ?'.$parentClose.' '); - $this->appendToParams($fieldValue); - - $first = false; - } - } - - //chain functions calls - return $this; - } - - /** - * Add INSERT INTO %tablename% to the SQL query. - * - * @param string $table the name of the table to be affected - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &insertInto($table) - { - $this->appendToQuery('INSERT INTO '.$table.' '); - - //chain functions calls - return $this; - } - - /** - * Add (col1, col2, col3) VALUES (?, ?, ?, ?) to the SQL query. - * - * @param array $values an associative array of columnName => rowValue - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &values(array $values) - { - $this->appendToQuery('('.implode(', ', array_keys($values)).') VALUES ('); - - //create the sql placeholder resolver - $first = true; - foreach ($values as $columnValue) { - $this->appendToParams($columnValue); - if (!$first) { - $this->appendToQuery(','); - } - $this->appendToQuery('?'); - $first = false; - } - - $this->appendToQuery(')'); - - //chain functions calls - return $this; - } - - /** - * Add LIMIT ? OFFSET ? ORDER BY ..... to the SQL query wheter they are needed. - * - * @param ResultModifier $mod the result modifier - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &limitOffsetOrderBy(ResultModifier $mod) - { - //execute the private function 'export' - $exportMethod = new \ReflectionMethod($mod, 'export'); - $exportMethod->setAccessible(true); - $resultExported = $exportMethod->invoke($mod); - - //append limit if needed - if ($resultExported['limit'] > 0) { - $this->appendToQuery('LIMIT '.$resultExported['limit'].' '); - } - - //append offset if needed - if ($resultExported['skip'] > 0) { - $this->appendToQuery('OFFSET '.$resultExported['skip'].' '); - } - - //append order if needed - if (count($resultExported['order']) > 0) { - $this->appendToQuery('ORDER BY '); - $first = true; - foreach ($resultExported['order'] as $column => $order) { - if (!$first) { - $this->appendToQuery(', '); - } - - $orderStr = ($order == FieldOrdering::ASC) ? 'ASC' : 'DESC'; - $this->appendToQuery($column.' '.$orderStr); - - $first = false; - } - } - - //chain functions calls - return $this; - } - - /** - * Add SELECT * FROM %tablename% to the SQL query. - * - * @param string $table the name of the table to be affected - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &selectAllFrom($table) - { - $this->appendToQuery('SELECT * FROM '.$table.' '); - - //chain functions calls - return $this; - } - - /** - * Add SELECT col1, col2, col3 FROM %tablename% to the SQL query. - * - * @param string $table the name of the table to be affected - * @param array $fields the list containing names of columns to be selected - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &selectFrom($table, array $fields) - { - $this->appendToQuery('SELECT '.implode(', ', $fields).' FROM '.$table.' '); - - //chain functions calls - return $this; - } - - /** - * Add DELETE FROM %tablename% to the SQL query. - * - * @param string $table the name of the table to be affected - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder - */ - public function &deleteFrom($table) - { - $this->appendToQuery('DELETE FROM '.$table.' '); - - //chain functions calls - return $this; - } - - /** - * Export the SQL query string with ? in place of actual parameters. - * - * @return string the SQL query without values - */ - public function exportQuery() - { - return self::beautify($this->sql); - } - - /** - * Export the list of parameters that will replace ? in the SQL query. - * - * @return array the list of params - */ - public function exportParams() - { - return $this->params; - } -} + + * $passwordHasher = new Hashing\Hasher(Hashing\Algorithm::BCRYPT); + * + * $queryBuilder = new SQLQueryBuilder(); + * $queryBuilder->insertInto("users")->values([ + * "name" => "Mario", + * "surname" => "Rossi", + * "password" => $passwordHasher->hash("Mario's password"); // good bye rainbow tables! + * "sex" => 0, + * "height" => 1.70 + * ]); + * + * //INSERT INTO "users" (name, surname, password, sex, height) VALUES (?, ?, ?, ?, ?) + * $sql = $queryBuilder->exportQuery(); + * + * // array( "Mario", "Rossi", ".....", 0, 1.70 ) + * $params = $queryBuilder->exportParams(); + * + * + * @author Benato Denis + */ +class GenericSQL +{ + /** + * @var string the SQL query that contains placeholders + */ + protected $sql; + + /** + * @var array a list of values to be escaped and inserted in place of sql placeholders + */ + protected $params; + + /** + * Beautify an SQL query. + * + * @param string $query your ugly SQL query + * @return string your beautiful SQL query + */ + public static function beautify($query) + { + $count = 1; + while ($count > 0) { + $query = str_replace(' ', ' ', $query, $count); + } + + $count = 1; + while ($count > 0) { + $query = str_replace('( ', '(', $query, $count); + } + + $count = 1; + while ($count > 0) { + $query = str_replace(' )', ')', $query, $count); + } + + $count = 1; + while ($count > 0) { + $query = str_replace('?, ?', '?,?', $query, $count); + } + + $query = str_replace(' ,', ',', $query, $count); + + return trim($query); + } + + /** + * Append to the current SQL the given text. + * + * Placeholders are question marks: ? and the value must be registered using + * the appendToParams function. + * + * @param string $sql the SQL with '?' placeholders + */ + protected function appendToQuery($sql) + { + $this->sql .= $sql; + } + + /** + * This is a collection of raw values that the PDO will replace to ? + * on the SQL query. + * + * @param mixed $newParams an array of values or the value to be replaced + */ + protected function appendToParams($newParams) + { + //this is used to recreate an array that doesn't conflicts with the $this->params when merging them + if (gettype($newParams) == 'array') { + $temp = []; + + foreach ($newParams as $currentKey => $currentValue) { + $temp[$currentKey + count($this->params)] = $currentValue; + } + + $newParams = $temp; + } + //the result will be something like: + // [0] => x1, [1] => x2 + // [5] => x1, [6] => x2 + + (is_array($newParams)) ? $this->params = array_merge($this->params, $newParams) : array_push($this->params, $newParams); + } + + /** + * Initialize an empty SQL query. + */ + public function __construct() + { + $this->sql = ''; + $this->params = []; + } + + /** + * Add UPDATE %tablename% to the SQL query. + * + * @param string $table the name of the table to be updated + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &update($table) + { + $this->appendToQuery('UPDATE '.$table.' '); + + //chain functions calls + return $this; + } + + /** + * Add SET col1 = ?, col2 = ?, col3 = ? to the SQL query. + * + * @param array $values an associative array of columns => value to be changed + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &set(array $values) + { + $this->appendToQuery('SET '); + + //create the sql placeholder resolver + $first = true; + foreach ($values as $columnName => $columnValue) { + $this->appendToParams($columnValue); + if (!$first) { + $this->appendToQuery(', '); + } + $this->appendToQuery($columnName.' = ?'); + + $first = false; + } + + $this->appendToQuery(' '); + + //chain functions calls + return $this; + } + + /** + * Add CREATE TABLE IF NOT EXISTS %tablename% to the SQL query. + * + * @param string $tableName the name of the table + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\SQLiteWrapper the updated sql builder + */ + public function &createTable($tableName) + { + $this->appendToQuery('CREATE TABLE IF NOT EXISTS '.$tableName.' '); + + //chain functions calls + return $this; + } + + /** + * Add DROP TABLE IF EXISTS %tablename% to the SQL query. + * + * @param string $tableName the name of the table + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\SQLiteWrapper the updated sql builder + */ + public function &dropTable($tableName) + { + $this->appendToQuery('DROP TABLE IF EXISTS '.$tableName.' '); + + //chain functions calls + return $this; + } + + /** + * Add WHERE col1 = ? OR col2 <= ? ....... to the SQL query. + * + * @param SelectionCriteria $where the selection criteria + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &where(SelectionCriteria $where) + { + //execute the private function 'export' + $exportMethod = new \ReflectionMethod($where, 'export'); + $exportMethod->setAccessible(true); + $resultExported = $exportMethod->invoke($where); + + if (count($resultExported['historic']) > 0) { + $this->appendToQuery('WHERE '); + + $first = true; + foreach ($resultExported['historic'] as $current) { + $conjunction = ''; + + $arrayIndex = $current & (~SelectionCriteria::AND_Historic_Marker); + $arrayConjunction = ''; + + //default is an OR + $conjunction = (!$first) ? ' OR ' : ' '; + $arrayConjunction = 'or'; + + //change to AND where necessary + if (($current & (SelectionCriteria::AND_Historic_Marker)) != 0) { + $conjunction = (!$first) ? ' AND ' : ' '; + $arrayConjunction = 'and'; + } + + $fieldName = $resultExported['criteria'][$arrayConjunction][$arrayIndex][0]; + $fieldRelation = $resultExported['criteria'][$arrayConjunction][$arrayIndex][1]; + $fieldValue = $resultExported['criteria'][$arrayConjunction][$arrayIndex][2]; + + //assemble the query + $qmarks = ''; + $parentOpen = ''; + $parentClose = ''; + if (is_array($fieldValue)) { + $qmarks = str_repeat(' ?,', count($fieldValue) - 1); + $parentOpen = '('; + $parentClose = ')'; + } + $this->appendToQuery($conjunction.$fieldName.' '.$fieldRelation.' '.$parentOpen.$qmarks.' ?'.$parentClose.' '); + $this->appendToParams($fieldValue); + + $first = false; + } + } + + //chain functions calls + return $this; + } + + /** + * Add INSERT INTO %tablename% to the SQL query. + * + * @param string $table the name of the table to be affected + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &insertInto($table) + { + $this->appendToQuery('INSERT INTO '.$table.' '); + + //chain functions calls + return $this; + } + + /** + * Add (col1, col2, col3) VALUES (?, ?, ?, ?) to the SQL query. + * + * @param array $values an associative array of columnName => rowValue + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &values(array $values) + { + $this->appendToQuery('('.implode(', ', array_keys($values)).') VALUES ('); + + //create the sql placeholder resolver + $first = true; + foreach ($values as $columnValue) { + $this->appendToParams($columnValue); + if (!$first) { + $this->appendToQuery(','); + } + $this->appendToQuery('?'); + $first = false; + } + + $this->appendToQuery(')'); + + //chain functions calls + return $this; + } + + /** + * Add LIMIT ? OFFSET ? ORDER BY ..... to the SQL query wheter they are needed. + * + * @param ResultModifier $mod the result modifier + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &limitOffsetOrderBy(ResultModifier $mod) + { + //execute the private function 'export' + $exportMethod = new \ReflectionMethod($mod, 'export'); + $exportMethod->setAccessible(true); + $resultExported = $exportMethod->invoke($mod); + + //append limit if needed + if ($resultExported['limit'] > 0) { + $this->appendToQuery('LIMIT '.$resultExported['limit'].' '); + } + + //append offset if needed + if ($resultExported['skip'] > 0) { + $this->appendToQuery('OFFSET '.$resultExported['skip'].' '); + } + + //append order if needed + if (count($resultExported['order']) > 0) { + $this->appendToQuery('ORDER BY '); + $first = true; + foreach ($resultExported['order'] as $column => $order) { + if (!$first) { + $this->appendToQuery(', '); + } + + $orderStr = ($order == FieldOrdering::ASC) ? 'ASC' : 'DESC'; + $this->appendToQuery($column.' '.$orderStr); + + $first = false; + } + } + + //chain functions calls + return $this; + } + + /** + * Add SELECT * FROM %tablename% to the SQL query. + * + * @param string $table the name of the table to be affected + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &selectAllFrom($table) + { + $this->appendToQuery('SELECT * FROM '.$table.' '); + + //chain functions calls + return $this; + } + + /** + * Add SELECT col1, col2, col3 FROM %tablename% to the SQL query. + * + * @param string $table the name of the table to be affected + * @param array $fields the list containing names of columns to be selected + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &selectFrom($table, array $fields) + { + $this->appendToQuery('SELECT '.implode(', ', $fields).' FROM '.$table.' '); + + //chain functions calls + return $this; + } + + /** + * Add DELETE FROM %tablename% to the SQL query. + * + * @param string $table the name of the table to be affected + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\GenericSQL the updated sql builder + */ + public function &deleteFrom($table) + { + $this->appendToQuery('DELETE FROM '.$table.' '); + + //chain functions calls + return $this; + } + + /** + * Export the SQL query string with ? in place of actual parameters. + * + * @return string the SQL query without values + */ + public function exportQuery() + { + return self::beautify($this->sql); + } + + /** + * Export the list of parameters that will replace ? in the SQL query. + * + * @return array the list of params + */ + public function exportParams() + { + return $this->params; + } +} diff --git a/Gishiki/Database/Adapters/Utils/SQLGenerator/MySQLWrapper.php b/Gishiki/Database/Adapters/Utils/SQLGenerator/MySQLWrapper.php index 6db42d12..999ea4bb 100644 --- a/Gishiki/Database/Adapters/Utils/SQLGenerator/MySQLWrapper.php +++ b/Gishiki/Database/Adapters/Utils/SQLGenerator/MySQLWrapper.php @@ -1,118 +1,118 @@ - - */ -final class MySQLWrapper extends GenericSQL -{ - /** - * Add (id INTEGER PRIMARY KEY NUT NULL, name TEXT NOT NULL, ... ) to the SQL query. - * - * @param array $columns a collection of Gishiki\Database\Schema\Column - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\MySQLWrapper the updated sql builder - */ - public function &definedAs(array $columns) - { - //mysql wants PRIMARY KEY(...) after column definitions - $primaryKeyName = ''; - - $this->appendToQuery('('); - - $first = true; - foreach ($columns as $column) { - if (!$first) { - $this->appendToQuery(', '); - } - - $this->appendToQuery($column->getName().' '); - - $typename = ''; - switch ($column->getType()) { - - case ColumnType::TEXT: - $typename = 'TEXT'; - break; - - case ColumnType::DATETIME: - $typename = 'INTEGER'; - break; - - case ColumnType::SMALLINT: - $typename = 'SMALLINT'; - break; - - case ColumnType::INTEGER: - $typename = 'INTEGER'; - break; - - case ColumnType::BIGINT; - $typename = 'BIGINT'; - break; - - case ColumnType::FLOAT: - $typename = 'FLOAT'; - break; - - case ColumnType::DOUBLE: - case ColumnType::MONEY: - case ColumnType::NUMERIC: - $typename = 'DOUBLE'; - break; - } - - $this->appendToQuery($typename.' '); - - if ($column->getPrimaryKey()) { - $primaryKeyName = $column->getName(); - } - - if ($column->getAutoIncrement()) { - $this->appendToQuery('AUTO_INCREMENT '); - } - - if ($column->getNotNull()) { - $this->appendToQuery('NOT NULL'); - } - - if (($relation = $column->getRelation()) != null) { - $this->appendToQuery(', FOREIGN KEY ('.$column->getName().') REFERENCES '.$relation->getForeignTable()->getName().'('.$relation->getForeignKey()->getName().')'); - } - - $first = false; - } - - if (strlen($primaryKeyName) > 0) { - $this->appendToQuery(', PRIMARY KEY ('.$primaryKeyName.')'); - } - - $this->appendToQuery(')'); - - //chain functions calls - return $this; - } + + */ +final class MySQLWrapper extends GenericSQL +{ + /** + * Add (id INTEGER PRIMARY KEY NUT NULL, name TEXT NOT NULL, ... ) to the SQL query. + * + * @param array $columns a collection of Gishiki\Database\Schema\Column + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\MySQLWrapper the updated sql builder + */ + public function &definedAs(array $columns) + { + //mysql wants PRIMARY KEY(...) after column definitions + $primaryKeyName = ''; + + $this->appendToQuery('('); + + $first = true; + foreach ($columns as $column) { + if (!$first) { + $this->appendToQuery(', '); + } + + $this->appendToQuery($column->getName().' '); + + $typename = ''; + switch ($column->getType()) { + + case ColumnType::TEXT: + $typename = 'TEXT'; + break; + + case ColumnType::DATETIME: + $typename = 'INTEGER'; + break; + + case ColumnType::SMALLINT: + $typename = 'SMALLINT'; + break; + + case ColumnType::INTEGER: + $typename = 'INTEGER'; + break; + + case ColumnType::BIGINT; + $typename = 'BIGINT'; + break; + + case ColumnType::FLOAT: + $typename = 'FLOAT'; + break; + + case ColumnType::DOUBLE: + case ColumnType::MONEY: + case ColumnType::NUMERIC: + $typename = 'DOUBLE'; + break; + } + + $this->appendToQuery($typename.' '); + + if ($column->getPrimaryKey()) { + $primaryKeyName = $column->getName(); + } + + if ($column->getAutoIncrement()) { + $this->appendToQuery('AUTO_INCREMENT '); + } + + if ($column->getNotNull()) { + $this->appendToQuery('NOT NULL'); + } + + if (($relation = $column->getRelation()) != null) { + $this->appendToQuery(', FOREIGN KEY ('.$column->getName().') REFERENCES '.$relation->getForeignTable()->getName().'('.$relation->getForeignKey()->getName().')'); + } + + $first = false; + } + + if (strlen($primaryKeyName) > 0) { + $this->appendToQuery(', PRIMARY KEY ('.$primaryKeyName.')'); + } + + $this->appendToQuery(')'); + + //chain functions calls + return $this; + } } \ No newline at end of file diff --git a/Gishiki/Database/Adapters/Utils/SQLGenerator/PostgreSQLWrapper.php b/Gishiki/Database/Adapters/Utils/SQLGenerator/PostgreSQLWrapper.php index eaef3a58..729f546e 100644 --- a/Gishiki/Database/Adapters/Utils/SQLGenerator/PostgreSQLWrapper.php +++ b/Gishiki/Database/Adapters/Utils/SQLGenerator/PostgreSQLWrapper.php @@ -1,124 +1,124 @@ - - */ -final class PostgreSQLWrapper extends GenericSQL -{ - /** - * Add RETURNING %id% to the SQL query. - * - * @param string $idFieldName the name of the table primary key - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\PostgreSQLWrapper the updated sql builder - */ - public function &returning($idFieldName) - { - $this->appendToQuery(' RETURNING "'.$idFieldName.'" '); - - //chain functions calls - return $this; - } - - /** - * Add (id sequence PRIMARY KEY, name text NOT NULL, ... ) to the SQL query. - * - * @param array $columns a collection of Gishiki\Database\Schema\Column - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\PostgreSQLWrapper the updated sql builder - */ - public function &definedAs(array $columns) - { - $this->appendToQuery('('); - - $first = true; - foreach ($columns as $column) { - if (!$first) { - $this->appendToQuery(', '); - } - - $this->appendToQuery($column->getName().' '); - - $typename = ''; - switch ($column->getType()) { - - case ColumnType::TEXT: - $typename = 'text'; - break; - - case ColumnType::SMALLINT: - $typename = ($column->getAutoIncrement()) ? 'serial' : 'smallint'; - break; - - case ColumnType::DATETIME: - case ColumnType::INTEGER: - $typename = ($column->getAutoIncrement()) ? 'serial' : 'integer'; - break; - - case ColumnType::BIGINT: - $typename = ($column->getAutoIncrement()) ? 'serial' : 'bigint'; - break; - - case ColumnType::FLOAT: - $typename = 'float'; - break; - - case ColumnType::DOUBLE: - $typename = 'double'; - break; - - case ColumnType::NUMERIC: - $typename = 'numeric'; - break; - - case ColumnType::MONEY: - $typename = 'money'; - break; - } - - $this->appendToQuery($typename.' '); - - if ($column->getPrimaryKey()) { - $this->appendToQuery('PRIMARY KEY '); - } - - if (($column->getNotNull()) && ($typename != 'serial')){ - $this->appendToQuery('NOT NULL'); - } - - if (($relation = $column->getRelation()) != null) { - $this->appendToQuery(' REFERENCES '.$relation->getForeignTable()->getName().'('.$relation->getForeignKey()->getName().')'); - } - - $first = false; - } - - $this->appendToQuery(')'); - - //chain functions calls - return $this; - } + + */ +final class PostgreSQLWrapper extends GenericSQL +{ + /** + * Add RETURNING %id% to the SQL query. + * + * @param string $idFieldName the name of the table primary key + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\PostgreSQLWrapper the updated sql builder + */ + public function &returning($idFieldName) + { + $this->appendToQuery(' RETURNING "'.$idFieldName.'" '); + + //chain functions calls + return $this; + } + + /** + * Add (id sequence PRIMARY KEY, name text NOT NULL, ... ) to the SQL query. + * + * @param array $columns a collection of Gishiki\Database\Schema\Column + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\PostgreSQLWrapper the updated sql builder + */ + public function &definedAs(array $columns) + { + $this->appendToQuery('('); + + $first = true; + foreach ($columns as $column) { + if (!$first) { + $this->appendToQuery(', '); + } + + $this->appendToQuery($column->getName().' '); + + $typename = ''; + switch ($column->getType()) { + + case ColumnType::TEXT: + $typename = 'text'; + break; + + case ColumnType::SMALLINT: + $typename = ($column->getAutoIncrement()) ? 'serial' : 'smallint'; + break; + + case ColumnType::DATETIME: + case ColumnType::INTEGER: + $typename = ($column->getAutoIncrement()) ? 'serial' : 'integer'; + break; + + case ColumnType::BIGINT: + $typename = ($column->getAutoIncrement()) ? 'serial' : 'bigint'; + break; + + case ColumnType::FLOAT: + $typename = 'float'; + break; + + case ColumnType::DOUBLE: + $typename = 'double'; + break; + + case ColumnType::NUMERIC: + $typename = 'numeric'; + break; + + case ColumnType::MONEY: + $typename = 'money'; + break; + } + + $this->appendToQuery($typename.' '); + + if ($column->getPrimaryKey()) { + $this->appendToQuery('PRIMARY KEY '); + } + + if (($column->getNotNull()) && ($typename != 'serial')){ + $this->appendToQuery('NOT NULL'); + } + + if (($relation = $column->getRelation()) != null) { + $this->appendToQuery(' REFERENCES '.$relation->getForeignTable()->getName().'('.$relation->getForeignKey()->getName().')'); + } + + $first = false; + } + + $this->appendToQuery(')'); + + //chain functions calls + return $this; + } } \ No newline at end of file diff --git a/Gishiki/Database/Adapters/Utils/SQLGenerator/SQLiteWrapper.php b/Gishiki/Database/Adapters/Utils/SQLGenerator/SQLiteWrapper.php index 24d9d182..df457d89 100644 --- a/Gishiki/Database/Adapters/Utils/SQLGenerator/SQLiteWrapper.php +++ b/Gishiki/Database/Adapters/Utils/SQLGenerator/SQLiteWrapper.php @@ -1,101 +1,101 @@ - - */ -final class SQLiteWrapper extends GenericSQL -{ - - /** - * Add (id INTEGER PRIMARY KEY NUT NULL, name TEXT NOT NULL, ... ) to the SQL query. - * - * @param array $columns a collection of Gishiki\Database\Schema\Column - * - * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\SQLiteWrapper the updated sql builder - */ - public function &definedAs(array $columns) - { - $this->appendToQuery('('); - - $first = true; - foreach ($columns as $column) { - if (!$first) { - $this->appendToQuery(', '); - } - - $this->appendToQuery($column->getName().' '); - - $typename = ''; - switch ($column->getType()) { - - case ColumnType::TEXT: - $typename = 'TEXT'; - break; - - case ColumnType::DATETIME: - case ColumnType::SMALLINT: - case ColumnType::INTEGER: - case ColumnType::BIGINT; - $typename = 'INTEGER'; - break; - - case ColumnType::FLOAT: - case ColumnType::DOUBLE: - case ColumnType::NUMERIC: - case ColumnType::MONEY: - $typename = 'REAL'; - break; - } - - $this->appendToQuery($typename.' '); - - if ($column->getPrimaryKey()) { - $this->appendToQuery('PRIMARY KEY '); - } - - if ($column->getAutoIncrement()) { - $this->appendToQuery('AUTOINCREMENT '); - } - - if ($column->getNotNull()) { - $this->appendToQuery('NOT NULL'); - } - - if (($relation = $column->getRelation()) != null) { - $this->appendToQuery(', FOREIGN KEY ('.$column->getName().') REFERENCES '.$relation->getForeignTable()->getName().'('.$relation->getForeignKey()->getName().')'); - } - - $first = false; - } - - $this->appendToQuery(')'); - - //chain functions calls - return $this; - } - -} + + */ +final class SQLiteWrapper extends GenericSQL +{ + + /** + * Add (id INTEGER PRIMARY KEY NUT NULL, name TEXT NOT NULL, ... ) to the SQL query. + * + * @param array $columns a collection of Gishiki\Database\Schema\Column + * + * @return \Gishiki\Database\Adapters\Utils\SQLGenerator\SQLiteWrapper the updated sql builder + */ + public function &definedAs(array $columns) + { + $this->appendToQuery('('); + + $first = true; + foreach ($columns as $column) { + if (!$first) { + $this->appendToQuery(', '); + } + + $this->appendToQuery($column->getName().' '); + + $typename = ''; + switch ($column->getType()) { + + case ColumnType::TEXT: + $typename = 'TEXT'; + break; + + case ColumnType::DATETIME: + case ColumnType::SMALLINT: + case ColumnType::INTEGER: + case ColumnType::BIGINT; + $typename = 'INTEGER'; + break; + + case ColumnType::FLOAT: + case ColumnType::DOUBLE: + case ColumnType::NUMERIC: + case ColumnType::MONEY: + $typename = 'REAL'; + break; + } + + $this->appendToQuery($typename.' '); + + if ($column->getPrimaryKey()) { + $this->appendToQuery('PRIMARY KEY '); + } + + if ($column->getAutoIncrement()) { + $this->appendToQuery('AUTOINCREMENT '); + } + + if ($column->getNotNull()) { + $this->appendToQuery('NOT NULL'); + } + + if (($relation = $column->getRelation()) != null) { + $this->appendToQuery(', FOREIGN KEY ('.$column->getName().') REFERENCES '.$relation->getForeignTable()->getName().'('.$relation->getForeignKey()->getName().')'); + } + + $first = false; + } + + $this->appendToQuery(')'); + + //chain functions calls + return $this; + } + +} diff --git a/Gishiki/Database/DatabaseException.php b/Gishiki/Database/DatabaseException.php index 8290c196..1b4e9daa 100644 --- a/Gishiki/Database/DatabaseException.php +++ b/Gishiki/Database/DatabaseException.php @@ -1,39 +1,39 @@ - - */ -final class DatabaseException extends Exception -{ - /** - * Create the database exception. - * - * @param string $message the error message - * @param int $errorCode the database error code - */ - public function __construct($message, $errorCode) - { - parent::__construct($message, $errorCode); - } -} + + */ +final class DatabaseException extends Exception +{ + /** + * Create the database exception. + * + * @param string $message the error message + * @param int $errorCode the database error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } +} diff --git a/Gishiki/Database/DatabaseInterface.php b/Gishiki/Database/DatabaseInterface.php index 19b606c3..b133c34d 100644 --- a/Gishiki/Database/DatabaseInterface.php +++ b/Gishiki/Database/DatabaseInterface.php @@ -1,129 +1,129 @@ - - */ -interface DatabaseInterface -{ - /** - * Create a new database handler and logically connect the given database. - * - * @param string $details the connection string - * - * @throws DatabaseException the error occurred while connecting to the database - * @throws \InvalidArgumentException given details are invalid - */ - public function __construct($details); - - /** - * Logically connect the given database. - * - * @param string $details the connection string - * - * @throws DatabaseException the error occurred while connecting to the database - * @throws \InvalidArgumentException given details are invalid - */ - public function connect($details); - - /** - * Close the connection to the database - * - * @return void - */ - public function close(); - - /** - * Check if the database handler is connected with a real database. - * - * @return bool TRUE only if the database connection is alive - */ - public function connected(); - - /** - * Write data to the database on the given collection/table. - * - * @param string $collection the name of the collection that will hold the data - * @param array|CollectionInterface $data the collection of data to be written - * @throw \InvalidArgumentException the given collection name or data is not a collection of valid values - * - * @throws DatabaseException the error occurred while inserting data to the database - * - * @return mixed the unique ID of the inserted data - */ - public function create($collection, $data); - - /** - * Update values of documents/records matching the given criteria. - * - * @param string $collection the name of the collection that will hold the changed data - * @param array|CollectionInterface $data the new data of selected documents/records - * @param SelectionCriteria $where the criteria used to select documents/records to update - * @throw \InvalidArgumentException the given collection name or data is not a collection of valid values - * - * @throws DatabaseException the error occurred while updating data on the database - * - * @return int the number of affected documents/records - */ - public function update($collection, $data, SelectionCriteria $where); - - /** - * Remove documents/records matching the given criteria. - * - * @param string $collection the name of the collection that will be affected - * @param SelectionCriteria $where the criteria used to select documents/records to update - * @throw \InvalidArgumentException the given collection name is not a valid collection name - * - * @throws DatabaseException the error occurred while removing data from the database - * - * @return int the number of removed documents/records - */ - public function delete($collection, SelectionCriteria $where); - - /** - * Remove EVERY documents/records on the given collection/table. - * - * @param string $collection the name of the collection that will be affected - * @throw \InvalidArgumentException the given collection name is not a valid collection name - * - * @throws DatabaseException the error occurred while removing data from the database - * - * @return int the number of removed documents/records - */ - public function deleteAll($collection); - - /** - * Fetch documents/records matching the given criteria. - * - * @param string $collection the name of the collection that will be searched - * @param SelectionCriteria $where the criteria used to select documents/records to fetch - * @param ResultModifier $mod the modifier to be applied to the result set - * @throw \InvalidArgumentException the given collection name is not a valid collection name - * - * @throws DatabaseException the error occurred while fetching data from the database - * - * @return array the search result expressed as an array of associative arrays - */ - public function read($collection, SelectionCriteria $where, ResultModifier $mod); -} + + */ +interface DatabaseInterface +{ + /** + * Create a new database handler and logically connect the given database. + * + * @param string $details the connection string + * + * @throws DatabaseException the error occurred while connecting to the database + * @throws \InvalidArgumentException given details are invalid + */ + public function __construct($details); + + /** + * Logically connect the given database. + * + * @param string $details the connection string + * + * @throws DatabaseException the error occurred while connecting to the database + * @throws \InvalidArgumentException given details are invalid + */ + public function connect($details); + + /** + * Close the connection to the database + * + * @return void + */ + public function close(); + + /** + * Check if the database handler is connected with a real database. + * + * @return bool TRUE only if the database connection is alive + */ + public function connected(); + + /** + * Write data to the database on the given collection/table. + * + * @param string $collection the name of the collection that will hold the data + * @param array|CollectionInterface $data the collection of data to be written + * @throw \InvalidArgumentException the given collection name or data is not a collection of valid values + * + * @throws DatabaseException the error occurred while inserting data to the database + * + * @return mixed the unique ID of the inserted data + */ + public function create($collection, $data); + + /** + * Update values of documents/records matching the given criteria. + * + * @param string $collection the name of the collection that will hold the changed data + * @param array|CollectionInterface $data the new data of selected documents/records + * @param SelectionCriteria $where the criteria used to select documents/records to update + * @throw \InvalidArgumentException the given collection name or data is not a collection of valid values + * + * @throws DatabaseException the error occurred while updating data on the database + * + * @return int the number of affected documents/records + */ + public function update($collection, $data, SelectionCriteria $where); + + /** + * Remove documents/records matching the given criteria. + * + * @param string $collection the name of the collection that will be affected + * @param SelectionCriteria $where the criteria used to select documents/records to update + * @throw \InvalidArgumentException the given collection name is not a valid collection name + * + * @throws DatabaseException the error occurred while removing data from the database + * + * @return int the number of removed documents/records + */ + public function delete($collection, SelectionCriteria $where); + + /** + * Remove EVERY documents/records on the given collection/table. + * + * @param string $collection the name of the collection that will be affected + * @throw \InvalidArgumentException the given collection name is not a valid collection name + * + * @throws DatabaseException the error occurred while removing data from the database + * + * @return int the number of removed documents/records + */ + public function deleteAll($collection); + + /** + * Fetch documents/records matching the given criteria. + * + * @param string $collection the name of the collection that will be searched + * @param SelectionCriteria $where the criteria used to select documents/records to fetch + * @param ResultModifier $mod the modifier to be applied to the result set + * @throw \InvalidArgumentException the given collection name is not a valid collection name + * + * @throws DatabaseException the error occurred while fetching data from the database + * + * @return array the search result expressed as an array of associative arrays + */ + public function read($collection, SelectionCriteria $where, ResultModifier $mod); +} diff --git a/Gishiki/Database/DatabaseManager.php b/Gishiki/Database/DatabaseManager.php index 2ad80925..74476acd 100644 --- a/Gishiki/Database/DatabaseManager.php +++ b/Gishiki/Database/DatabaseManager.php @@ -1,101 +1,101 @@ - - */ -abstract class DatabaseManager -{ - /** - * @var array the list of database connections as an associative array - */ - private static $connections = []; - - //used to give a second name to an adapter - private static $adaptersMap = [ - 'Sqlite3' => 'Sqlite', - 'Postgres' => 'Pgsql', - 'Postgre' => 'Pgsql', - ]; - - /** - * Create a new database connection and store the newly generated connection. - * - * @param string $connectionName the name of the database connection - * @param string $connectionString the connection string - * @throws \InvalidArgumentException invalid name or connection string - * @throws DatabaseException a database adapter with the given name doesn't exists - * @return DatabaseInterface the connected database instance - */ - public static function connect($connectionName, $connectionString) - { - //check for malformed input - if ((!is_string($connectionName)) || (strlen($connectionName) <= 0) || (!is_string($connectionString)) || (strlen($connectionString) <= 0)) { - throw new \InvalidArgumentException('The connection name and the connection details must be given as two string'); - } - - //get the adapter name - $temp = explode('://', $connectionString); - $adapterTemp = ucfirst($temp[0]); - $adapter = (array_key_exists($adapterTemp, self::$adaptersMap)) ? - self::$adaptersMap[$adapterTemp] : $adapterTemp; - $connectionQuery = $temp[1]; - - try { - //reflect the adapter - $reflectedAdapter = new \ReflectionClass('Gishiki\\Database\\Adapters\\'.$adapter); - - //and use the adapter to estabilish the database connection and return the connection handler - self::$connections[sha1($connectionName)] = $reflectedAdapter->newInstance($connectionQuery); - - return self::$connections[sha1($connectionName)]; - } catch (\ReflectionException $ex) { - throw new DatabaseException('The given connection query requires an nonexistent adapter', 0); - } - } - - /** - * Retrieve the connection with the given name from the list of performed conenctions. - * If the name is not specified the default one is retrieved. - * - * @param string $connectionName the name of the selected connection - * - * @return DatabaseInterface the connected database instance - * - * @throws \InvalidArgumentException the collection name has not be given as a string - * @throws DatabaseException the given connection name is not registered as a valid collection - */ - public static function retrieve($connectionName = 'default') - { - //check for malformed input - if ((!is_string($connectionName)) || (strlen($connectionName) <= 0)) { - throw new \InvalidArgumentException('The name of the connection to be retrieved must be given as a string'); - } - - //check if the connection was estabilish - if (!array_key_exists(sha1($connectionName), self::$connections)) { - throw new DatabaseException("The given connection doesn't exists", 1); - } - - //return the estabilish connection - return self::$connections[sha1($connectionName)]; - } -} + + */ +abstract class DatabaseManager +{ + /** + * @var array the list of database connections as an associative array + */ + private static $connections = []; + + //used to give a second name to an adapter + private static $adaptersMap = [ + 'Sqlite3' => 'Sqlite', + 'Postgres' => 'Pgsql', + 'Postgre' => 'Pgsql', + ]; + + /** + * Create a new database connection and store the newly generated connection. + * + * @param string $connectionName the name of the database connection + * @param string $connectionString the connection string + * @throws \InvalidArgumentException invalid name or connection string + * @throws DatabaseException a database adapter with the given name doesn't exists + * @return DatabaseInterface the connected database instance + */ + public static function connect($connectionName, $connectionString) + { + //check for malformed input + if ((!is_string($connectionName)) || (strlen($connectionName) <= 0) || (!is_string($connectionString)) || (strlen($connectionString) <= 0)) { + throw new \InvalidArgumentException('The connection name and the connection details must be given as two string'); + } + + //get the adapter name + $temp = explode('://', $connectionString); + $adapterTemp = ucfirst($temp[0]); + $adapter = (array_key_exists($adapterTemp, self::$adaptersMap)) ? + self::$adaptersMap[$adapterTemp] : $adapterTemp; + $connectionQuery = $temp[1]; + + try { + //reflect the adapter + $reflectedAdapter = new \ReflectionClass('Gishiki\\Database\\Adapters\\'.$adapter); + + //and use the adapter to estabilish the database connection and return the connection handler + self::$connections[sha1($connectionName)] = $reflectedAdapter->newInstance($connectionQuery); + + return self::$connections[sha1($connectionName)]; + } catch (\ReflectionException $ex) { + throw new DatabaseException('The given connection query requires an nonexistent adapter', 0); + } + } + + /** + * Retrieve the connection with the given name from the list of performed conenctions. + * If the name is not specified the default one is retrieved. + * + * @param string $connectionName the name of the selected connection + * + * @return DatabaseInterface the connected database instance + * + * @throws \InvalidArgumentException the collection name has not be given as a string + * @throws DatabaseException the given connection name is not registered as a valid collection + */ + public static function retrieve($connectionName = 'default') + { + //check for malformed input + if ((!is_string($connectionName)) || (strlen($connectionName) <= 0)) { + throw new \InvalidArgumentException('The name of the connection to be retrieved must be given as a string'); + } + + //check if the connection was estabilish + if (!array_key_exists(sha1($connectionName), self::$connections)) { + throw new DatabaseException("The given connection doesn't exists", 1); + } + + //return the estabilish connection + return self::$connections[sha1($connectionName)]; + } +} diff --git a/Gishiki/Database/ORM/DatabaseStructure.php b/Gishiki/Database/ORM/DatabaseStructure.php index 6cd97d59..90593aac 100644 --- a/Gishiki/Database/ORM/DatabaseStructure.php +++ b/Gishiki/Database/ORM/DatabaseStructure.php @@ -1,159 +1,159 @@ - - */ -final class DatabaseStructure -{ - /** - * @var string The name of the corresponding connection - */ - protected $connectionName; - - /** - * @var StackCollection the collection of tables in creation reversed order - */ - protected $stackTables; - - /** - * Build the Database structure from a json text. - * - * @param string $description the json description of the database - * @throws StructureException the error in the description - */ - public function __construct($description) - { - $this->connectionName = new StackCollection(); - - //deserialize the json content - $deserializedDescription = SerializableCollection::deserialize($description); - - if (!$deserializedDescription->has('connection')) { - throw new StructureException('A database description must contains the connection field', 0); - } - - $this->connectionName = $deserializedDescription->get('connection'); - - if (!$deserializedDescription->has('tables')) { - throw new StructureException('A database description must contains a tables field', 1); - } - - foreach ($deserializedDescription->get('tables') as $tb) { - $table = new GenericCollection($tb); - - if (!$table->has('name')) { - throw new StructureException('Each table must have a name'); - } - - $currentTable = new Table($table->get('name')); - - foreach ($table->get('fields') as $fd) { - $field = new GenericCollection($fd); - - if (!$field->has('name')) { - throw new StructureException('Each column must have a name'); - } - - if (!$field->has('type')) { - throw new StructureException('Each column must have a type'); - } - - $typeIdentifier = ColumnType::UNKNOWN; - switch ($field->get('type')) { - case 'string': - case 'text': - $typeIdentifier = ColumnType::TEXT; - break; - - case 'smallint': - $typeIdentifier = ColumnType::SMALLINT; - break; - - case 'int': - case 'integer': - $typeIdentifier = ColumnType::INTEGER; - break; - - case 'bigint': - $typeIdentifier = ColumnType::BIGINT; - break; - - case 'money': - $typeIdentifier = ColumnType::MONEY; - break; - - case 'numeric': - $typeIdentifier = ColumnType::NUMERIC; - break; - - case 'float': - $typeIdentifier = ColumnType::FLOAT; - break; - - case 'double': - $typeIdentifier = ColumnType::DOUBLE; - break; - - case 'datetime': - $typeIdentifier = ColumnType::DATETIME; - break; - } - - $currentField = new Column($field->get('name'), $typeIdentifier); - $currentField->setPrimaryKey(($field->get('primary key') === true)); - $currentField->setNotNull(($field->get('not null') === true)); - $currentField->setAutoIncrement(($field->get('autoincrement') === true)); - - $currentTable->addColumn($currentField); - } - - //add the table to the collection - $this->stackTables->push($currentTable); - } - } - - /** - * Apply the structure to the database. - */ - public function apply() - { - $connection = DatabaseManager::retrieve($this->connectionName); - - if ($connection instanceof RelationalDatabaseInterface) { - $this->stackTables->reverse(); - - while (!$this->stackTables->empty()) { - $connection->createTable($this->stackTables->pop()); - } - } - } - + + */ +final class DatabaseStructure +{ + /** + * @var string The name of the corresponding connection + */ + protected $connectionName; + + /** + * @var StackCollection the collection of tables in creation reversed order + */ + protected $stackTables; + + /** + * Build the Database structure from a json text. + * + * @param string $description the json description of the database + * @throws StructureException the error in the description + */ + public function __construct($description) + { + $this->connectionName = new StackCollection(); + + //deserialize the json content + $deserializedDescription = SerializableCollection::deserialize($description); + + if (!$deserializedDescription->has('connection')) { + throw new StructureException('A database description must contains the connection field', 0); + } + + $this->connectionName = $deserializedDescription->get('connection'); + + if (!$deserializedDescription->has('tables')) { + throw new StructureException('A database description must contains a tables field', 1); + } + + foreach ($deserializedDescription->get('tables') as $tb) { + $table = new GenericCollection($tb); + + if (!$table->has('name')) { + throw new StructureException('Each table must have a name'); + } + + $currentTable = new Table($table->get('name')); + + foreach ($table->get('fields') as $fd) { + $field = new GenericCollection($fd); + + if (!$field->has('name')) { + throw new StructureException('Each column must have a name'); + } + + if (!$field->has('type')) { + throw new StructureException('Each column must have a type'); + } + + $typeIdentifier = ColumnType::UNKNOWN; + switch ($field->get('type')) { + case 'string': + case 'text': + $typeIdentifier = ColumnType::TEXT; + break; + + case 'smallint': + $typeIdentifier = ColumnType::SMALLINT; + break; + + case 'int': + case 'integer': + $typeIdentifier = ColumnType::INTEGER; + break; + + case 'bigint': + $typeIdentifier = ColumnType::BIGINT; + break; + + case 'money': + $typeIdentifier = ColumnType::MONEY; + break; + + case 'numeric': + $typeIdentifier = ColumnType::NUMERIC; + break; + + case 'float': + $typeIdentifier = ColumnType::FLOAT; + break; + + case 'double': + $typeIdentifier = ColumnType::DOUBLE; + break; + + case 'datetime': + $typeIdentifier = ColumnType::DATETIME; + break; + } + + $currentField = new Column($field->get('name'), $typeIdentifier); + $currentField->setPrimaryKey(($field->get('primary key') === true)); + $currentField->setNotNull(($field->get('not null') === true)); + $currentField->setAutoIncrement(($field->get('autoincrement') === true)); + + $currentTable->addColumn($currentField); + } + + //add the table to the collection + $this->stackTables->push($currentTable); + } + } + + /** + * Apply the structure to the database. + */ + public function apply() + { + $connection = DatabaseManager::retrieve($this->connectionName); + + if ($connection instanceof RelationalDatabaseInterface) { + $this->stackTables->reverse(); + + while (!$this->stackTables->empty()) { + $connection->createTable($this->stackTables->pop()); + } + } + } + } \ No newline at end of file diff --git a/Gishiki/Database/ORM/StructureException.php b/Gishiki/Database/ORM/StructureException.php index 52e93f2a..3fefb3ea 100644 --- a/Gishiki/Database/ORM/StructureException.php +++ b/Gishiki/Database/ORM/StructureException.php @@ -1,39 +1,39 @@ - - */ -class StructureException extends Exception -{ - /** - * Create the serialization-related exception. - * - * @param string $message the error message - * @param int $errorCode the error code - */ - public function __construct($message, $errorCode) - { - parent::__construct($message, $errorCode); - } + + */ +class StructureException extends Exception +{ + /** + * Create the serialization-related exception. + * + * @param string $message the error message + * @param int $errorCode the error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } } \ No newline at end of file diff --git a/Gishiki/Database/RelationalDatabaseInterface.php b/Gishiki/Database/RelationalDatabaseInterface.php index cd5e16da..960593d0 100644 --- a/Gishiki/Database/RelationalDatabaseInterface.php +++ b/Gishiki/Database/RelationalDatabaseInterface.php @@ -1,54 +1,54 @@ - - */ -interface RelationalDatabaseInterface extends DatabaseInterface -{ - /** - * Fetch documents/records matching the given criteria, but retrieve only the specified columns. - * - * @param string $collection the name of the collection that will be searched - * @param array $fields the list containing names of columns to be fetched - * @param SelectionCriteria $where the criteria used to select documents/records to fetch - * @param ResultModifier $mod the modifier to be applied to the result set - * @throw \InvalidArgumentException the given collection name is not a valid collection name - * - * @throws DatabaseException the error occurred while fetching data from the database - * - * @return array the search result expressed as an array of associative arrays - */ - public function readSelective($collection, $fields, SelectionCriteria $where, ResultModifier $mod); - - /** - * Create the given table schema. - * - * @param Schema\Table $tb the table to be created on the database - * - * @throws DatabaseException the error occurred while creating the table - */ - public function createTable(Table $tb); -} + + */ +interface RelationalDatabaseInterface extends DatabaseInterface +{ + /** + * Fetch documents/records matching the given criteria, but retrieve only the specified columns. + * + * @param string $collection the name of the collection that will be searched + * @param array $fields the list containing names of columns to be fetched + * @param SelectionCriteria $where the criteria used to select documents/records to fetch + * @param ResultModifier $mod the modifier to be applied to the result set + * @throw \InvalidArgumentException the given collection name is not a valid collection name + * + * @throws DatabaseException the error occurred while fetching data from the database + * + * @return array the search result expressed as an array of associative arrays + */ + public function readSelective($collection, $fields, SelectionCriteria $where, ResultModifier $mod); + + /** + * Create the given table schema. + * + * @param Schema\Table $tb the table to be created on the database + * + * @throws DatabaseException the error occurred while creating the table + */ + public function createTable(Table $tb); +} diff --git a/Gishiki/Database/Runtime/FieldOrdering.php b/Gishiki/Database/Runtime/FieldOrdering.php index f2b9d625..43696094 100644 --- a/Gishiki/Database/Runtime/FieldOrdering.php +++ b/Gishiki/Database/Runtime/FieldOrdering.php @@ -1,29 +1,29 @@ - - */ -abstract class FieldOrdering -{ - const ASC = 1; - const DESC = -1; -} + + */ +abstract class FieldOrdering +{ + const ASC = 1; + const DESC = -1; +} diff --git a/Gishiki/Database/Runtime/FieldRelation.php b/Gishiki/Database/Runtime/FieldRelation.php index b8fc0cdc..e882861e 100644 --- a/Gishiki/Database/Runtime/FieldRelation.php +++ b/Gishiki/Database/Runtime/FieldRelation.php @@ -1,37 +1,37 @@ - - */ -abstract class FieldRelation -{ - const EQUAL = '='; - const NOT_EQUAL = '!='; - const LESS_THAN = '<'; - const LESS_OR_EQUAL_THAN = '<='; - const GREATER_THAN = '>'; - const GREATER_OR_EQUAL_THAN = '>='; - const IN_RANGE = 'IN'; - const NOT_IN_RANGE = 'NOT IN'; - const LIKE = 'LIKE'; - const NOT_LIKE = 'NOT LIKE'; -} + + */ +abstract class FieldRelation +{ + const EQUAL = '='; + const NOT_EQUAL = '!='; + const LESS_THAN = '<'; + const LESS_OR_EQUAL_THAN = '<='; + const GREATER_THAN = '>'; + const GREATER_OR_EQUAL_THAN = '>='; + const IN_RANGE = 'IN'; + const NOT_IN_RANGE = 'NOT IN'; + const LIKE = 'LIKE'; + const NOT_LIKE = 'NOT LIKE'; +} diff --git a/Gishiki/Database/Runtime/ResultModifier.php b/Gishiki/Database/Runtime/ResultModifier.php index 93153853..6bf71d9c 100644 --- a/Gishiki/Database/Runtime/ResultModifier.php +++ b/Gishiki/Database/Runtime/ResultModifier.php @@ -1,191 +1,191 @@ - - */ -final class ResultModifier -{ - /** - * @var int the number of rows to be discarded - */ - protected $skip = 0; - - /** - * @var int the limit of rows to be fetched - */ - protected $limit = 0; - - /** - * @var array a list of columns with the respective order - */ - protected $orderColumns = []; - - /** - * Initialize a new result modifier using the initializer data. - * - * - * $resultFilter = ResultModifier::initialize([ - * 'limit' => 5, - * 'skip' => 8, - * 'name' => FieldOrdering::ASC - * ]); - * - * - * @param array|null $init the initializer data - * - * @return \self the initialized result modifier - * - * @throws \InvalidArgumentException the initializer data is not valid - */ - public static function initialize($init = null) - { - if ((!is_null($init)) && (!is_array($init))) { - throw new \InvalidArgumentException('The initialization filter con only be null or a valid array'); - } - - //create a new result modifier - $modifier = new self(); - - if (is_array($init)) { - foreach ($init as $key => $value) { - if (is_string($key)) { - switch (strtolower($key)) { - case 'skip': - $modifier->skip($value); - break; - - case 'limit': - $modifier->limit($value); - break; - - default: - $modifier->order($key, $value); - break; - } - } - } - } - - // return the new result modifier - return $modifier; - } - - /** - * Create a new result modifier that acts as "no filters". - */ - public function __construct() - { - } - - /** - * Change the order of elements in the result set. - * - * @param string $field the name of the field to be used for ordering - * @param int $order the order to be applied (one of FieldOrdering consts) - * - * @return \Gishiki\Database\Runtime\ResultModifier the modified filter - * - * @throws \InvalidArgumentException passed input is not valid or incompatible type - */ - public function order($field, $order) - { - //check for the type of the input - if (!is_string($field)) { - throw new \InvalidArgumentException('The name of the field to be ordered must be given as a string'); - } - if (($order !== FieldOrdering::ASC) && ($order !== FieldOrdering::DESC)) { - throw new \InvalidArgumentException('The ordering mus be given as ASC or DESC (see FieldOrdering)'); - } - - //set ordering - $this->orderColumns[$field] = $order; - - //return the modified filter - return $this; - //this is really important as it - //allows the developer to chain - //filter modifier functions - } - - /** - * Change the limit of the elements in the result set. - * - * @param int $limit the maximum number of results that can be fetched from the database - * - * @return \Gishiki\Database\Runtime\ResultModifier the modified filter - * - * @throws \InvalidArgumentException passed input is not valid or incompatible type - */ - public function limit($limit = -1) - { - //check for the type of the input - if (!is_int($limit)) { - throw new \InvalidArgumentException('The limit must be given as an integer number'); - } - - //change the limit - $this->limit = $limit; - - //return the modified filter - return $this; - //this is really important as it - //allows the developer to chain - //filter modifier functions - } - - /** - * Change the offset of elements in the result set. - * - * @param int $offset the offset to be applied - * - * @return \Gishiki\Database\Runtime\ResultModifier the modified filter - * - * @throws \InvalidArgumentException passed input is not valid or incompatible type - */ - public function skip($offset = -1) - { - //check for the type of the input - if (!is_int($offset)) { - throw new \InvalidArgumentException('The offset must be given as an integer number'); - } - - //change the limit - $this->skip = $offset; - - //return the modified filter - return $this; - //this is really important as it - //allows the developer to chain - //filter modifier functions - } - - private function export() - { - $export = [ - 'limit' => $this->limit, - 'skip' => $this->skip, - 'order' => $this->orderColumns, - ]; - - return $export; - } -} + + */ +final class ResultModifier +{ + /** + * @var int the number of rows to be discarded + */ + protected $skip = 0; + + /** + * @var int the limit of rows to be fetched + */ + protected $limit = 0; + + /** + * @var array a list of columns with the respective order + */ + protected $orderColumns = []; + + /** + * Initialize a new result modifier using the initializer data. + * + * + * $resultFilter = ResultModifier::initialize([ + * 'limit' => 5, + * 'skip' => 8, + * 'name' => FieldOrdering::ASC + * ]); + * + * + * @param array|null $init the initializer data + * + * @return \self the initialized result modifier + * + * @throws \InvalidArgumentException the initializer data is not valid + */ + public static function initialize($init = null) + { + if ((!is_null($init)) && (!is_array($init))) { + throw new \InvalidArgumentException('The initialization filter con only be null or a valid array'); + } + + //create a new result modifier + $modifier = new self(); + + if (is_array($init)) { + foreach ($init as $key => $value) { + if (is_string($key)) { + switch (strtolower($key)) { + case 'skip': + $modifier->skip($value); + break; + + case 'limit': + $modifier->limit($value); + break; + + default: + $modifier->order($key, $value); + break; + } + } + } + } + + // return the new result modifier + return $modifier; + } + + /** + * Create a new result modifier that acts as "no filters". + */ + public function __construct() + { + } + + /** + * Change the order of elements in the result set. + * + * @param string $field the name of the field to be used for ordering + * @param int $order the order to be applied (one of FieldOrdering consts) + * + * @return \Gishiki\Database\Runtime\ResultModifier the modified filter + * + * @throws \InvalidArgumentException passed input is not valid or incompatible type + */ + public function order($field, $order) + { + //check for the type of the input + if (!is_string($field)) { + throw new \InvalidArgumentException('The name of the field to be ordered must be given as a string'); + } + if (($order !== FieldOrdering::ASC) && ($order !== FieldOrdering::DESC)) { + throw new \InvalidArgumentException('The ordering mus be given as ASC or DESC (see FieldOrdering)'); + } + + //set ordering + $this->orderColumns[$field] = $order; + + //return the modified filter + return $this; + //this is really important as it + //allows the developer to chain + //filter modifier functions + } + + /** + * Change the limit of the elements in the result set. + * + * @param int $limit the maximum number of results that can be fetched from the database + * + * @return \Gishiki\Database\Runtime\ResultModifier the modified filter + * + * @throws \InvalidArgumentException passed input is not valid or incompatible type + */ + public function limit($limit = -1) + { + //check for the type of the input + if (!is_int($limit)) { + throw new \InvalidArgumentException('The limit must be given as an integer number'); + } + + //change the limit + $this->limit = $limit; + + //return the modified filter + return $this; + //this is really important as it + //allows the developer to chain + //filter modifier functions + } + + /** + * Change the offset of elements in the result set. + * + * @param int $offset the offset to be applied + * + * @return \Gishiki\Database\Runtime\ResultModifier the modified filter + * + * @throws \InvalidArgumentException passed input is not valid or incompatible type + */ + public function skip($offset = -1) + { + //check for the type of the input + if (!is_int($offset)) { + throw new \InvalidArgumentException('The offset must be given as an integer number'); + } + + //change the limit + $this->skip = $offset; + + //return the modified filter + return $this; + //this is really important as it + //allows the developer to chain + //filter modifier functions + } + + private function export() + { + $export = [ + 'limit' => $this->limit, + 'skip' => $this->skip, + 'order' => $this->orderColumns, + ]; + + return $export; + } +} diff --git a/Gishiki/Database/Runtime/SelectionCriteria.php b/Gishiki/Database/Runtime/SelectionCriteria.php index 0b88a21f..46702f5f 100644 --- a/Gishiki/Database/Runtime/SelectionCriteria.php +++ b/Gishiki/Database/Runtime/SelectionCriteria.php @@ -1,159 +1,159 @@ - - */ -final class SelectionCriteria -{ - const AND_Historic_Marker = 0b10000000; - - /** - * @var array keeps track of the order clauses were inserted - */ - protected $historic = []; - - /** - * @var array both 'or' and 'and' are two arrays of sub-arrays - */ - protected $criteria = [ - 'and' => [], - 'or' => [], - ]; - - public static function select(array $selection = []) - { - //create an empty selection criteria - $selectionCriteria = new self(); - - foreach ($selection as $fieldName => $fieldValue) { - (!is_array($fieldValue)) ? - $selectionCriteria->AndWhere($fieldName, FieldRelation::EQUAL, $fieldValue) - : $selectionCriteria->AndWhere($fieldName, FieldRelation::IN_RANGE, $fieldValue); - } - - return $selectionCriteria; - } - - /** - * Create a sub-clause and append it to the where clause using an and as conjunction. - * - * @param string $field the name of the field/column to be related with the data - * @param int $relation the Relation between the field and the data - * @param mixed $data the data to be related with the field - * - * @return \Gishiki\Database\Runtime\SelectionCriteria the updated selection criteria - * - * @throws \InvalidArgumentException one parameter has a wrong type - */ - public function AndWhere($field, $relation, $data) - { - if (!is_string($field) || (strlen($field) <= 0)) { - throw new \InvalidArgumentException('the field name must be a string'); - } - if (($relation != FieldRelation::EQUAL) && - ($relation != FieldRelation::NOT_EQUAL) && - ($relation != FieldRelation::LESS_THAN) && - ($relation != FieldRelation::LESS_OR_EQUAL_THAN) && - ($relation != FieldRelation::GREATER_THAN) && - ($relation != FieldRelation::GREATER_OR_EQUAL_THAN) && - ($relation != FieldRelation::IN_RANGE) && - ($relation != FieldRelation::NOT_IN_RANGE) && - ($relation != FieldRelation::LIKE) && - ($relation != FieldRelation::NOT_LIKE)) { - throw new \InvalidArgumentException('the Relation between a column and its value must be expressed by one of FieldRelation constants'); - } - if ((is_object($data)) || (is_resource($data))) { - throw new \InvalidArgumentException('the field data cannot be a php object or an extension native resource'); - } - - $this->criteria['and'][] = [ - 0 => $field, - 1 => $relation, - 2 => $data, - ]; - - $this->historic[] = self::AND_Historic_Marker | (count($this->criteria['and']) - 1); - - //return the modified filter - return $this; - //this is really important as it - //allows the developer to chain - //filter modifier functions - } - - /** - * Create a sub-clause and append it to the where clause using an or as conjunction. - * - * @param string $field the name of the field/column to be related with the data - * @param int $relation the Relation between the field and the data - * @param mixed $data the data to be related with the field - * - * @return \Gishiki\Database\Runtime\SelectionCriteria the updated selection criteria - * - * @throws \InvalidArgumentException one parameter has a wrong type - */ - public function OrWhere($field, $relation, $data) - { - if (!is_string($field)) { - throw new \InvalidArgumentException('the field name must be a string'); - } - if (($relation != FieldRelation::EQUAL) && - ($relation != FieldRelation::NOT_EQUAL) && - ($relation != FieldRelation::LESS_THAN) && - ($relation != FieldRelation::LESS_OR_EQUAL_THAN) && - ($relation != FieldRelation::GREATER_THAN) && - ($relation != FieldRelation::GREATER_OR_EQUAL_THAN) && - ($relation != FieldRelation::IN_RANGE) && - ($relation != FieldRelation::NOT_IN_RANGE) && - ($relation != FieldRelation::LIKE) && - ($relation != FieldRelation::NOT_LIKE)) { - throw new \InvalidArgumentException('the Relation between a column and its value must be expressed by one of FieldRelation constants'); - } - if ((is_object($data)) || (is_resource($data))) { - throw new \InvalidArgumentException('the field data cannot be a php object or an extension native resource'); - } - - $this->criteria['or'][] = [ - 0 => $field, - 1 => $relation, - 2 => $data, - ]; - - $this->historic[] = count($this->criteria['or']) - 1; - - //return the modified filter - return $this; - //this is really important as it - //allows the developer to chain - //filter modifier functions - } - - private function export() - { - $export = [ - 'historic' => $this->historic, - 'criteria' => $this->criteria, - ]; - - return $export; - } -} + + */ +final class SelectionCriteria +{ + const AND_Historic_Marker = 0b10000000; + + /** + * @var array keeps track of the order clauses were inserted + */ + protected $historic = []; + + /** + * @var array both 'or' and 'and' are two arrays of sub-arrays + */ + protected $criteria = [ + 'and' => [], + 'or' => [], + ]; + + public static function select(array $selection = []) + { + //create an empty selection criteria + $selectionCriteria = new self(); + + foreach ($selection as $fieldName => $fieldValue) { + (!is_array($fieldValue)) ? + $selectionCriteria->AndWhere($fieldName, FieldRelation::EQUAL, $fieldValue) + : $selectionCriteria->AndWhere($fieldName, FieldRelation::IN_RANGE, $fieldValue); + } + + return $selectionCriteria; + } + + /** + * Create a sub-clause and append it to the where clause using an and as conjunction. + * + * @param string $field the name of the field/column to be related with the data + * @param int $relation the Relation between the field and the data + * @param mixed $data the data to be related with the field + * + * @return \Gishiki\Database\Runtime\SelectionCriteria the updated selection criteria + * + * @throws \InvalidArgumentException one parameter has a wrong type + */ + public function AndWhere($field, $relation, $data) + { + if (!is_string($field) || (strlen($field) <= 0)) { + throw new \InvalidArgumentException('the field name must be a string'); + } + if (($relation != FieldRelation::EQUAL) && + ($relation != FieldRelation::NOT_EQUAL) && + ($relation != FieldRelation::LESS_THAN) && + ($relation != FieldRelation::LESS_OR_EQUAL_THAN) && + ($relation != FieldRelation::GREATER_THAN) && + ($relation != FieldRelation::GREATER_OR_EQUAL_THAN) && + ($relation != FieldRelation::IN_RANGE) && + ($relation != FieldRelation::NOT_IN_RANGE) && + ($relation != FieldRelation::LIKE) && + ($relation != FieldRelation::NOT_LIKE)) { + throw new \InvalidArgumentException('the Relation between a column and its value must be expressed by one of FieldRelation constants'); + } + if ((is_object($data)) || (is_resource($data))) { + throw new \InvalidArgumentException('the field data cannot be a php object or an extension native resource'); + } + + $this->criteria['and'][] = [ + 0 => $field, + 1 => $relation, + 2 => $data, + ]; + + $this->historic[] = self::AND_Historic_Marker | (count($this->criteria['and']) - 1); + + //return the modified filter + return $this; + //this is really important as it + //allows the developer to chain + //filter modifier functions + } + + /** + * Create a sub-clause and append it to the where clause using an or as conjunction. + * + * @param string $field the name of the field/column to be related with the data + * @param int $relation the Relation between the field and the data + * @param mixed $data the data to be related with the field + * + * @return \Gishiki\Database\Runtime\SelectionCriteria the updated selection criteria + * + * @throws \InvalidArgumentException one parameter has a wrong type + */ + public function OrWhere($field, $relation, $data) + { + if (!is_string($field)) { + throw new \InvalidArgumentException('the field name must be a string'); + } + if (($relation != FieldRelation::EQUAL) && + ($relation != FieldRelation::NOT_EQUAL) && + ($relation != FieldRelation::LESS_THAN) && + ($relation != FieldRelation::LESS_OR_EQUAL_THAN) && + ($relation != FieldRelation::GREATER_THAN) && + ($relation != FieldRelation::GREATER_OR_EQUAL_THAN) && + ($relation != FieldRelation::IN_RANGE) && + ($relation != FieldRelation::NOT_IN_RANGE) && + ($relation != FieldRelation::LIKE) && + ($relation != FieldRelation::NOT_LIKE)) { + throw new \InvalidArgumentException('the Relation between a column and its value must be expressed by one of FieldRelation constants'); + } + if ((is_object($data)) || (is_resource($data))) { + throw new \InvalidArgumentException('the field data cannot be a php object or an extension native resource'); + } + + $this->criteria['or'][] = [ + 0 => $field, + 1 => $relation, + 2 => $data, + ]; + + $this->historic[] = count($this->criteria['or']) - 1; + + //return the modified filter + return $this; + //this is really important as it + //allows the developer to chain + //filter modifier functions + } + + private function export() + { + $export = [ + 'historic' => $this->historic, + 'criteria' => $this->criteria, + ]; + + return $export; + } +} diff --git a/Gishiki/Database/Schema/Column.php b/Gishiki/Database/Schema/Column.php index 8c05e704..42ce69d8 100644 --- a/Gishiki/Database/Schema/Column.php +++ b/Gishiki/Database/Schema/Column.php @@ -1,238 +1,238 @@ - - */ -final class Column -{ - /** - * @var string the name of the column - */ - protected $name; - - /** - * @var int the type of the column, expressed as one of the ColumnType constants - */ - protected $type; - - /** - * @var bool TRUE if the column cannot hold null - */ - protected $notNull; - - /** - * @var bool TRUE if the column is autoincrement - */ - protected $autoIncrement; - - /** - * @var bool TRUE if the column is a primary key - */ - protected $pkey; - - /** - * @var ColumnRelation|null the relation to an external table or null - */ - protected $relation; - - /** - * Initialize a column with the given name. - * This function internally calls setName(), and you should catch - * exceptions thrown by that function. - * - * @param string $name the name of the column - * @param int $type the data type of the column - */ - public function __construct($name, $type) - { - $this->name = ''; - $this->dataType = 0; - $this->pkey = false; - $this->notNull = false; - $this->relation = null; - $this->autoIncrement = null; - $this->setName($name); - $this->setType($type); - } - - /** - * Change the auto increment flag on the column. - * - * @param bool $enable TRUE is used to flag an auto increment column as such - * @return Column a reference to the modified Column - * @throws \InvalidArgumentException the new status is invalid - */ - public function &setAutoIncrement($enable) - { - if (!is_bool($enable)) { - throw new \InvalidArgumentException('The auto-increment flag of a column must be given as a boolean value'); - } - - $this->autoIncrement = $enable; - - return $this; - } - - public function getAutoIncrement() - { - return $this->autoIncrement; - } - - /** - * Change the primary key flag on the column. - * - * @param bool $enable TRUE is used to flag a not null column as such - * @return Column a reference to the modified Column - * @throws \InvalidArgumentException the new status is invalid - */ - public function &setNotNull($enable) - { - if (!is_bool($enable)) { - throw new \InvalidArgumentException('The not null flag of a column must be given as a boolean value'); - } - - $this->notNull = $enable; - - return $this; - } - - /** - * Retrieve the not null flag on the column. - * - * @return bool $enable TRUE if the column cannot contains null - */ - public function getNotNull() - { - return $this->notNull; - } - - /** - * Change the primary key flag on the column. - * - * @param bool $enable TRUE is used to flag a primary key column as such - * @return Column a reference to the modified Column - * @throws \InvalidArgumentException the new status is invalid - */ - public function &setPrimaryKey($enable) - { - if (!is_bool($enable)) { - throw new \InvalidArgumentException('The primary key flag of a column must be given as a boolean value'); - } - - $this->pkey = $enable; - - return $this; - } - - /** - * Retrieve the auto increment flag on the column. - * - * @return bool $enable TRUE if the column is a primary key - */ - public function getPrimaryKey() - { - return $this->pkey; - } - - /** - * Change the relation of the current column. - * - * @param ColumnRelation $rel the column relation - * @return Column a reference to the modified Column - * @throws \InvalidArgumentException the column name is invalid - */ - public function &setRelation(ColumnRelation &$rel) - { - $this->relation = $rel; - - return $this; - } - - /** - * Retrieve the relation of the column. - * - * @return ColumnRelation|null the column relation or null - */ - public function getRelation() - { - return $this->relation; - } - - /** - * Change the name of the current column. - * - * @param string $name the name of the column - * @return Column a reference to the modified Column - * @throws \InvalidArgumentException the column name is invalid - */ - public function &setName($name) - { - //avoid bad names - if ((!is_string($name)) || (strlen($name) <= 0)) { - throw new \InvalidArgumentException('The name of a column must be expressed as a non-empty string'); - } - - $this->name = $name; - - return $this; - } - - /** - * Retrieve the name of the column. - * - * @return string the column name - */ - public function getName() - { - return $this->name; - } - - /** - * Change the type of the current table passing as argument one - * of the ColumnType contants. - * - * @param string $type the type of the column - * @return Column a reference to the modified Column - * @throws \InvalidArgumentException the column name is invalid - */ - public function &setType($type) - { - //avoid bad names - if ((!is_integer($type)) || ($type >= ColumnType::UNKNOWN) || ($type < 0)) { - throw new \InvalidArgumentException('The type of the column is invalid.'); - } - - $this->type = $type; - - return $this; - } - - /** - * Retrieve the type of the column. - * - * @return int the column name - */ - public function getType() - { - return $this->type; - } -} + + */ +final class Column +{ + /** + * @var string the name of the column + */ + protected $name; + + /** + * @var int the type of the column, expressed as one of the ColumnType constants + */ + protected $type; + + /** + * @var bool TRUE if the column cannot hold null + */ + protected $notNull; + + /** + * @var bool TRUE if the column is autoincrement + */ + protected $autoIncrement; + + /** + * @var bool TRUE if the column is a primary key + */ + protected $pkey; + + /** + * @var ColumnRelation|null the relation to an external table or null + */ + protected $relation; + + /** + * Initialize a column with the given name. + * This function internally calls setName(), and you should catch + * exceptions thrown by that function. + * + * @param string $name the name of the column + * @param int $type the data type of the column + */ + public function __construct($name, $type) + { + $this->name = ''; + $this->dataType = 0; + $this->pkey = false; + $this->notNull = false; + $this->relation = null; + $this->autoIncrement = null; + $this->setName($name); + $this->setType($type); + } + + /** + * Change the auto increment flag on the column. + * + * @param bool $enable TRUE is used to flag an auto increment column as such + * @return Column a reference to the modified Column + * @throws \InvalidArgumentException the new status is invalid + */ + public function &setAutoIncrement($enable) + { + if (!is_bool($enable)) { + throw new \InvalidArgumentException('The auto-increment flag of a column must be given as a boolean value'); + } + + $this->autoIncrement = $enable; + + return $this; + } + + public function getAutoIncrement() + { + return $this->autoIncrement; + } + + /** + * Change the primary key flag on the column. + * + * @param bool $enable TRUE is used to flag a not null column as such + * @return Column a reference to the modified Column + * @throws \InvalidArgumentException the new status is invalid + */ + public function &setNotNull($enable) + { + if (!is_bool($enable)) { + throw new \InvalidArgumentException('The not null flag of a column must be given as a boolean value'); + } + + $this->notNull = $enable; + + return $this; + } + + /** + * Retrieve the not null flag on the column. + * + * @return bool $enable TRUE if the column cannot contains null + */ + public function getNotNull() + { + return $this->notNull; + } + + /** + * Change the primary key flag on the column. + * + * @param bool $enable TRUE is used to flag a primary key column as such + * @return Column a reference to the modified Column + * @throws \InvalidArgumentException the new status is invalid + */ + public function &setPrimaryKey($enable) + { + if (!is_bool($enable)) { + throw new \InvalidArgumentException('The primary key flag of a column must be given as a boolean value'); + } + + $this->pkey = $enable; + + return $this; + } + + /** + * Retrieve the auto increment flag on the column. + * + * @return bool $enable TRUE if the column is a primary key + */ + public function getPrimaryKey() + { + return $this->pkey; + } + + /** + * Change the relation of the current column. + * + * @param ColumnRelation $rel the column relation + * @return Column a reference to the modified Column + * @throws \InvalidArgumentException the column name is invalid + */ + public function &setRelation(ColumnRelation &$rel) + { + $this->relation = $rel; + + return $this; + } + + /** + * Retrieve the relation of the column. + * + * @return ColumnRelation|null the column relation or null + */ + public function getRelation() + { + return $this->relation; + } + + /** + * Change the name of the current column. + * + * @param string $name the name of the column + * @return Column a reference to the modified Column + * @throws \InvalidArgumentException the column name is invalid + */ + public function &setName($name) + { + //avoid bad names + if ((!is_string($name)) || (strlen($name) <= 0)) { + throw new \InvalidArgumentException('The name of a column must be expressed as a non-empty string'); + } + + $this->name = $name; + + return $this; + } + + /** + * Retrieve the name of the column. + * + * @return string the column name + */ + public function getName() + { + return $this->name; + } + + /** + * Change the type of the current table passing as argument one + * of the ColumnType contants. + * + * @param string $type the type of the column + * @return Column a reference to the modified Column + * @throws \InvalidArgumentException the column name is invalid + */ + public function &setType($type) + { + //avoid bad names + if ((!is_integer($type)) || ($type >= ColumnType::UNKNOWN) || ($type < 0)) { + throw new \InvalidArgumentException('The type of the column is invalid.'); + } + + $this->type = $type; + + return $this; + } + + /** + * Retrieve the type of the column. + * + * @return int the column name + */ + public function getType() + { + return $this->type; + } +} diff --git a/Gishiki/Database/Schema/ColumnRelation.php b/Gishiki/Database/Schema/ColumnRelation.php index 3efe194e..8cc21f6f 100644 --- a/Gishiki/Database/Schema/ColumnRelation.php +++ b/Gishiki/Database/Schema/ColumnRelation.php @@ -1,81 +1,81 @@ - - */ -final class ColumnRelation -{ - /** - * @var Table the table containing the foreign key - */ - protected $foreignTable; - - /** - * @var Column the column of the current table - */ - protected $foreignKey; - - /** - * Create a new relation to the given pprimary key. - * - * @param \Gishiki\Database\Schema\Column $externColumn the foreign column - * - * @throws DatabaseException the error occurred while enstabilishing the Relation - */ - public function __construct(Table &$externTable, Column &$externColumn) - { - //I hope you are not going to reference something that is not a primary key - if (!$externColumn->getPrimaryKey()) { - throw new DatabaseException('A Relation can only be created with a foreign primary key', 128); - } - - //... oh and I am pretty sure you are not doing something bad, right? - if (!in_array($externColumn, $externTable->getColumns())) { - throw new DatabaseException("The given foreign table doesn't contain a column with the same name", 129); - } - - $this->foreignKey = $externColumn; - $this->foreignTable = $externTable; - } - - /** - * Get the column on the current table. - * - * @return Column the reference to the column - */ - public function &getForeignKey() - { - return $this->foreignKey; - } - - /** - * Get the table containing the foreign key. - * - * @return Table the table containing the foreign key - */ - public function &getForeignTable() - { - return $this->foreignTable; - } -} + + */ +final class ColumnRelation +{ + /** + * @var Table the table containing the foreign key + */ + protected $foreignTable; + + /** + * @var Column the column of the current table + */ + protected $foreignKey; + + /** + * Create a new relation to the given pprimary key. + * + * @param \Gishiki\Database\Schema\Column $externColumn the foreign column + * + * @throws DatabaseException the error occurred while enstabilishing the Relation + */ + public function __construct(Table &$externTable, Column &$externColumn) + { + //I hope you are not going to reference something that is not a primary key + if (!$externColumn->getPrimaryKey()) { + throw new DatabaseException('A Relation can only be created with a foreign primary key', 128); + } + + //... oh and I am pretty sure you are not doing something bad, right? + if (!in_array($externColumn, $externTable->getColumns())) { + throw new DatabaseException("The given foreign table doesn't contain a column with the same name", 129); + } + + $this->foreignKey = $externColumn; + $this->foreignTable = $externTable; + } + + /** + * Get the column on the current table. + * + * @return Column the reference to the column + */ + public function &getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the table containing the foreign key. + * + * @return Table the table containing the foreign key + */ + public function &getForeignTable() + { + return $this->foreignTable; + } +} diff --git a/Gishiki/Database/Schema/ColumnType.php b/Gishiki/Database/Schema/ColumnType.php index 19241110..96444ef8 100644 --- a/Gishiki/Database/Schema/ColumnType.php +++ b/Gishiki/Database/Schema/ColumnType.php @@ -1,41 +1,41 @@ - - */ -abstract class ColumnType -{ - const SMALLINT = 0; - const INTEGER = 1; - const BIGINT = 2; - const TEXT = 3; - const FLOAT = 4; - const DOUBLE= 5; - const DATETIME = 6; - const NUMERIC = 7; - const MONEY = 8; - - /** - * This is NOT a type and MUST NOT be used! - */ - const UNKNOWN = 9; -} + + */ +abstract class ColumnType +{ + const SMALLINT = 0; + const INTEGER = 1; + const BIGINT = 2; + const TEXT = 3; + const FLOAT = 4; + const DOUBLE= 5; + const DATETIME = 6; + const NUMERIC = 7; + const MONEY = 8; + + /** + * This is NOT a type and MUST NOT be used! + */ + const UNKNOWN = 9; +} diff --git a/Gishiki/Database/Schema/Table.php b/Gishiki/Database/Schema/Table.php index 03d77382..e5794e61 100644 --- a/Gishiki/Database/Schema/Table.php +++ b/Gishiki/Database/Schema/Table.php @@ -1,116 +1,116 @@ - - */ -final class Table -{ - /** - * @var string the name of the table - */ - protected $name; - - /** - * @var array a list of columns inside the current database - */ - protected $columns; - - /** - * Initialize a table with the given name. - * This function internally calls setName(), and you should catch - * exceptions thrown by that function. - * - * @param string $name the name of the table - */ - public function __construct($name) - { - $this->name = ''; - $this->columns = []; - $this->foreignKeys = []; - $this->setName($name); - } - - /** - * Add a column to the current table. - * - * @param \Gishiki\Database\Schema\Column $col the column to be added - * - * @return \Gishiki\Database\Schema\Table a reference to the modified table - * - * @throws DatabaseException A table with the same name already exists - */ - public function &addColumn(Column &$col) - { - foreach ($this->columns as $currentCol) { - if (strcmp($col->getName(), $currentCol->getName()) == 0) { - throw new DatabaseException('A Table cannot contain two columns with the same name', 140); - } - } - - $this->columns[] = $col; - - return $this; - } - - /** - * Return the list of columns inside the current table. - * - * @return array thelist of columns - */ - public function getColumns() - { - return $this->columns; - } - - /** - * Change the name of the current table. - * - * @param string $name the name of the table - * - * @return \Gishiki\Database\Schema\Table a reference to the modified table - * - * @throws \InvalidArgumentException the table name is invalid - */ - public function &setName($name) - { - //avoid bad names - if ((!is_string($name)) || (strlen($name) <= 0)) { - throw new \InvalidArgumentException('The name of a table must be expressed as a non-empty string'); - } - - $this->name = $name; - - return $this; - } - - /** - * Retrieve the name of the table. - * - * @return string the table name - */ - public function getName() - { - return $this->name; - } -} + + */ +final class Table +{ + /** + * @var string the name of the table + */ + protected $name; + + /** + * @var array a list of columns inside the current database + */ + protected $columns; + + /** + * Initialize a table with the given name. + * This function internally calls setName(), and you should catch + * exceptions thrown by that function. + * + * @param string $name the name of the table + */ + public function __construct($name) + { + $this->name = ''; + $this->columns = []; + $this->foreignKeys = []; + $this->setName($name); + } + + /** + * Add a column to the current table. + * + * @param \Gishiki\Database\Schema\Column $col the column to be added + * + * @return \Gishiki\Database\Schema\Table a reference to the modified table + * + * @throws DatabaseException A table with the same name already exists + */ + public function &addColumn(Column &$col) + { + foreach ($this->columns as $currentCol) { + if (strcmp($col->getName(), $currentCol->getName()) == 0) { + throw new DatabaseException('A Table cannot contain two columns with the same name', 140); + } + } + + $this->columns[] = $col; + + return $this; + } + + /** + * Return the list of columns inside the current table. + * + * @return array thelist of columns + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Change the name of the current table. + * + * @param string $name the name of the table + * + * @return \Gishiki\Database\Schema\Table a reference to the modified table + * + * @throws \InvalidArgumentException the table name is invalid + */ + public function &setName($name) + { + //avoid bad names + if ((!is_string($name)) || (strlen($name) <= 0)) { + throw new \InvalidArgumentException('The name of a table must be expressed as a non-empty string'); + } + + $this->name = $name; + + return $this; + } + + /** + * Retrieve the name of the table. + * + * @return string the table name + */ + public function getName() + { + return $this->name; + } +} diff --git a/Gishiki/Gishiki.php b/Gishiki/Gishiki.php index 66e14cb6..d80a1750 100755 --- a/Gishiki/Gishiki.php +++ b/Gishiki/Gishiki.php @@ -1,88 +1,91 @@ - - */ -abstract class Gishiki -{ - //this is the environment used to fulfill the incoming request - public static $executionEnvironment = null; - - //was the Run function already being executed? - private static $executed = false; - - /** - * Initialize the Gishiki engine and prepare for - * the execution of a framework instance. - */ - public static function initialize() - { - //remove default execution time - set_time_limit(0); - - //get the root path - $documentRoot = filter_input(INPUT_SERVER, 'DOCUMENT_ROOT'); - - (strlen($documentRoot) > 0) ? - define('ROOT', filter_input(INPUT_SERVER, 'DOCUMENT_ROOT').DIRECTORY_SEPARATOR) : - define('ROOT', getcwd().DIRECTORY_SEPARATOR); - - //the name of the directory that contains model, view and controller (must be placed in the root) - if (!defined('APPLICATION_DIR')) { - define('APPLICATION_DIR', ROOT.DIRECTORY_SEPARATOR); - } - } - - /** - * Execute the requested operation. - */ - public static function run() - { - //avoid double executions - if (self::$executed) { - return; - } - - //initialize the framework - self::initialize(); - - //each Gishiki instance is binded with a newly created Environment - if (!is_object(self::$executionEnvironment)) { - self::$executionEnvironment = new Environment( - filter_input_array(INPUT_SERVER), true, true); - } - - //if the framework needs to be installed..... - if (Environment::applicationExists()) { - //fulfill the client request - Environment::getCurrentEnvironment()->fulfillRequest(); - } elseif (!defined('CLI_TOOLKIT')) { - //show the no application page! - echo file_get_contents(__DIR__.DIRECTORY_SEPARATOR.'no_application.html'); - } - - //the framework execution is complete - self::$executed = true; - } -} + + */ +abstract class Gishiki +{ + //this is the environment used to fulfill the incoming request + public static $executionEnvironment = null; + + //was the Run function already being executed? + private static $executed = false; + + /** + * Initialize the Gishiki engine and prepare for + * the execution of a framework instance. + */ + public static function initialize() + { + //remove default execution time + set_time_limit(0); + + //get the root path + $documentRoot = filter_input(INPUT_SERVER, 'DOCUMENT_ROOT'); + + ((!defined('ROOT')) && (strlen($documentRoot) > 0)) ? + define('ROOT', filter_input(INPUT_SERVER, 'DOCUMENT_ROOT').DIRECTORY_SEPARATOR) : + define('ROOT', getcwd().DIRECTORY_SEPARATOR); + + //the name of the directory that contains model, view and controller (must be placed in the root) + if (!defined('APPLICATION_DIR')) { + define('APPLICATION_DIR', ROOT.DIRECTORY_SEPARATOR); + } + } + + /** + * Execute the requested operation. + * + * @param $application Router + */ + public static function run(Router &$application) + { + //avoid double executions + if (self::$executed) { + return; + } + + //initialize the framework + self::initialize(); + + //each Gishiki instance is binded with a newly created Environment + if (!is_object(self::$executionEnvironment)) { + self::$executionEnvironment = new Environment( + filter_input_array(INPUT_SERVER), true, true); + } + + //if the framework needs to be installed..... + if (Environment::applicationExists()) { + //fulfill the client request + Environment::getCurrentEnvironment()->fulfillRequest($application); + } elseif (!defined('CLI_TOOLKIT')) { + //show the no application page! + echo file_get_contents(__DIR__.DIRECTORY_SEPARATOR.'no_application.html'); + } + + //the framework execution is complete + self::$executed = true; + } +} diff --git a/Gishiki/HttpKernel/Body.php b/Gishiki/HttpKernel/Body.php deleted file mode 100644 index 4106703f..00000000 --- a/Gishiki/HttpKernel/Body.php +++ /dev/null @@ -1,23 +0,0 @@ - '', - 'domain' => null, - 'path' => null, - 'expires' => null, - 'secure' => false, - 'httponly' => false, - ]; - - /** - * Create new cookies helper. - * - * @param array $cookies - */ - public function __construct(array $cookies = []) - { - $this->requestCookies = $cookies; - } - - /** - * Set default cookie properties. - * - * @param array $settings - */ - public function setDefaults(array $settings) - { - $this->defaults = array_replace($this->defaults, $settings); - } - - /** - * Get request cookie. - * - * @param string $name Cookie name - * @param mixed $default Cookie default value - * - * @return mixed Cookie value if present, else default - */ - public function get($name, $default = null) - { - return isset($this->requestCookies[$name]) ? $this->requestCookies[$name] : $default; - } - - /** - * Set response cookie. - * - * @param string $name Cookie name - * @param string|array $value Cookie value, or cookie properties - */ - public function set($name, $value) - { - if (!is_array($value)) { - $value = ['value' => (string) $value]; - } - $this->responseCookies[$name] = array_replace($this->defaults, $value); - } - - /** - * Convert to `Set-Cookie` headers. - * - * @return string[] - */ - public function toHeaders() - { - $headers = []; - foreach ($this->responseCookies as $name => $properties) { - $headers[] = $this->toHeader($name, $properties); - } - - return $headers; - } - - /** - * Convert to `Set-Cookie` header. - * - * @param string $name Cookie name - * @param array $properties Cookie properties - * - * @return string - */ - protected function toHeader($name, array $properties) - { - $result = urlencode($name).'='.urlencode($properties['value']); - - if (isset($properties['domain'])) { - $result .= '; domain='.$properties['domain']; - } - - if (isset($properties['path'])) { - $result .= '; path='.$properties['path']; - } - - if (isset($properties['expires'])) { - if (is_string($properties['expires'])) { - $timestamp = strtotime($properties['expires']); - } else { - $timestamp = (int) $properties['expires']; - } - if ($timestamp !== 0) { - $result .= '; expires='.gmdate('D, d-M-Y H:i:s e', $timestamp); - } - } - - if (isset($properties['secure']) && $properties['secure']) { - $result .= '; secure'; - } - - if (isset($properties['httponly']) && $properties['httponly']) { - $result .= '; HttpOnly'; - } - - return $result; - } - - /** - * Parse HTTP request `Cookie:` header and extract - * into a PHP associative array. - * - * @param string $header The raw HTTP request `Cookie:` header - * - * @return array Associative array of cookie names and values - * - * @throws InvalidArgumentException if the cookie data cannot be parsed - */ - public static function parseHeader($header) - { - if (is_array($header) === true) { - $header = isset($header[0]) ? $header[0] : ''; - } - - if (is_string($header) === false) { - throw new InvalidArgumentException('Cannot parse Cookie data. Header value must be a string.'); - } - - $header = rtrim($header, "\r\n"); - $pieces = preg_split('@\s*[;,]\s*@', $header); - $cookies = []; - - foreach ($pieces as $cookie) { - $cookie = explode('=', $cookie, 2); - - if (count($cookie) === 2) { - $key = urldecode($cookie[0]); - $value = urldecode($cookie[1]); - - if (!isset($cookies[$key])) { - $cookies[$key] = $value; - } - } - } - - return $cookies; - } -} diff --git a/Gishiki/HttpKernel/CookiesInterface.php b/Gishiki/HttpKernel/CookiesInterface.php deleted file mode 100644 index a3365f17..00000000 --- a/Gishiki/HttpKernel/CookiesInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - 1, - 'CONTENT_LENGTH' => 1, - 'PHP_AUTH_USER' => 1, - 'PHP_AUTH_PW' => 1, - 'PHP_AUTH_DIGEST' => 1, - 'AUTH_TYPE' => 1, - ]; - - /** - * Create new headers collection with data extracted from - * the application Environment object. - * - * @param Environment $environment The Slim application Environment - * - * @return self - */ - public static function createFromEnvironment(Environment $environment) - { - $data = []; - foreach ($environment as $key => $value) { - $key = strtoupper($key); - if (isset(static::$special[$key]) || strpos($key, 'HTTP_') === 0) { - if ($key !== 'HTTP_CONTENT_LENGTH') { - $data[$key] = $value; - } - } - } - - return new static($data); - } - - /** - * Return array of HTTP header names and values. - * This method returns the _original_ header name - * as specified by the end user. - * - * @return array - */ - public function all() - { - $all = parent::all(); - $out = []; - foreach ($all as $props) { - $out[$props['originalKey']] = $props['value']; - } - - return $out; - } - - /** - * Set HTTP header value. - * - * This method sets a header value. It replaces - * any values that may already exist for the header name. - * - * @param string $key The case-insensitive header name - * @param string $value The header value - */ - public function set($key, $value) - { - if (!is_array($value)) { - $value = [$value]; - } - parent::set($this->normalizeKey($key), [ - 'value' => $value, - 'originalKey' => $key, - ]); - } - - /** - * Get HTTP header value. - * - * @param string $key The case-insensitive header name - * @param mixed $default The default value if key does not exist - * - * @return string[] - */ - public function get($key, $default = null) - { - if ($this->has($key)) { - return parent::get($this->normalizeKey($key))['value']; - } - - return $default; - } - - /** - * Get HTTP header key as originally specified. - * - * @param string $key The case-insensitive header name - * @param mixed $default The default value if key does not exist - * - * @return string - */ - public function getOriginalKey($key, $default = null) - { - if ($this->has($key)) { - return parent::get($this->normalizeKey($key))['originalKey']; - } - - return $default; - } - - /** - * Add HTTP header value. - * - * This method appends a header value. Unlike the set() method, - * this method _appends_ this new value to any values - * that already exist for this header name. - * - * @param string $key The case-insensitive header name - * @param array|string $value The new header value(s) - */ - public function add($key, $value) - { - $oldValues = $this->get($key, []); - $newValues = is_array($value) ? $value : [$value]; - $this->set($key, array_merge($oldValues, array_values($newValues))); - } - - /** - * Does this collection have a given header? - * - * @param string $key The case-insensitive header name - * - * @return bool - */ - public function has($key) - { - return parent::has($this->normalizeKey($key)); - } - - /** - * Remove header from collection. - * - * @param string $key The case-insensitive header name - */ - public function remove($key) - { - parent::remove($this->normalizeKey($key)); - } - - /** - * Normalize header name. - * - * This method transforms header names into a - * normalized form. This is how we enable case-insensitive - * header names in the other methods in this class. - * - * @param string $key The case-insensitive header name - * - * @return string Normalized header name - */ - public function normalizeKey($key) - { - $key = strtr(strtolower($key), '_', '-'); - if (strpos($key, 'http-') === 0) { - $key = substr($key, 5); - } - - return $key; - } -} diff --git a/Gishiki/HttpKernel/HeadersInterface.php b/Gishiki/HttpKernel/HeadersInterface.php deleted file mode 100644 index 3f4189a7..00000000 --- a/Gishiki/HttpKernel/HeadersInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -protocolVersion; - } - - /** - * Return an instance with the specified HTTP protocol version. - * - * The version string MUST contain only the HTTP version number (e.g., - * "1.1", "1.0"). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new protocol version. - * - * @param string $version HTTP protocol version - * - * @return static - * - * @throws InvalidArgumentException if the http version is an invalid number - */ - public function withProtocolVersion($version) - { - static $valid = [ - '1.0' => true, - '1.1' => true, - '2.0' => true, - ]; - if (!isset($valid[$version])) { - throw new InvalidArgumentException('Invalid HTTP version. Must be one of: 1.0, 1.1, 2.0'); - } - $clone = clone $this; - $clone->protocolVersion = $version; - - return $clone; - } - - /******************************************************************************* - * Headers - ******************************************************************************/ - - /** - * Retrieves all message header values. - * - * The keys represent the header name as it will be sent over the wire, and - * each value is an array of strings associated with the header. - * - * // Represent the headers as a string - * foreach ($message->getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * // Emit headers iteratively: - * foreach ($message->getHeaders() as $name => $values) { - * foreach ($values as $value) { - * header(sprintf('%s: %s', $name, $value), false); - * } - * } - * - * While header names are not case-sensitive, getHeaders() will preserve the - * exact case in which headers were originally specified. - * - * @return array Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings - * for that header - */ - public function getHeaders() - { - return $this->headers->all(); - } - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $name Case-insensitive header field name - * - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message - */ - public function hasHeader($name) - { - return $this->headers->has($name); - } - - /** - * Retrieves a message header value by the given case-insensitive name. - * - * This method returns an array of all the header values of the given - * case-insensitive header name. - * - * If the header does not appear in the message, this method MUST return an - * empty array. - * - * @param string $name Case-insensitive header field name - * - * @return string[] An array of string values as provided for the given - * header. If the header does not appear in the message, this method MUST - * return an empty array - */ - public function getHeader($name) - { - return $this->headers->get($name, []); - } - - /** - * Retrieves a comma-separated string of the values for a single header. - * - * This method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. - * - * NOTE: Not all header values may be appropriately represented using - * comma concatenation. For such headers, use getHeader() instead - * and supply your own delimiter when concatenating. - * - * If the header does not appear in the message, this method MUST return - * an empty string. - * - * @param string $name Case-insensitive header field name - * - * @return string A string of values as provided for the given header - * concatenated together using a comma. If the header does not appear in - * the message, this method MUST return an empty string - */ - public function getHeaderLine($name) - { - return implode(',', $this->headers->get($name, [])); - } - - /** - * Return an instance with the provided value replacing the specified header. - * - * While header names are case-insensitive, the casing of the header will - * be preserved by this function, and returned from getHeaders(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new and/or updated header and value. - * - * @param string $name Case-insensitive header field name - * @param string|string[] $value Header value(s) - * - * @return static - * - * @throws \InvalidArgumentException for invalid header names or values - */ - public function withHeader($name, $value) - { - $clone = clone $this; - $clone->headers->set($name, $value); - - return $clone; - } - - /** - * Return an instance with the specified header appended with the given value. - * - * Existing values for the specified header will be maintained. The new - * value(s) will be appended to the existing list. If the header did not - * exist previously, it will be added. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new header and/or value. - * - * @param string $name Case-insensitive header field name to add - * @param string|string[] $value Header value(s) - * - * @return static - * - * @throws \InvalidArgumentException for invalid header names or values - */ - public function withAddedHeader($name, $value) - { - $clone = clone $this; - $clone->headers->add($name, $value); - - return $clone; - } - - /** - * Return an instance without the specified header. - * - * Header resolution MUST be done without case-sensitivity. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the named header. - * - * @param string $name Case-insensitive header field name to remove - * - * @return static - */ - public function withoutHeader($name) - { - $clone = clone $this; - $clone->headers->remove($name); - - return $clone; - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - /** - * Gets the body of the message. - * - * @return StreamInterface Returns the body as a stream - */ - public function getBody() - { - return $this->body; - } - - /** - * Return an instance with the specified message body. - * - * The body MUST be a StreamInterface object. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return a new instance that has the - * new body stream. - * - * @param StreamInterface $body Body - * - * @return static - * - * @throws \InvalidArgumentException When the body is not valid - */ - public function withBody(StreamInterface $body) - { - // TODO: Test for invalid body? - $clone = clone $this; - $clone->body = $body; - - return $clone; - } -} diff --git a/Gishiki/HttpKernel/Request.php b/Gishiki/HttpKernel/Request.php deleted file mode 100644 index 666ffb6c..00000000 --- a/Gishiki/HttpKernel/Request.php +++ /dev/null @@ -1,1191 +0,0 @@ - 1, - 'DELETE' => 1, - 'GET' => 1, - 'HEAD' => 1, - 'OPTIONS' => 1, - 'PATCH' => 1, - 'POST' => 1, - 'PUT' => 1, - 'TRACE' => 1, - ]; - - /** - * Create new HTTP request with data extracted from the application - * Environment object. - * - * @param Environment $environment The Slim application Environment - * - * @return self - */ - public static function createFromEnvironment(Environment $environment) - { - $method = $environment['REQUEST_METHOD']; - $uri = Uri::createFromEnvironment($environment); - $headers = Headers::createFromEnvironment($environment); - $cookies = Cookies::parseHeader($headers->get('Cookie', [])); - $serverParams = $environment->all(); - $body = new RequestBody(); - $uploadedFiles = UploadedFile::createFromEnvironment($environment); - - $request = new static($method, $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); - - if ($method === 'POST' && - in_array($request->getMediaType(), ['application/x-www-form-urlencoded', 'multipart/form-data']) - ) { - // parsed body must be $_POST filter_input_array(INPUT_POST) won't work! - $request = $request->withParsedBody($_POST); - } - - return $request; - } - - /** - * Create new HTTP request. - * - * Adds a host header when none was provided and a host is defined in uri. - * - * @param string $method The request method - * @param UriInterface $uri The request URI object - * @param HeadersInterface $headers The request headers collection - * @param array $cookies The request cookies collection - * @param array $serverParams The server environment variables - * @param StreamInterface $body The request body object - * @param array $uploadedFiles The request uploadedFiles collection - */ - public function __construct($method, UriInterface $uri, HeadersInterface $headers, array $cookies, array $serverParams, StreamInterface $body, array $uploadedFiles = []) - { - $this->originalMethod = $this->filterMethod($method); - $this->uri = $uri; - $this->headers = $headers; - $this->cookies = $cookies; - $this->serverParams = $serverParams; - $this->attributes = new GenericCollection(); - $this->body = $body; - $this->uploadedFiles = $uploadedFiles; - - if (isset($serverParams['SERVER_PROTOCOL'])) { - $this->protocolVersion = str_replace('HTTP/', '', $serverParams['SERVER_PROTOCOL']); - } - - if (!$this->headers->has('Host') || $this->uri->getHost() !== '') { - $this->headers->set('Host', $this->uri->getHost()); - } - - $this->registerMediaTypeParser('application/json', function ($input) { - return json_decode($input, true); - }); - - $this->registerMediaTypeParser('application/xml', function ($input) { - $backup = libxml_disable_entity_loader(true); - $result = simplexml_load_string($input); - libxml_disable_entity_loader($backup); - - return $result; - }); - - $this->registerMediaTypeParser('text/xml', function ($input) { - $backup = libxml_disable_entity_loader(true); - $result = simplexml_load_string($input); - libxml_disable_entity_loader($backup); - - return $result; - }); - - $this->registerMediaTypeParser('application/x-www-form-urlencoded', function ($input) { - $data = null; - - parse_str($input, $data); - - return $data; - }); - } - - /** - * This method is applied to the cloned object - * after PHP performs an initial shallow-copy. This - * method completes a deep-copy by creating new objects - * for the cloned object's internal reference pointers. - */ - public function __clone() - { - $this->headers = clone $this->headers; - $this->attributes = clone $this->attributes; - $this->body = clone $this->body; - } - - /******************************************************************************* - * Method - ******************************************************************************/ - - /** - * Retrieves the HTTP method of the request. - * - * @return string Returns the request method - */ - public function getMethod() - { - if ($this->method === null) { - $this->method = $this->originalMethod; - $customMethod = $this->getHeaderLine('X-Http-Method-Override'); - - if ($customMethod) { - $this->method = $this->filterMethod($customMethod); - } elseif ($this->originalMethod === 'POST') { - $body = $this->getParsedBody(); - - if (is_object($body) && property_exists($body, '_METHOD')) { - $this->method = $this->filterMethod((string) $body->_METHOD); - } elseif (is_array($body) && isset($body['_METHOD'])) { - $this->method = $this->filterMethod((string) $body['_METHOD']); - } - - if ($this->getBody()->eof()) { - $this->getBody()->rewind(); - } - } - } - - return $this->method; - } - - /** - * Get the original HTTP method (ignore override). - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string - */ - public function getOriginalMethod() - { - return $this->originalMethod; - } - - /** - * Return an instance with the provided HTTP method. - * - * While HTTP method names are typically all uppercase characters, HTTP - * method names are case-sensitive and thus implementations SHOULD NOT - * modify the given string. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * changed request method. - * - * @param string $method Case-sensitive method - * - * @return self - * - * @throws \InvalidArgumentException for invalid HTTP methods - */ - public function withMethod($method) - { - $method = $this->filterMethod($method); - $clone = clone $this; - $clone->originalMethod = $method; - $clone->method = $method; - - return $clone; - } - - /** - * Validate the HTTP method. - * - * @param null|string $method - * - * @return null|string - * - * @throws \InvalidArgumentException on invalid HTTP method - */ - protected function filterMethod($method) - { - if ($method === null) { - return $method; - } - - if (!is_string($method)) { - throw new InvalidArgumentException(sprintf( - 'Unsupported HTTP method; must be a string, received %s', - (is_object($method) ? get_class($method) : gettype($method)) - )); - } - - $method = strtoupper($method); - if (!isset($this->validMethods[$method])) { - throw new InvalidArgumentException(sprintf( - 'Unsupported HTTP method "%s" provided', - $method - )); - } - - return $method; - } - - /** - * Does this request use a given method? - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $method HTTP method - * - * @return bool - */ - public function isMethod($method) - { - return $this->getMethod() === $method; - } - - /** - * Is this a GET request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isGet() - { - return $this->isMethod('GET'); - } - - /** - * Is this a POST request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isPost() - { - return $this->isMethod('POST'); - } - - /** - * Is this a PUT request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isPut() - { - return $this->isMethod('PUT'); - } - - /** - * Is this a PATCH request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isPatch() - { - return $this->isMethod('PATCH'); - } - - /** - * Is this a DELETE request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isDelete() - { - return $this->isMethod('DELETE'); - } - - /** - * Is this a HEAD request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isHead() - { - return $this->isMethod('HEAD'); - } - - /** - * Is this a OPTIONS request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isOptions() - { - return $this->isMethod('OPTIONS'); - } - - /** - * Is this an XHR request? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isXhr() - { - return $this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest'; - } - - /******************************************************************************* - * URI - ******************************************************************************/ - - /** - * Retrieves the message's request target. - * - * Retrieves the message's request-target either as it will appear (for - * clients), as it appeared at request (for servers), or as it was - * specified for the instance (see withRequestTarget()). - * - * In most cases, this will be the origin-form of the composed URI, - * unless a value was provided to the concrete implementation (see - * withRequestTarget() below). - * - * If no URI is available, and no request-target has been specifically - * provided, this method MUST return the string "/". - * - * @return string - */ - public function getRequestTarget() - { - if ($this->requestTarget) { - return $this->requestTarget; - } - - if ($this->uri === null) { - return '/'; - } - - $basePath = $this->uri->getBasePath(); - $path = $this->uri->getPath(); - $path = $basePath.'/'.ltrim($path, '/'); - - $query = $this->uri->getQuery(); - if ($query) { - $path .= '?'.$query; - } - $this->requestTarget = $path; - - return $this->requestTarget; - } - - /** - * Return an instance with the specific request-target. - * - * If the request needs a non-origin-form request-target — e.g., for - * specifying an absolute-form, authority-form, or asterisk-form — - * this method may be used to create an instance with the specified - * request-target, verbatim. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * changed request target. - * - * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various - * request-target forms allowed in request messages) - * - * @param mixed $requestTarget - * - * @return self - * - * @throws InvalidArgumentException if the request target is invalid - */ - public function withRequestTarget($requestTarget) - { - if (preg_match('#\s#', $requestTarget)) { - throw new InvalidArgumentException( - 'Invalid request target provided; must be a string and cannot contain whitespace' - ); - } - $clone = clone $this; - $clone->requestTarget = $requestTarget; - - return $clone; - } - - /** - * Retrieves the URI instance. - * - * This method MUST return a UriInterface instance. - * - * @link http://tools.ietf.org/html/rfc3986#section-4.3 - * - * @return UriInterface Returns a UriInterface instance - * representing the URI of the request - */ - public function getUri() - { - return $this->uri; - } - - /** - * Returns an instance with the provided URI. - * - * This method MUST update the Host header of the returned request by - * default if the URI contains a host component. If the URI does not - * contain a host component, any pre-existing Host header MUST be carried - * over to the returned request. - * - * You can opt-in to preserving the original state of the Host header by - * setting `$preserveHost` to `true`. When `$preserveHost` is set to - * `true`, this method interacts with the Host header in the following ways: - * - * - If the the Host header is missing or empty, and the new URI contains - * a host component, this method MUST update the Host header in the returned - * request. - * - If the Host header is missing or empty, and the new URI does not contain a - * host component, this method MUST NOT update the Host header in the returned - * request. - * - If a Host header is present and non-empty, this method MUST NOT update - * the Host header in the returned request. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new UriInterface instance. - * - * @link http://tools.ietf.org/html/rfc3986#section-4.3 - * - * @param UriInterface $uri New request URI to use - * @param bool $preserveHost Preserve the original state of the Host header - * - * @return self - */ - public function withUri(UriInterface $uri, $preserveHost = false) - { - $clone = clone $this; - $clone->uri = $uri; - - if (!$preserveHost) { - if ($uri->getHost() !== '') { - $clone->headers->set('Host', $uri->getHost()); - } - } else { - if ($this->uri->getHost() !== '' && (!$this->hasHeader('Host') || $this->getHeader('Host') === null)) { - $clone->headers->set('Host', $uri->getHost()); - } - } - - return $clone; - } - - /** - * Get request content type. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string|null The request content type, if known - */ - public function getContentType() - { - $result = $this->getHeader('Content-Type'); - - return $result ? $result[0] : null; - } - - /** - * Get request media type, if known. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string|null The request media type, minus content-type params - */ - public function getMediaType() - { - $contentType = $this->getContentType(); - if ($contentType) { - $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); - - return strtolower($contentTypeParts[0]); - } - - return; - } - - /** - * Get request media type params, if known. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return array - */ - public function getMediaTypeParams() - { - $contentType = $this->getContentType(); - $contentTypeParams = []; - if ($contentType) { - $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); - $contTypePartsLength = count($contentTypeParts); - for ($i = 1; $i < $contTypePartsLength; ++$i) { - $paramParts = explode('=', $contentTypeParts[$i]); - $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1]; - } - } - - return $contentTypeParams; - } - - /** - * Get request content character set, if known. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string|null - */ - public function getContentCharset() - { - $mediaTypeParams = $this->getMediaTypeParams(); - if (isset($mediaTypeParams['charset'])) { - return $mediaTypeParams['charset']; - } - - return; - } - - /** - * Get request content length, if known. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return int|null - */ - public function getContentLength() - { - $result = $this->headers->get('Content-Length'); - - return $result ? (int) $result[0] : null; - } - - /******************************************************************************* - * Cookies - ******************************************************************************/ - - /** - * Retrieve cookies. - * - * Retrieves cookies sent by the client to the server. - * - * The data MUST be compatible with the structure of the $_COOKIE - * superglobal. - * - * @return array - */ - public function getCookieParams() - { - return $this->cookies; - } - - /** - * Return an instance with the specified cookies. - * - * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST - * be compatible with the structure of $_COOKIE. Typically, this data will - * be injected at instantiation. - * - * This method MUST NOT update the related Cookie header of the request - * instance, nor related values in the server params. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated cookie values. - * - * @param array $cookies Array of key/value pairs representing cookies - * - * @return self - */ - public function withCookieParams(array $cookies) - { - $clone = clone $this; - $clone->cookies = $cookies; - - return $clone; - } - - /******************************************************************************* - * Query Params - ******************************************************************************/ - - /** - * Retrieve query string arguments. - * - * Retrieves the deserialized query string arguments, if any. - * - * Note: the query params might not be in sync with the URI or server - * params. If you need to ensure you are only getting the original - * values, you may need to parse the query string from `getUri()->getQuery()` - * or from the `QUERY_STRING` server param. - * - * @return array - */ - public function getQueryParams() - { - if ($this->queryParams) { - return $this->queryParams; - } - - if ($this->uri === null) { - return []; - } - - parse_str($this->uri->getQuery(), $this->queryParams); // <-- URL decodes data - - return $this->queryParams; - } - - /** - * Return an instance with the specified query string arguments. - * - * These values SHOULD remain immutable over the course of the incoming - * request. They MAY be injected during instantiation, such as from PHP's - * $_GET superglobal, or MAY be derived from some other value such as the - * URI. In cases where the arguments are parsed from the URI, the data - * MUST be compatible with what PHP's parse_str() would return for - * purposes of how duplicate query parameters are handled, and how nested - * sets are handled. - * - * Setting query string arguments MUST NOT change the URI stored by the - * request, nor the values in the server params. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated query string arguments. - * - * @param array $query Array of query string arguments, typically from - * $_GET - * - * @return self - */ - public function withQueryParams(array $query) - { - $clone = clone $this; - $clone->queryParams = $query; - - return $clone; - } - - /******************************************************************************* - * File Params - ******************************************************************************/ - - /** - * Retrieve normalized file upload data. - * - * This method returns upload metadata in a normalized tree, with each leaf - * an instance of Psr\Http\Message\UploadedFileInterface. - * - * These values MAY be prepared from $_FILES or the message body during - * instantiation, or MAY be injected via withUploadedFiles(). - * - * @return array An array tree of UploadedFileInterface instances; an empty - * array MUST be returned if no data is present - */ - public function getUploadedFiles() - { - return $this->uploadedFiles; - } - - /** - * Create a new instance with the specified uploaded files. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param array $uploadedFiles An array tree of UploadedFileInterface instances - * - * @return self - * - * @throws \InvalidArgumentException if an invalid structure is provided - */ - public function withUploadedFiles(array $uploadedFiles) - { - $clone = clone $this; - $clone->uploadedFiles = $uploadedFiles; - - return $clone; - } - - /******************************************************************************* - * Server Params - ******************************************************************************/ - - /** - * Retrieve server parameters. - * - * Retrieves data related to the incoming request environment, - * typically derived from PHP's $_SERVER superglobal. The data IS NOT - * REQUIRED to originate from $_SERVER. - * - * @return array - */ - public function getServerParams() - { - return $this->serverParams; - } - - /******************************************************************************* - * Attributes - ******************************************************************************/ - - /** - * Retrieve attributes derived from the request. - * - * The request "attributes" may be used to allow injection of any - * parameters derived from the request: e.g., the results of path - * match operations; the results of decrypting cookies; the results of - * deserializing non-form-encoded message bodies; etc. Attributes - * will be application and request specific, and CAN be mutable. - * - * @return array Attributes derived from the request - */ - public function getAttributes() - { - return $this->attributes->all(); - } - - /** - * Retrieve a single derived request attribute. - * - * Retrieves a single derived request attribute as described in - * getAttributes(). If the attribute has not been previously set, returns - * the default value as provided. - * - * This method obviates the need for a hasAttribute() method, as it allows - * specifying a default value to return if the attribute is not found. - * - * @see getAttributes() - * - * @param string $name The attribute name - * @param mixed $default Default value to return if the attribute does not exist - * - * @return mixed - */ - public function getAttribute($name, $default = null) - { - return $this->attributes->get($name, $default); - } - - /** - * Return an instance with the specified derived request attribute. - * - * This method allows setting a single derived request attribute as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated attribute. - * - * @see getAttributes() - * - * @param string $name The attribute name - * @param mixed $value The value of the attribute - * - * @return self - */ - public function withAttribute($name, $value) - { - $clone = clone $this; - $clone->attributes->set($name, $value); - - return $clone; - } - - /** - * Create a new instance with the specified derived request attributes. - * - * Note: This method is not part of the PSR-7 standard. - * - * This method allows setting all new derived request attributes as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return a new instance that has the - * updated attributes. - * - * @param array $attributes New attributes - * - * @return self - */ - public function withAttributes(array $attributes) - { - $clone = clone $this; - $clone->attributes = new GenericCollection($attributes); - - return $clone; - } - - /** - * Return an instance that removes the specified derived request attribute. - * - * This method allows removing a single derived request attribute as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the attribute. - * - * @see getAttributes() - * - * @param string $name The attribute name - * - * @return self - */ - public function withoutAttribute($name) - { - $clone = clone $this; - $clone->attributes->remove($name); - - return $clone; - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - /** - * Retrieve any parameters provided in the request body. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, this method MUST - * return the contents of $_POST. - * - * Otherwise, this method may return any results of deserializing - * the request body content; as parsing returns structured content, the - * potential types MUST be arrays or objects only. A null value indicates - * the absence of body content. - * - * @return null|array|object The deserialized body parameters, if any. - * These will typically be an array or object - * - * @throws RuntimeException if the request body media type parser returns an invalid value - */ - public function getParsedBody() - { - if ($this->bodyParsed) { - return $this->bodyParsed; - } - - if (!$this->body) { - return; - } - - $mediaType = $this->getMediaType(); - $body = (string) $this->getBody(); - - if (isset($this->bodyParsers[$mediaType]) === true) { - $parsed = $this->bodyParsers[$mediaType]($body); - - if (!is_null($parsed) && !is_object($parsed) && !is_array($parsed)) { - throw new RuntimeException('Request body media type parser return value must be an array, an object, or null'); - } - $this->bodyParsed = $parsed; - } - - return $this->bodyParsed; - } - - /** - * @var null|SerializableCollection the deserialized request content - */ - protected $cachedDeserBody = null; - - /** - * Deserialize the content passed as the request. - * - * Supported media types for the deserialization are: - * - 'application/json' - * - 'application/xml' - * - 'text/xml' - * - 'application/x-www-form-urlencoded' - * - 'multipart/form-data' - * - 'text/yaml' - * - 'text/x-yaml' - * - 'application/yaml' - * - 'application/x-yaml' - * - * @return SerializableCollection the deserialized body - * @throw RuntimeException the error preventing the deserialization - */ - public function getDeserializedBody() - { - if ($this->cachedDeserBody) { - return $this->cachedDeserBody; - } - - //get the media type that gives the serializator to be used - $mediaType = $this->getMediaType(); - - //get the body or something invalid - $body = ($this->body) ? (string) $this->getBody() : null; - - //get the serializer - $serializer = null; - - //this is what will be deserialized - $data = null; - switch ($mediaType) { - case 'application/json': - $data = $body; - $serializer = SerializableCollection::JSON; - break; - - case 'application/xml': - case 'text/xml': - $serializer = SerializableCollection::XML; - $data = $body; - break; - - case 'text/yaml': - case 'text/x-yaml': - case 'application/yaml': - case 'application/x-yaml': - $serializer = SerializableCollection::YAML; - $data = $body; - break; - - case 'application/x-www-form-urlencoded': - case 'multipart/form-data': - $data = $this->getParsedBody(); - break; - - default: - $data = []; - $serializer = null; - } - - //return the serialization result - try { - return $this->cachedDeserBody = SerializableCollection::deserialize($data, $serializer); - } catch (Gishiki\Algorithms\Collections\DeserializationException $ex) { - throw new RuntimeException('The HTTP request is malformed'); - } - } - - /** - * Return an instance with the specified body parameters. - * - * These MAY be injected during instantiation. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, use this method - * ONLY to inject the contents of $_POST. - * - * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of - * deserializing the request body content. Deserialization/parsing returns - * structured data, and, as such, this method ONLY accepts arrays or objects, - * or a null value if nothing was available to parse. - * - * As an example, if content negotiation determines that the request data - * is a JSON payload, this method could be used to create a request - * instance with the deserialized parameters. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param null|array|object $data The deserialized body data. This will - * typically be in an array or object - * - * @return self - * - * @throws \InvalidArgumentException if an unsupported argument type is - * provided - */ - public function withParsedBody($data) - { - if (!is_null($data) && !is_object($data) && !is_array($data)) { - throw new InvalidArgumentException('Parsed body value must be an array, an object, or null'); - } - - $clone = clone $this; - $clone->bodyParsed = $data; - - return $clone; - } - - /** - * Register media type parser. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $mediaType A HTTP media type (excluding content-type - * params) - * @param callable $callable A callable that returns parsed contents for - * media type - */ - public function registerMediaTypeParser($mediaType, callable $callable) - { - if ($callable instanceof Closure) { - $callable = $callable->bindTo($this); - } - $this->bodyParsers[(string) $mediaType] = $callable; - } - - /******************************************************************************* - * Parameters (e.g., POST and GET data) - ******************************************************************************/ - - /** - * Fetch request parameter value from body or query string (in that order). - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $key The parameter key - * @param string $default The default value - * - * @return mixed The parameter value - */ - public function getParam($key, $default = null) - { - $postParams = $this->getParsedBody(); - $getParams = $this->getQueryParams(); - $result = $default; - if (is_array($postParams) && isset($postParams[$key])) { - $result = $postParams[$key]; - } elseif (is_object($postParams) && property_exists($postParams, $key)) { - $result = $postParams->$key; - } elseif (isset($getParams[$key])) { - $result = $getParams[$key]; - } - - return $result; - } - - /** - * Fetch assocative array of body and query string parameters. - * - * @return array - */ - public function getParams() - { - $params = $this->getQueryParams(); - $postParams = $this->getParsedBody(); - if ($postParams) { - $params = array_merge($params, (array) $postParams); - } - - return $params; - } -} diff --git a/Gishiki/HttpKernel/RequestBody.php b/Gishiki/HttpKernel/RequestBody.php deleted file mode 100644 index 4ffa0f8d..00000000 --- a/Gishiki/HttpKernel/RequestBody.php +++ /dev/null @@ -1,29 +0,0 @@ - 'Continue', - 101 => 'Switching Protocols', - 102 => 'Processing', - //Successful 2xx - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 207 => 'Multi-Status', - 208 => 'Already Reported', - 226 => 'IM Used', - //Redirection 3xx - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 306 => '(Unused)', - 307 => 'Temporary Redirect', - 308 => 'Permanent Redirect', - //Client Error 4xx - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 418 => 'I\'m a teapot', - 422 => 'Unprocessable Entity', - 423 => 'Locked', - 424 => 'Failed Dependency', - 426 => 'Upgrade Required', - 428 => 'Precondition Required', - 429 => 'Too Many Requests', - 431 => 'Request Header Fields Too Large', - 451 => 'Unavailable For Legal Reasons', - //Server Error 5xx - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', - 506 => 'Variant Also Negotiates', - 507 => 'Insufficient Storage', - 508 => 'Loop Detected', - 510 => 'Not Extended', - 511 => 'Network Authentication Required', - ]; - - //Note: This method is not part of the PSR-7 standard. - public static function deriveFromRequest(Request $request) - { - //build an empty header - $headers = new Headers(); - - //analyze each possible acceptable encoding - foreach ($request->getHeader('Accept') as $acceptable) { - if (in_array($acceptable, [ - 'text/yaml', - 'text/x-yaml', - 'application/yaml', - 'application/x-yaml', - 'application/xml', - 'text/xml', - 'application/json', - ])) { - //select (and store) the content type - $headers->set('Content-Type', $acceptable); - - //the content-type has been selected - break; - } - } - - $headers->add('Date', gmstrftime('%a, %d %b %Y %H:%M:%S GMT', time())); - $headers->add('X-Powered-By', phpversion()); - $headers->add('X-Runtime', 'Gishiki'); - - //build and return the response - return new self(200, $headers); - } - - /** - * Sends the given HTTP response to the client. - * - * You MUST AVOID calls to this function! - * - * Note: This method is not part of the PSR-7 standard. - * - * @param int $chunkSize the size of each chunk of the response message - * - * @return int the numer of characters sent to the client - */ - public function send($chunkSize = 512) - { - $runningInCli = ((php_sapi_name() === 'cli') || (headers_sent())); - - //send the response HTTP header - - // Status - if (!$runningInCli) { - header('HTTP/'.$this->getProtocolVersion().' '. - $this->getStatusCode().' '. - $this->getReasonPhrase() - ); - } - - // Headers - foreach ($this->getHeaders() as $name => $values) { - foreach ($values as $value) { - //send to output only if this is not a test - if (!$runningInCli) { - header($name.': '.$value); - } - } - } - - //send the response HTTP message - $body = $this->getBody(); - $bodySize = $body->getSize(); - if ($body->isSeekable()) { - $body->rewind(); - } - - //get the content length - $contentLength = $this->getHeaderLine('Content-Length'); - //$contentLength = (!$contentLength) ? $body->getSize() : $contentLength; - - //send the result and count sent bytes - $sent = 0; - //$defContLength = isset($contentLength); - - $amountToRead = ($contentLength) ? intval($contentLength) : $bodySize; - while ($amountToRead > 0 && !$body->eof()) { - $data = $body->read(min($chunkSize, $amountToRead)); - - //send to output only if this is not a test - if (!$runningInCli) { - echo $data; - } - - $amountToRead -= strlen($data); - - //get the connection status - $connStatus = connection_status(); - if ($connStatus != CONNECTION_NORMAL) { - break; - } elseif ($connStatus == CONNECTION_NORMAL) { - $sent += strlen($data); - } - } - - //return the number of characters sent - return $sent; - } - - /** - * Create new HTTP response. - * - * @param int $status The response status code - * @param HeadersInterface|null $headers The response headers - * @param StreamInterface|null $body The response body - */ - public function __construct($status = 200, HeadersInterface $headers = null, StreamInterface $body = null) - { - $this->status = $this->filterStatus($status); - $this->headers = $headers ? $headers : new Headers(); - $this->body = $body ? $body : new Body(fopen('php://temp', 'r+')); - } - - /** - * This method is applied to the cloned object - * after PHP performs an initial shallow-copy. This - * method completes a deep-copy by creating new objects - * for the cloned object's internal reference pointers. - */ - public function __clone() - { - $this->headers = clone $this->headers; - $this->body = clone $this->body; - } - - /******************************************************************************* - * Status - ******************************************************************************/ - - /** - * Gets the response status code. - * - * The status code is a 3-digit integer result code of the server's attempt - * to understand and satisfy the request. - * - * @return int Status code - */ - public function getStatusCode() - { - return $this->status; - } - - /** - * Return an instance with the specified status code and, optionally, reason phrase. - * - * If no reason phrase is specified, implementations MAY choose to default - * to the RFC 7231 or IANA recommended reason phrase for the response's - * status code. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated status and reason phrase. - * - * @link http://tools.ietf.org/html/rfc7231#section-6 - * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml - * - * @param int $code The 3-digit integer result code to set - * @param string $reasonPhrase The reason phrase to use with the - * provided status code; if none is provided, implementations MAY - * use the defaults as suggested in the HTTP specification - * - * @return self - * - * @throws \InvalidArgumentException For invalid status code arguments - */ - public function withStatus($code, $reasonPhrase = '') - { - $code = $this->filterStatus($code); - - if (!is_string($reasonPhrase) && !method_exists($reasonPhrase, '__toString')) { - throw new InvalidArgumentException('ReasonPhrase must be a string'); - } - - $clone = clone $this; - $clone->status = $code; - if ((!$reasonPhrase) && (isset(static::$messages[$code]))) { - $reasonPhrase = static::$messages[$code]; - } - - if ($reasonPhrase === '') { - throw new InvalidArgumentException('ReasonPhrase must be supplied for this code'); - } - - $clone->reasonPhrase = $reasonPhrase; - - return $clone; - } - - /** - * Filter HTTP status code. - * - * @param int $status HTTP status code - * - * @return int - * - * @throws \InvalidArgumentException If an invalid HTTP status code is provided - */ - protected function filterStatus($status) - { - if (!is_integer($status) || $status < 100 || $status > 599) { - throw new InvalidArgumentException('Invalid HTTP status code'); - } - - return $status; - } - - /** - * Gets the response reason phrase associated with the status code. - * - * Because a reason phrase is not a required element in a response - * status line, the reason phrase value MAY be null. Implementations MAY - * choose to return the default RFC 7231 recommended reason phrase (or those - * listed in the IANA HTTP Status Code Registry) for the response's - * status code. - * - * @link http://tools.ietf.org/html/rfc7231#section-6 - * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml - * - * @return string Reason phrase; must return an empty string if none present - */ - public function getReasonPhrase() - { - //this is the "default" reason phrase - $reasonPhrase = ''; - - //try fetching the reason phrase - if ($this->reasonPhrase) { - return $this->reasonPhrase; - } elseif (isset(static::$messages[$this->status])) { - return static::$messages[$this->status]; - } - - return $reasonPhrase; - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - /** - * Write data to the response body. - * - * Note: This method is not part of the PSR-7 standard. - * - * Proxies to the underlying stream and writes the provided data to it. - * - * @param string $data - * - * @return self - */ - public function write($data) - { - //write data to the response body - $this->getBody()->write($data); - - return $this; - } - - /** - * Serialize the given serializable collection using the better serialization - * for the current 'Content-Type' header. - * - * The serialization result is written to the reponse body. - * - * If none or an invalid 'Content-Type' header is provided the default one - * will be used (which is 'application/json'). - * - * @param SerializableCollection $data the serializable collection - * - * @return Response the current response (after update) - * - * @throws \RuntimeException an error occurred during the serialization process - */ - public function setSerializedBody(SerializableCollection $data) - { - $format = SerializableCollection::JSON; - - //read the content-type and, if no content-type is given use the default one - $mediaTypes = $this->getHeader('Content-Type'); - $mediaType = (count($mediaTypes) > 0) ? $mediaTypes[0] : 'application/json'; - - //make sure to be using the correct content-type - $this->headers->set('Content-Type', $mediaType.';charset=utf8'); - - switch ($mediaType) { - case 'application/json': - case 'text/json': - $format = SerializableCollection::JSON; - break; - - case 'text/yaml': - case 'text/x-yaml': - case 'application/yaml': - case 'application/x-yaml': - $format = SerializableCollection::YAML; - break; - - case 'application/xml': - case 'text/xml': - $format = SerializableCollection::XML; - break; - - // entering this case is prevented by the first 3 lines of the function - default: - $format = SerializableCollection::JSON; - break; - } - - //serialize given data and write it on the response body - try { - $this->body->rewind(); - $this->body->write($data->serialize($format)); - } catch (Gishiki\Algorithms\Collections\SerializationException $ex) { - throw new \RuntimeException('The given content cannot be serialized'); - } - - return $this; - } - - /******************************************************************************* - * Response Helpers - ******************************************************************************/ - - /** - * Redirect. - * - * Note: This method is not part of the PSR-7 standard. - * - * This method prepares the response object to return an HTTP Redirect - * response to the client. - * - * @param string|UriInterface $url The redirect destination - * @param int $status The redirect HTTP status code - * - * @return self - */ - public function withRedirect($url, $status = 302) - { - return $this->withStatus($status)->withHeader('Location', (string) $url); - } - - /** - * Directly write the serialized json to the output. - * - * @deprecated deprecated since the first release (kept for Slim compatibility) - * - * Note: This method is not part of the PSR-7 standard. - * - * This method prepares the response object to return an HTTP Json - * response to the client from an associative array. - * - * This method is dangerous because no error checks are performed - * - * @param mixed $data The data - * @param int $status The HTTP status code - * @param int $encodingOptions Json encoding options - * - * @return self - */ - public function withJson($data, $status = 200, $encodingOptions = 0) - { - $body = $this->getBody(); - $body->rewind(); - $body->write(json_encode($data, $encodingOptions)); - - return $this->withStatus($status)->withHeader('Content-Type', 'application/json;charset=utf-8'); - } - - /** - * Is this response empty? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isEmpty() - { - return in_array($this->getStatusCode(), [204, 205, 304]); - } - - /** - * Is this response informational? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isInformational() - { - return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200; - } - - /** - * Is this response OK? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isOk() - { - return $this->getStatusCode() === 200; - } - - /** - * Is this response successful? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isSuccessful() - { - return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300; - } - - /** - * Is this response a redirect? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isRedirect() - { - return in_array($this->getStatusCode(), [301, 302, 303, 307]); - } - - /** - * Is this response a redirection? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isRedirection() - { - return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400; - } - - /** - * Is this response forbidden? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - * - * @api - */ - public function isForbidden() - { - return $this->getStatusCode() === 403; - } - - /** - * Is this response not Found? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isNotFound() - { - return $this->getStatusCode() === 404; - } - - /** - * Is this response a client error? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isClientError() - { - return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500; - } - - /** - * Is this response a server error? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - public function isServerError() - { - return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600; - } - - /** - * Convert response to string. - * - * Note: This method is not part of the PSR-7 standard. - * - * @return string the response complete of header and body (non-streamable) - */ - public function __toString() - { - $output = sprintf( - 'HTTP/%s %s %s', - $this->getProtocolVersion(), - $this->getStatusCode(), - $this->getReasonPhrase() - ); - $output .= PHP_EOL; - foreach (array_keys($this->getHeaders()) as $name) { - $output .= sprintf('%s: %s', $name, $this->getHeaderLine($name)).PHP_EOL; - } - $output .= PHP_EOL; - $output .= (string) $this->getBody(); - - return $output; - } -} diff --git a/Gishiki/HttpKernel/Stream.php b/Gishiki/HttpKernel/Stream.php deleted file mode 100644 index 98919bdd..00000000 --- a/Gishiki/HttpKernel/Stream.php +++ /dev/null @@ -1,413 +0,0 @@ - ['r', 'r+', 'w+', 'a+', 'x+', 'c+'], - 'writable' => ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'], - ]; - - /** - * The underlying stream resource. - * - * @var resource - */ - protected $stream; - - /** - * Stream metadata. - * - * @var array - */ - protected $meta; - - /** - * Is this stream readable? - * - * @var bool - */ - protected $readable; - - /** - * Is this stream writable? - * - * @var bool - */ - protected $writable; - - /** - * Is this stream seekable? - * - * @var bool - */ - protected $seekable; - - /** - * The size of the stream if known. - * - * @var null|int - */ - protected $size; - - /** - * Create a new Stream. - * - * @param resource $stream A PHP resource handle - * - * @throws InvalidArgumentException If argument is not a resource - */ - public function __construct($stream) - { - $this->attach($stream); - } - - /** - * Get stream metadata as an associative array or retrieve a specific key. - * - * The keys returned are identical to the keys returned from PHP's - * stream_get_meta_data() function. - * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * - * @param string $key Specific metadata to retrieve - * - * @return array|mixed|null Returns an associative array if no key is - * provided. Returns a specific key value if a key is provided and the - * value is found, or null if the key is not found - */ - public function getMetadata($key = null) - { - $this->meta = stream_get_meta_data($this->stream); - if (is_null($key) === true) { - return $this->meta; - } - - return isset($this->meta[$key]) ? $this->meta[$key] : null; - } - - /** - * Is a resource attached to this stream? - * - * Note: This method is not part of the PSR-7 standard. - * - * @return bool - */ - protected function isAttached() - { - return is_resource($this->stream); - } - - /** - * Attach new resource to this object. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param resource $newStream A PHP resource handle - * - * @throws InvalidArgumentException If argument is not a valid PHP resource - */ - protected function attach($newStream) - { - if (is_resource($newStream) === false) { - throw new InvalidArgumentException(__METHOD__.' argument must be a valid PHP resource'); - } - - if ($this->isAttached() === true) { - $this->detach(); - } - - $this->stream = $newStream; - } - - /** - * Separates any underlying resources from the stream. - * - * After the stream has been detached, the stream is in an unusable state. - * - * @return resource|null Underlying PHP stream, if any - */ - public function detach() - { - $oldResource = $this->stream; - $this->stream = null; - $this->meta = null; - $this->readable = null; - $this->writable = null; - $this->seekable = null; - $this->size = null; - - return $oldResource; - } - - /** - * Reads all data from the stream into a string, from the beginning to end. - * - * This method MUST attempt to seek to the beginning of the stream before - * reading data and read the stream until the end is reached. - * - * Warning: This could attempt to load a large amount of data into memory. - * - * This method MUST NOT raise an exception in order to conform with PHP's - * string casting operations. - * - * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring - * - * @return string - */ - public function __toString() - { - if (!$this->isAttached()) { - return ''; - } - - try { - $this->rewind(); - - return $this->getContents(); - } catch (RuntimeException $e) { - return ''; - } - } - - /** - * Closes the stream and any underlying resources. - */ - public function close() - { - if ($this->isAttached() === true) { - fclose($this->stream); - } - - $this->detach(); - } - - /** - * Get the size of the stream if known. - * - * @return int|null Returns the size in bytes if known, or null if unknown - */ - public function getSize() - { - if (!$this->size && $this->isAttached() === true) { - $stats = fstat($this->stream); - $this->size = isset($stats['size']) ? $stats['size'] : null; - } - - return $this->size; - } - - /** - * Returns the current position of the file read/write pointer. - * - * @return int Position of the file pointer - * - * @throws RuntimeException on error - */ - public function tell() - { - if (!$this->isAttached() || ($position = ftell($this->stream)) === false) { - throw new RuntimeException('Could not get the position of the pointer in stream'); - } - - return $position; - } - - /** - * Returns true if the stream is at the end of the stream. - * - * @return bool - */ - public function eof() - { - return $this->isAttached() ? feof($this->stream) : true; - } - - /** - * Returns whether or not the stream is readable. - * - * @return bool - */ - public function isReadable() - { - if ($this->readable === null) { - $this->readable = false; - if ($this->isAttached()) { - $meta = $this->getMetadata(); - foreach (self::$modes['readable'] as $mode) { - if (strpos($meta['mode'], $mode) === 0) { - $this->readable = true; - break; - } - } - } - } - - return $this->readable; - } - - /** - * Returns whether or not the stream is writable. - * - * @return bool - */ - public function isWritable() - { - if ($this->writable === null) { - $this->writable = false; - if ($this->isAttached()) { - $meta = $this->getMetadata(); - foreach (self::$modes['writable'] as $mode) { - if (strpos($meta['mode'], $mode) === 0) { - $this->writable = true; - break; - } - } - } - } - - return $this->writable; - } - - /** - * Returns whether or not the stream is seekable. - * - * @return bool - */ - public function isSeekable() - { - if ($this->seekable === null) { - $this->seekable = false; - if ($this->isAttached()) { - $meta = $this->getMetadata(); - $this->seekable = $meta['seekable']; - } - } - - return $this->seekable; - } - - /** - * Seek to a position in the stream. - * - * @link http://www.php.net/manual/en/function.fseek.php - * - * @param int $offset Stream offset - * @param int $whence Specifies how the cursor position will be calculated - * based on the seek offset. Valid values are identical to the built-in - * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to - * offset bytes SEEK_CUR: Set position to current location plus offset - * SEEK_END: Set position to end-of-stream plus offset - * - * @throws RuntimeException on failure - */ - public function seek($offset, $whence = SEEK_SET) - { - // Note that fseek returns 0 on success! - if (!$this->isSeekable() || fseek($this->stream, $offset, $whence) === -1) { - throw new RuntimeException('Could not seek in stream'); - } - } - - /** - * Seek to the beginning of the stream. - * - * If the stream is not seekable, this method will raise an exception; - * otherwise, it will perform a seek(0). - * - * @see seek() - * @link http://www.php.net/manual/en/function.fseek.php - * - * @throws RuntimeException on failure - */ - public function rewind() - { - if (!$this->isSeekable() || rewind($this->stream) === false) { - throw new RuntimeException('Could not rewind stream'); - } - } - - /** - * Read data from the stream. - * - * @param int $length Read up to $length bytes from the object and return - * them. Fewer than $length bytes may be returned if underlying stream - * call returns fewer bytes - * - * @return string Returns the data read from the stream, or an empty string - * if no bytes are available - * - * @throws RuntimeException if an error occurs - */ - public function read($length) - { - if (!$this->isReadable() || ($data = fread($this->stream, $length)) === false) { - throw new RuntimeException('Could not read from stream'); - } - - return $data; - } - - /** - * Write data to the stream. - * - * @param string $string The string that is to be written - * - * @return int Returns the number of bytes written to the stream - * - * @throws RuntimeException on failure - */ - public function write($string) - { - if (!$this->isWritable() || ($written = fwrite($this->stream, $string)) === false) { - throw new RuntimeException('Could not write to stream'); - } - - // reset size so that it will be recalculated on next call to getSize() - $this->size = null; - - return $written; - } - - /** - * Returns the remaining contents in a string. - * - * @return string - * - * @throws RuntimeException if unable to read or an error occurs while - * reading - */ - public function getContents() - { - if (!$this->isReadable() || ($contents = stream_get_contents($this->stream)) === false) { - throw new RuntimeException('Could not get contents of stream'); - } - - return $contents; - } -} diff --git a/Gishiki/HttpKernel/UploadedFile.php b/Gishiki/HttpKernel/UploadedFile.php deleted file mode 100644 index 077e8b93..00000000 --- a/Gishiki/HttpKernel/UploadedFile.php +++ /dev/null @@ -1,321 +0,0 @@ -has('slim.files')) { - return $env['slim.files']; - } elseif (isset($_FILES)) { - return static::parseUploadedFiles($_FILES); - } - - return []; - } - - /** - * Parse a non-normalized, i.e. $_FILES superglobal, tree of uploaded file data. - * - * @param array $uploadedFiles The non-normalized tree of uploaded file data - * - * @return array A normalized tree of UploadedFile instances - */ - private static function parseUploadedFiles(array $uploadedFiles) - { - $parsed = []; - foreach ($uploadedFiles as $field => $uploadedFile) { - if (!isset($uploadedFile['error'])) { - if (is_array($uploadedFile)) { - $parsed[$field] = static::parseUploadedFiles($uploadedFile); - } - continue; - } - $parsed[$field] = []; - if (!is_array($uploadedFile['error'])) { - $parsed[$field] = new static( - $uploadedFile['tmp_name'], - isset($uploadedFile['tmp_name']) ? $uploadedFile['name'] : null, - isset($uploadedFile['type']) ? $uploadedFile['type'] : null, - isset($uploadedFile['size']) ? $uploadedFile['size'] : null, - $uploadedFile['error'], - true - ); - } else { - foreach ($uploadedFile['error'] as $fileIdx => $error) { - $parsed[$field][] = new static( - $uploadedFile['tmp_name'][$fileIdx], - isset($uploadedFile['tmp_name']) ? $uploadedFile['name'][$fileIdx] : null, - isset($uploadedFile['type']) ? $uploadedFile['type'][$fileIdx] : null, - isset($uploadedFile['size']) ? $uploadedFile['size'][$fileIdx] : null, - $uploadedFile['error'][$fileIdx], - true - ); - } - } - } - - return $parsed; - } - - /** - * Construct a new UploadedFile instance. - * - * @param string $file The full path to the uploaded file provided by the client - * @param string|null $name The file name - * @param string|null $type The file media type - * @param int|null $size The file size in bytes - * @param int $error The UPLOAD_ERR_XXX code representing the status of the upload - * @param bool $sapi Indicates if the upload is in a SAPI environment - */ - public function __construct($file, $name = null, $type = null, $size = null, $error = UPLOAD_ERR_OK, $sapi = false) - { - $this->file = $file; - $this->name = $name; - $this->type = $type; - $this->size = $size; - $this->error = $error; - $this->sapi = $sapi; - } - - /** - * Retrieve a stream representing the uploaded file. - * - * This method MUST return a StreamInterface instance, representing the - * uploaded file. The purpose of this method is to allow utilizing native PHP - * stream functionality to manipulate the file upload, such as - * stream_copy_to_stream() (though the result will need to be decorated in a - * native PHP stream wrapper to work with such functions). - * - * If the moveTo() method has been called previously, this method MUST raise - * an exception. - * - * @return StreamInterface Stream representation of the uploaded file - * - * @throws \RuntimeException in cases when no stream is available or can be - * created - */ - public function getStream() - { - if ($this->moved) { - throw new \RuntimeException(sprintf('Uploaded file %1s has already been moved', $this->name)); - } - if ($this->stream === null) { - $this->stream = new Stream(fopen($this->file, 'r')); - } - - return $this->stream; - } - - /** - * Move the uploaded file to a new location. - * - * Use this method as an alternative to move_uploaded_file(). This method is - * guaranteed to work in both SAPI and non-SAPI environments. - * Implementations must determine which environment they are in, and use the - * appropriate method (move_uploaded_file(), rename(), or a stream - * operation) to perform the operation. - * - * $targetPath may be an absolute path, or a relative path. If it is a - * relative path, resolution should be the same as used by PHP's rename() - * function. - * - * The original file or stream MUST be removed on completion. - * - * If this method is called more than once, any subsequent calls MUST raise - * an exception. - * - * When used in an SAPI environment where $_FILES is populated, when writing - * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be - * used to ensure permissions and upload status are verified correctly. - * - * If you wish to move to a stream, use getStream(), as SAPI operations - * cannot guarantee writing to stream destinations. - * - * @see http://php.net/is_uploaded_file - * @see http://php.net/move_uploaded_file - * - * @param string $targetPath Path to which to move the uploaded file - * - * @throws InvalidArgumentException if the $path specified is invalid - * @throws RuntimeException on any error during the move operation, or on - * the second or subsequent call to the method - */ - public function moveTo($targetPath) - { - if ($this->moved) { - throw new RuntimeException('Uploaded file already moved'); - } - - if (!is_writable(dirname($targetPath))) { - throw new InvalidArgumentException('Upload target path is not writable'); - } - - $targetIsStream = strpos($targetPath, '://') > 0; - if ($targetIsStream) { - if (!copy($this->file, $targetPath)) { - throw new RuntimeException(sprintf('Error moving uploaded file %1s to %2s', $this->name, $targetPath)); - } - if (!unlink($this->file)) { - throw new RuntimeException(sprintf('Error removing uploaded file %1s', $this->name)); - } - } elseif ($this->sapi) { - if (!is_uploaded_file($this->file)) { - throw new RuntimeException(sprintf('%1s is not a valid uploaded file', $this->file)); - } - - if (!move_uploaded_file($this->file, $targetPath)) { - throw new RuntimeException(sprintf('Error moving uploaded file %1s to %2s', $this->name, $targetPath)); - } - } else { - if (!rename($this->file, $targetPath)) { - throw new RuntimeException(sprintf('Error moving uploaded file %1s to %2s', $this->name, $targetPath)); - } - } - - $this->moved = true; - } - - /** - * Retrieve the error associated with the uploaded file. - * - * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants. - * - * If the file was uploaded successfully, this method MUST return - * UPLOAD_ERR_OK. - * - * Implementations SHOULD return the value stored in the "error" key of - * the file in the $_FILES array. - * - * @see http://php.net/manual/en/features.file-upload.errors.php - * - * @return int One of PHP's UPLOAD_ERR_XXX constants - */ - public function getError() - { - return $this->error; - } - - /** - * Retrieve the filename sent by the client. - * - * Do not trust the value returned by this method. A client could send - * a malicious filename with the intention to corrupt or hack your - * application. - * - * Implementations SHOULD return the value stored in the "name" key of - * the file in the $_FILES array. - * - * @return string|null The filename sent by the client or null if none - * was provided - */ - public function getClientFilename() - { - return $this->name; - } - - /** - * Retrieve the media type sent by the client. - * - * Do not trust the value returned by this method. A client could send - * a malicious media type with the intention to corrupt or hack your - * application. - * - * Implementations SHOULD return the value stored in the "type" key of - * the file in the $_FILES array. - * - * @return string|null The media type sent by the client or null if none - * was provided - */ - public function getClientMediaType() - { - return $this->type; - } - - /** - * Retrieve the file size. - * - * Implementations SHOULD return the value stored in the "size" key of - * the file in the $_FILES array if available, as PHP calculates this based - * on the actual size transmitted. - * - * @return int|null The file size in bytes or null if unknown - */ - public function getSize() - { - return $this->size; - } -} diff --git a/Gishiki/HttpKernel/Uri.php b/Gishiki/HttpKernel/Uri.php deleted file mode 100644 index d3e28ff2..00000000 --- a/Gishiki/HttpKernel/Uri.php +++ /dev/null @@ -1,840 +0,0 @@ -scheme = $this->filterScheme($scheme); - $this->host = $host; - $this->port = $this->filterPort($port); - $this->path = empty($path) ? '/' : $this->filterPath($path); - $this->query = $this->filterQuery($query); - $this->fragment = $this->filterQuery($fragment); - $this->user = $user; - $this->password = $password; - } - - /** - * Create new Uri from string. - * - * @param string $uri Complete Uri string - * (i.e., https://user:pass@host:443/path?query) - * - * @return self - */ - public static function createFromString($uri) - { - if (!is_string($uri) && !method_exists($uri, '__toString')) { - throw new InvalidArgumentException('Uri must be a string'); - } - - $parts = parse_url($uri); - $scheme = isset($parts['scheme']) ? $parts['scheme'] : ''; - $user = isset($parts['user']) ? $parts['user'] : ''; - $pass = isset($parts['pass']) ? $parts['pass'] : ''; - $host = isset($parts['host']) ? $parts['host'] : ''; - $port = isset($parts['port']) ? $parts['port'] : null; - $path = isset($parts['path']) ? $parts['path'] : ''; - $query = isset($parts['query']) ? $parts['query'] : ''; - $fragment = isset($parts['fragment']) ? $parts['fragment'] : ''; - - return new static($scheme, $host, $port, $path, $query, $fragment, $user, $pass); - } - - /** - * Create new Uri from environment. - * - * @param Environment $env - * - * @return self - */ - public static function createFromEnvironment(Environment $env) - { - // Scheme - $isSecure = $env->get('HTTPS'); - $scheme = (empty($isSecure) || $isSecure === 'off') ? 'http' : 'https'; - - // Authority: Username and password - $username = $env->get('PHP_AUTH_USER', ''); - $password = $env->get('PHP_AUTH_PW', ''); - - // Authority: Host - if ($env->has('HTTP_HOST')) { - $host = $env->get('HTTP_HOST'); - } else { - $host = $env->get('SERVER_NAME'); - } - - // Authority: Port - $port = (int) $env->get('SERVER_PORT', 80); - if (preg_match('/^(\[[a-fA-F0-9:.]+\])(:\d+)?\z/', $host, $matches)) { - $host = $matches[1]; - - if ($matches[2]) { - $port = (int) substr($matches[2], 1); - } - } else { - $pos = strpos($host, ':'); - if ($pos !== false) { - $port = (int) substr($host, $pos + 1); - $host = strstr($host, ':', true); - } - } - - // Path - $requestScriptName = parse_url($env->get('SCRIPT_NAME'), PHP_URL_PATH); - $requestScriptDir = dirname($requestScriptName); - - // parse_url() requires a full URL. As we don't extract the domain name or scheme, - // we use a stand-in. - $requestUri = parse_url('http://example.com'.$env->get('REQUEST_URI'), PHP_URL_PATH); - - $basePath = ''; - $virtualPath = $requestUri; - if (stripos($requestUri, $requestScriptName) === 0) { - $basePath = $requestScriptName; - } elseif ($requestScriptDir !== '/' && stripos($requestUri, $requestScriptDir) === 0) { - $basePath = $requestScriptDir; - } - - if ($basePath) { - $virtualPath = ltrim(substr($requestUri, strlen($basePath)), '/'); - } - - // Query string - $queryString = $env->get('QUERY_STRING', ''); - - // Fragment - $fragment = ''; - - // Build Uri - $uri = new static($scheme, $host, $port, $virtualPath, $queryString, $fragment, $username, $password); - if ($basePath) { - $uri = $uri->withBasePath($basePath); - } - - return $uri; - } - - /******************************************************************************** - * Scheme - *******************************************************************************/ - - /** - * Retrieve the scheme component of the URI. - * - * If no scheme is present, this method MUST return an empty string. - * - * The value returned MUST be normalized to lowercase, per RFC 3986 - * Section 3.1. - * - * The trailing ":" character is not part of the scheme and MUST NOT be - * added. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.1 - * - * @return string The URI scheme - */ - public function getScheme() - { - return $this->scheme; - } - - /** - * Return an instance with the specified scheme. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified scheme. - * - * Implementations MUST support the schemes "http" and "https" case - * insensitively, and MAY accommodate other schemes if required. - * - * An empty scheme is equivalent to removing the scheme. - * - * @param string $scheme The scheme to use with the new instance - * - * @return self A new instance with the specified scheme - * - * @throws \InvalidArgumentException for invalid or unsupported schemes - */ - public function withScheme($scheme) - { - $scheme = $this->filterScheme($scheme); - $clone = clone $this; - $clone->scheme = $scheme; - - return $clone; - } - - /** - * Filter Uri scheme. - * - * @param string $scheme Raw Uri scheme - * - * @return string - * - * @throws InvalidArgumentException If the Uri scheme is not a string - * @throws InvalidArgumentException If Uri scheme is not "", "https", or "http" - */ - protected function filterScheme($scheme) - { - static $valid = [ - '' => true, - 'https' => true, - 'http' => true, - ]; - - if (!is_string($scheme) && !method_exists($scheme, '__toString')) { - throw new InvalidArgumentException('Uri scheme must be a string'); - } - - $scheme = str_replace('://', '', strtolower((string) $scheme)); - if (!isset($valid[$scheme])) { - throw new InvalidArgumentException('Uri scheme must be one of: "", "https", "http"'); - } - - return $scheme; - } - - /******************************************************************************** - * Authority - *******************************************************************************/ - - /** - * Retrieve the authority component of the URI. - * - * If no authority information is present, this method MUST return an empty - * string. - * - * The authority syntax of the URI is: - * - *
-     * [user-info@]host[:port]
-     * 
- * - * If the port component is not set or is the standard port for the current - * scheme, it SHOULD NOT be included. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.2 - * - * @return string The URI authority, in "[user-info@]host[:port]" format - */ - public function getAuthority() - { - $userInfo = $this->getUserInfo(); - $host = $this->getHost(); - $port = $this->getPort(); - - return ($userInfo ? $userInfo.'@' : '').$host.($port !== null ? ':'.$port : ''); - } - - /** - * Retrieve the user information component of the URI. - * - * If no user information is present, this method MUST return an empty - * string. - * - * If a user is present in the URI, this will return that value; - * additionally, if the password is also present, it will be appended to the - * user value, with a colon (":") separating the values. - * - * The trailing "@" character is not part of the user information and MUST - * NOT be added. - * - * @return string The URI user information, in "username[:password]" format - */ - public function getUserInfo() - { - return $this->user.($this->password ? ':'.$this->password : ''); - } - - /** - * Return an instance with the specified user information. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified user information. - * - * Password is optional, but the user information MUST include the - * user; an empty string for the user is equivalent to removing user - * information. - * - * @param string $user The user name to use for authority - * @param null|string $password The password associated with $user - * - * @return self A new instance with the specified user information - */ - public function withUserInfo($user, $password = null) - { - $clone = clone $this; - $clone->user = $user; - $clone->password = $password ? $password : ''; - - return $clone; - } - - /** - * Retrieve the host component of the URI. - * - * If no host is present, this method MUST return an empty string. - * - * The value returned MUST be normalized to lowercase, per RFC 3986 - * Section 3.2.2. - * - * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 - * - * @return string The URI host - */ - public function getHost() - { - return $this->host; - } - - /** - * Return an instance with the specified host. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified host. - * - * An empty host value is equivalent to removing the host. - * - * @param string $host The hostname to use with the new instance - * - * @return self A new instance with the specified host - * - * @throws \InvalidArgumentException for invalid hostnames - */ - public function withHost($host) - { - $clone = clone $this; - $clone->host = $host; - - return $clone; - } - - /** - * Retrieve the port component of the URI. - * - * If a port is present, and it is non-standard for the current scheme, - * this method MUST return it as an integer. If the port is the standard port - * used with the current scheme, this method SHOULD return null. - * - * If no port is present, and no scheme is present, this method MUST return - * a null value. - * - * If no port is present, but a scheme is present, this method MAY return - * the standard port for that scheme, but SHOULD return null. - * - * @return null|int The URI port - */ - public function getPort() - { - return $this->port && !$this->hasStandardPort() ? $this->port : null; - } - - /** - * Return an instance with the specified port. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified port. - * - * Implementations MUST raise an exception for ports outside the - * established TCP and UDP port ranges. - * - * A null value provided for the port is equivalent to removing the port - * information. - * - * @param null|int $port The port to use with the new instance; a null value - * removes the port information - * - * @return self A new instance with the specified port - * - * @throws \InvalidArgumentException for invalid ports - */ - public function withPort($port) - { - $port = $this->filterPort($port); - $clone = clone $this; - $clone->port = $port; - - return $clone; - } - - /** - * Does this Uri use a standard port? - * - * @return bool - */ - protected function hasStandardPort() - { - return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443); - } - - /** - * Filter Uri port. - * - * @param null|int $port The Uri port number - * - * @return null|int - * - * @throws InvalidArgumentException If the port is invalid - */ - protected function filterPort($port) - { - if (is_null($port) || (is_integer($port) && ($port >= 1 && $port <= 65535))) { - return $port; - } - - throw new InvalidArgumentException('Uri port must be null or an integer between 1 and 65535 (inclusive)'); - } - - /******************************************************************************** - * Path - *******************************************************************************/ - - /** - * Retrieve the path component of the URI. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * Normally, the empty path "" and absolute path "/" are considered equal as - * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically - * do this normalization because in contexts with a trimmed base path, e.g. - * the front controller, this difference becomes significant. It's the task - * of the user to handle both "" and "/". - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.3. - * - * As an example, if the value should include a slash ("/") not intended as - * delimiter between path segments, that value MUST be passed in encoded - * form (e.g., "%2F") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.3 - * - * @return string The URI path - */ - public function getPath() - { - return $this->path; - } - - /** - * Return an instance with the specified path. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified path. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * If the path is intended to be domain-relative rather than path relative then - * it must begin with a slash ("/"). Paths not starting with a slash ("/") - * are assumed to be relative to some base path known to the application or - * consumer. - * - * Users can provide both encoded and decoded path characters. - * Implementations ensure the correct encoding as outlined in getPath(). - * - * @param string $path The path to use with the new instance - * - * @return self A new instance with the specified path - * - * @throws \InvalidArgumentException for invalid paths - */ - public function withPath($path) - { - if (!is_string($path)) { - throw new InvalidArgumentException('Uri path must be a string'); - } - - $clone = clone $this; - $clone->path = $this->filterPath($path); - - // if the path is absolute, then clear basePath - if (substr($path, 0, 1) == '/') { - $clone->basePath = ''; - } - - return $clone; - } - - /** - * Retrieve the base path segment of the URI. - * - * Note: This method is not part of the PSR-7 standard. - * - * This method MUST return a string; if no path is present it MUST return - * an empty string. - * - * @return string The base path segment of the URI - */ - public function getBasePath() - { - return $this->basePath; - } - - /** - * Set base path. - * - * Note: This method is not part of the PSR-7 standard. - * - * @param string $basePath - * - * @return self - */ - public function withBasePath($basePath) - { - if (!is_string($basePath)) { - throw new InvalidArgumentException('Uri path must be a string'); - } - if (!empty($basePath)) { - $basePath = '/'.trim($basePath, '/'); // <-- Trim on both sides - } - $clone = clone $this; - - if ($basePath !== '/') { - $clone->basePath = $this->filterPath($basePath); - } - - return $clone; - } - - /** - * Filter Uri path. - * - * This method percent-encodes all reserved - * characters in the provided path string. This method - * will NOT double-encode characters that are already - * percent-encoded. - * - * @param string $path The raw uri path - * - * @return string The RFC 3986 percent-encoded uri path - * - * @link http://www.faqs.org/rfcs/rfc3986.html - */ - protected function filterPath($path) - { - return preg_replace_callback( - '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', - function ($match) { - return rawurlencode($match[0]); - }, - $path - ); - } - - /******************************************************************************** - * Query - *******************************************************************************/ - - /** - * Retrieve the query string of the URI. - * - * If no query string is present, this method MUST return an empty string. - * - * The leading "?" character is not part of the query and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.4. - * - * As an example, if a value in a key/value pair of the query string should - * include an ampersand ("&") not intended as a delimiter between values, - * that value MUST be passed in encoded form (e.g., "%26") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.4 - * - * @return string The URI query string - */ - public function getQuery() - { - return $this->query; - } - - /** - * Return an instance with the specified query string. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified query string. - * - * Users can provide both encoded and decoded query characters. - * Implementations ensure the correct encoding as outlined in getQuery(). - * - * An empty query string value is equivalent to removing the query string. - * - * @param string $query The query string to use with the new instance - * - * @return self A new instance with the specified query string - * - * @throws \InvalidArgumentException for invalid query strings - */ - public function withQuery($query) - { - if (!is_string($query) && !method_exists($query, '__toString')) { - throw new InvalidArgumentException('Uri query must be a string'); - } - $query = ltrim((string) $query, '?'); - $clone = clone $this; - $clone->query = $this->filterQuery($query); - - return $clone; - } - - /** - * Filters the query string or fragment of a URI. - * - * @param string $query The raw uri query string - * - * @return string The percent-encoded query string - */ - protected function filterQuery($query) - { - return preg_replace_callback( - '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', - function ($match) { - return rawurlencode($match[0]); - }, - $query - ); - } - - /******************************************************************************** - * Fragment - *******************************************************************************/ - - /** - * Retrieve the fragment component of the URI. - * - * If no fragment is present, this method MUST return an empty string. - * - * The leading "#" character is not part of the fragment and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.5. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.5 - * - * @return string The URI fragment - */ - public function getFragment() - { - return $this->fragment; - } - - /** - * Return an instance with the specified URI fragment. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified URI fragment. - * - * Users can provide both encoded and decoded fragment characters. - * Implementations ensure the correct encoding as outlined in getFragment(). - * - * An empty fragment value is equivalent to removing the fragment. - * - * @param string $fragment The fragment to use with the new instance - * - * @return self A new instance with the specified fragment - */ - public function withFragment($fragment) - { - if (!is_string($fragment) && !method_exists($fragment, '__toString')) { - throw new InvalidArgumentException('Uri fragment must be a string'); - } - $fragment = ltrim((string) $fragment, '#'); - $clone = clone $this; - $clone->fragment = $this->filterQuery($fragment); - - return $clone; - } - - /******************************************************************************** - * Helpers - *******************************************************************************/ - - /** - * Return the string representation as a URI reference. - * - * Depending on which components of the URI are present, the resulting - * string is either a full URI or relative reference according to RFC 3986, - * Section 4.1. The method concatenates the various components of the URI, - * using the appropriate delimiters: - * - * - If a scheme is present, it MUST be suffixed by ":". - * - If an authority is present, it MUST be prefixed by "//". - * - The path can be concatenated without delimiters. But there are two - * cases where the path has to be adjusted to make the URI reference - * valid as PHP does not allow to throw an exception in __toString(): - * - If the path is rootless and an authority is present, the path MUST - * be prefixed by "/". - * - If the path is starting with more than one "/" and no authority is - * present, the starting slashes MUST be reduced to one. - * - If a query is present, it MUST be prefixed by "?". - * - If a fragment is present, it MUST be prefixed by "#". - * - * @see http://tools.ietf.org/html/rfc3986#section-4.1 - * - * @return string - */ - public function __toString() - { - $scheme = $this->getScheme(); - $authority = $this->getAuthority(); - $basePath = $this->getBasePath(); - $path = $this->getPath(); - $query = $this->getQuery(); - $fragment = $this->getFragment(); - - $path = $basePath.'/'.ltrim($path, '/'); - - return ($scheme ? $scheme.':' : '') - .($authority ? '//'.$authority : '') - .$path - .($query ? '?'.$query : '') - .($fragment ? '#'.$fragment : ''); - } - - /** - * Return the fully qualified base URL. - * - * Note that this method never includes a trailing / - * - * This method is not part of PSR-7. - * - * @return string - */ - public function getBaseUrl() - { - $scheme = $this->getScheme(); - $authority = $this->getAuthority(); - $basePath = $this->getBasePath(); - - if ($authority && substr($basePath, 0, 1) !== '/') { - $basePath = $basePath.'/'.$basePath; - } - - return ($scheme ? $scheme.':' : '') - .($authority ? '//'.$authority : '') - .rtrim($basePath, '/'); - } -} diff --git a/Gishiki/Logging/LoggerManager.php b/Gishiki/Logging/LoggerManager.php index 4ad7e94d..4e3e95da 100755 --- a/Gishiki/Logging/LoggerManager.php +++ b/Gishiki/Logging/LoggerManager.php @@ -1,144 +1,144 @@ - - */ -abstract class LoggerManager -{ - /** - * @var array the list of logger instances as an associative array - */ - protected static $connections = []; - - /** - * @var string the sha1 hash of the default connection - */ - protected static $hashOfDefault = null; - - /** - * Set as default the PSR-3 logger instance with the given name - * - * @param string $name the name of the logger instance to be set as the default one - * @throws \InvalidArgumentException invalid name or inexistent logger instance - * @return \Monolog\Logger the logger instance - */ - public static function setDefault($name) - { - //check for the logger name - if ((!is_string($name)) || (strlen($name) <= 0)) { - throw new \InvalidArgumentException('The logger name must be given as a valid non-empty string'); - } - - if (!array_key_exists(sha1($name), self::$connections)) { - throw new \InvalidArgumentException('The given logger name is not valid'); - } - - self::$hashOfDefault = sha1($name); - - //return the selected connection - return self::$connections[sha1($name)]; - } - - /** - * Create a new logger instance. - * - * @param string $name the connection name - * @param array $details an array containing sub-arrays of connection details - * @throws \InvalidArgumentException invalid name or connection details - * @return \Monolog\Logger the new logger instance - */ - public static function connect($name, array $details) - { - //check for the logger name - if ((!is_string($name)) || (strlen($name) <= 0)) { - throw new \InvalidArgumentException('The logger name must be given as a valid non-empty string'); - } - - //create the new logger instance - self::$connections[sha1($name)] = new Logger($name); - - foreach ($details as $handler) { - $handlerCollection = new GenericCollection($handler); - - if ((!$handlerCollection->has('class')) || (!$handlerCollection->has('connection'))) { - throw new \InvalidArgumentException('The logger configuration is not fully-qualified'); - } - - try { - $adapterClassName = (strpos($handlerCollection->get('class'), "\\") === false) ? - 'Monolog\\Handler\\'.$handlerCollection->get('class') : - $handlerCollection->get('class'); - - //reflect the adapter - $reflectedAdapter = new \ReflectionClass($adapterClassName); - - //bind the handler to the current logger - self::$connections[sha1($name)]->pushHandler( - $reflectedAdapter->newInstanceArgs($handlerCollection->get('connection')) - ); - } catch (\ReflectionException $ex) { - throw new \InvalidArgumentException('The given connection requires an unknown class'); - } - } - - //return the newly created logger - return self::$connections[sha1($name)]; - } - - /** - * Retrieve the PSR-3 logger instance with the given name - * - * @param string|null $name the name of the logger instance or NULL for the default one - * @throws \InvalidArgumentException invalid name or inexistent logger instance - * @return \Monolog\Logger the logger instance - */ - public static function retrieve($name = null) - { - if (!is_null($name) && (!is_string($name))) { - throw new \InvalidArgumentException('The logger instance to be retrieved must be given as a valid, non-empty string or NULL'); - } - - //is the default one requested? - if (is_null($name)) { - if (is_null(self::$hashOfDefault)) { - throw new \InvalidArgumentException('A default logger instance doesn\'t exists'); - } - - return self::$connections[self::$hashOfDefault]; - } - - //check for bad logger name - if ((!is_string($name)) || (strlen($name) <= 0)) { - throw new \InvalidArgumentException('The logger name must be given as a valid non-empty string'); - } - - if (!array_key_exists(sha1($name), self::$connections)) { - throw new \InvalidArgumentException('The given logger name is not valid'); - } - - //return the requested connection - return self::$connections[sha1($name)]; - } -} + + */ +abstract class LoggerManager +{ + /** + * @var array the list of logger instances as an associative array + */ + protected static $connections = []; + + /** + * @var string the sha1 hash of the default connection + */ + protected static $hashOfDefault = null; + + /** + * Set as default the PSR-3 logger instance with the given name + * + * @param string $name the name of the logger instance to be set as the default one + * @throws \InvalidArgumentException invalid name or inexistent logger instance + * @return \Monolog\Logger the logger instance + */ + public static function setDefault($name) + { + //check for the logger name + if ((!is_string($name)) || (strlen($name) <= 0)) { + throw new \InvalidArgumentException('The logger name must be given as a valid non-empty string'); + } + + if (!array_key_exists(sha1($name), self::$connections)) { + throw new \InvalidArgumentException('The given logger name is not valid'); + } + + self::$hashOfDefault = sha1($name); + + //return the selected connection + return self::$connections[sha1($name)]; + } + + /** + * Create a new logger instance. + * + * @param string $name the connection name + * @param array $details an array containing sub-arrays of connection details + * @throws \InvalidArgumentException invalid name or connection details + * @return \Monolog\Logger the new logger instance + */ + public static function connect($name, array $details) + { + //check for the logger name + if ((!is_string($name)) || (strlen($name) <= 0)) { + throw new \InvalidArgumentException('The logger name must be given as a valid non-empty string'); + } + + //create the new logger instance + self::$connections[sha1($name)] = new Logger($name); + + foreach ($details as $handler) { + $handlerCollection = new GenericCollection($handler); + + if ((!$handlerCollection->has('class')) || (!$handlerCollection->has('connection'))) { + throw new \InvalidArgumentException('The logger configuration is not fully-qualified'); + } + + try { + $adapterClassName = (strpos($handlerCollection->get('class'), "\\") === false) ? + 'Monolog\\Handler\\'.$handlerCollection->get('class') : + $handlerCollection->get('class'); + + //reflect the adapter + $reflectedAdapter = new \ReflectionClass($adapterClassName); + + //bind the handler to the current logger + self::$connections[sha1($name)]->pushHandler( + $reflectedAdapter->newInstanceArgs($handlerCollection->get('connection')) + ); + } catch (\ReflectionException $ex) { + throw new \InvalidArgumentException('The given connection requires an unknown class'); + } + } + + //return the newly created logger + return self::$connections[sha1($name)]; + } + + /** + * Retrieve the PSR-3 logger instance with the given name + * + * @param string|null $name the name of the logger instance or NULL for the default one + * @throws \InvalidArgumentException invalid name or inexistent logger instance + * @return \Monolog\Logger the logger instance + */ + public static function retrieve($name = null) + { + if (!is_null($name) && (!is_string($name))) { + throw new \InvalidArgumentException('The logger instance to be retrieved must be given as a valid, non-empty string or NULL'); + } + + //is the default one requested? + if (is_null($name)) { + if (is_null(self::$hashOfDefault)) { + throw new \InvalidArgumentException('A default logger instance doesn\'t exists'); + } + + return self::$connections[self::$hashOfDefault]; + } + + //check for bad logger name + if ((!is_string($name)) || (strlen($name) <= 0)) { + throw new \InvalidArgumentException('The logger name must be given as a valid non-empty string'); + } + + if (!array_key_exists(sha1($name), self::$connections)) { + throw new \InvalidArgumentException('The given logger name is not valid'); + } + + //return the requested connection + return self::$connections[sha1($name)]; + } +} diff --git a/Gishiki/Security/Encryption/Asymmetric/AsymmetricException.php b/Gishiki/Security/Encryption/Asymmetric/AsymmetricException.php index cf2be597..55469bac 100644 --- a/Gishiki/Security/Encryption/Asymmetric/AsymmetricException.php +++ b/Gishiki/Security/Encryption/Asymmetric/AsymmetricException.php @@ -1,38 +1,38 @@ - - */ -final class AsymmetricException extends \Gishiki\Core\Exception -{ - /** - * Create the asymmetric-related exception. - * - * @param string $message the error message - * @param int $errorCode the asymmetric encryption error code - */ - public function __construct($message, $errorCode) - { - parent::__construct($message, $errorCode); - } -} + + */ +final class AsymmetricException extends \Gishiki\Core\Exception +{ + /** + * Create the asymmetric-related exception. + * + * @param string $message the error message + * @param int $errorCode the asymmetric encryption error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } +} diff --git a/Gishiki/Security/Encryption/Asymmetric/Cryptography.php b/Gishiki/Security/Encryption/Asymmetric/Cryptography.php index dd024953..c332985b 100644 --- a/Gishiki/Security/Encryption/Asymmetric/Cryptography.php +++ b/Gishiki/Security/Encryption/Asymmetric/Cryptography.php @@ -1,395 +1,395 @@ - - */ -abstract class Cryptography -{ - /** - * Encrypt the given message using the given private key. - * - * You will need the public key to decrypt the encrypted content. - * - * You can decrypt an encrypted content with the decrypt() function. - * - * An example of usage can be: - * - * - * $default_privkey = new PrivateKey(); - * $encrypted_message = Cryptography::encrypt($default_privkey, "this is my important message from my beloved half"); - * - * echo "Take good care of this and give it to my GF: " . $encrypted_message; - * - * - * @param PrivateKey $key the private key to be used to encrypt the plain message - * @param string $message the message to be encrypted - * - * @return string the encrypted message - * - * @throws \InvalidArgumentException the plain message is not a string - * @throws AsymmetricException an error occurred while encrypting the given message - */ - public static function encrypt(PrivateKey &$key, $message) - { - //check the plain message type - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The plain message to be encrypted must be given as a non-empty string'); - } - - //check for the private key - if (!$key->isLoaded()) { - throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); - } - - //get the key in native format and its length - $managedKey = $key(); - - //encrypt the complete message - $completeMsg = ''; - foreach (str_split($message, $managedKey['byteLength'] / 2) as $msgChunk) { - //the encrypted message - $encryptedChunk = null; - - //encrypt the current message and check for failure - if (!openssl_private_encrypt($msgChunk, $encryptedChunk, $managedKey['key'], OPENSSL_PKCS1_PADDING)) { - throw new AsymmetricException("The message encryption can't be accomplished due to an unknown error", 4); - } - - //join the current encrypted chunk to the encrypted message - $completeMsg .= (string) $encryptedChunk; - } - - //return the encrypted message base64-encoded - return base64_encode($completeMsg); - } - - /** - * Encrypt the given message using the given public key. - * - * You will need the private key to decrypt the encrypted content. - * - * You can decrypt an encrypted content with the decryptReverse() function. - * - * An example of usage can be: - * - * - * $default_pubkey = new PublicKey(); - * $encrypted_message = Cryptography::encryptReverse($default_pubkey, "this is my important message from my beloved half"); - * - * echo "Take good care of this and give it to my GF: " . $encrypted_message; - * - * - * @param PublicKey $key the public key to be used to encrypt the plain message - * @param string $message the message to be encrypted - * - * @return string the encrypted message - * - * @throws \InvalidArgumentException the plain message is not a string - * @throws AsymmetricException an error occurred while encrypting the given message - */ - public static function encryptReverse(PublicKey &$key, $message) - { - //check the plain message type - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The plain message to be encrypted must be given as a non-empty string'); - } - - //check for the public key - if (!$key->isLoaded()) { - throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); - } - - //get the key in native format and its length - $managedKey = $key(); - - //encrypt the complete message - $completeMsg = ''; - foreach (str_split($message, $managedKey['byteLength'] / 2) as $msgChunk) { - //the encrypted message - $encryptedChunk = null; - - //encrypt the current message and check for failure - if (!openssl_public_encrypt($msgChunk, $encryptedChunk, $managedKey['key'], OPENSSL_PKCS1_PADDING)) { - throw new AsymmetricException("The message encryption can't be accomplished due to an unknown error", 15); - } - - //join the current encrypted chunk to the encrypted message - $completeMsg .= (string) $encryptedChunk; - } - - //return the encrypted message base64-encoded - return base64_encode($completeMsg); - } - - /** - * Decrypt an encrypted message created using the encrypt() function. - * - * The used public key must be decoupled from the private key used to generate the message. - * - * En example usage can be: - * - * - * //load the default public key - * $default_pubkey = new PublicKey(); - * - * //this is a message encrypted with the application's default key - * $encrypted_message = "..."; - * - * //decrypt the message - * $plain_message = Cryptography::decrypt($default_pubkey, $encrypted_message); - * - * echo $encrypted_message; - * - * - * - * @param PublicKey $key the public key to be used to decrypt the encrypted message - * @param string $encryptedMsg the message to be decrypted - * - * @return string the encrypted message - * - * @throws \InvalidArgumentException the encrypted message is not a string - * @throws AsymmetricException an error occurred while decrypting the given message - */ - public static function decrypt(PublicKey &$key, $encryptedMsg) - { - //check the encrypted message type - if ((!is_string($encryptedMsg)) || (strlen($encryptedMsg) <= 0)) { - throw new \InvalidArgumentException('The encrypted message to be decrypted must be given as a non-empty string'); - } - - //check for the public key - if (!$key->isLoaded()) { - throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); - } - - //base64-decode of the encrypted message - $completeMsg = base64_decode($encryptedMsg); - - //get the key in native format and its length - $managedKey = $key(); - - //encrypt the complete message - $message = ''; - foreach (str_split($completeMsg, $managedKey['byteLength']) as $encryptedChunk) { - $msgChunk = null; - - //decrypt the current chunk of encrypted message - if (!openssl_public_decrypt($encryptedChunk, $msgChunk, $managedKey['key'], OPENSSL_PKCS1_PADDING)) { - throw new AsymmetricException("The message decryption can't be accomplished due to an unknown error", 5); - } - - //join the current unencrypted chunk to the complete message - $message .= (string) $msgChunk; - } - - //return the decrypted message - return $message; - } - - /** - * Decrypt an encrypted message created using the encryptReverse() function. - * - * The used private key must be must be the corresponding public key used to generate the message. - * - * En example usage can be: - * - * - * //load the default private key - * $default_pubkey = new PrivateKey(); - * - * //this is a message encrypted with the application's default key - * $encrypted_message = "..."; - * - * //decrypt the message - * $plain_message = Cryptography::decryptReverse($default_privkey, $encrypted_message); - * - * echo $encrypted_message; - * - * - * - * @param PrivateKey $key the public key to be used to decrypt the encrypted message - * @param string $encryptedMsg the message to be decrypted - * - * @return string the encrypted message - * - * @throws \InvalidArgumentException the encrypted message is not a string - * @throws AsymmetricException an error occurred while decrypting the given message - */ - public static function decryptReverse(PrivateKey &$key, $encryptedMsg) - { - //check the encrypted message type - if ((!is_string($encryptedMsg)) || (strlen($encryptedMsg) <= 0)) { - throw new \InvalidArgumentException('The encrypted message to be decrypted must be given as a non-empty string'); - } - - //check for the private key - if (!$key->isLoaded()) { - throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); - } - - //base64-decode of the encrypted message - $completeMsg = base64_decode($encryptedMsg); - - //get the key in native format and its length - $managedKey = $key(); - - //check if the message can be decrypted - /*if (($completeMsg % $managedKey['byteLength']) != 0) { - throw new AsymmetricException('The message decryption cannot take place because the given message is malformed', 6); - }*/ - - //encrypt the complete message - $message = ''; - foreach (str_split($completeMsg, $managedKey['byteLength']) as $encryptedChunk) { - $msgChunk = null; - - //decrypt the current chunk of encrypted message - if (!openssl_private_decrypt($encryptedChunk, $msgChunk, $managedKey['key'], OPENSSL_PKCS1_PADDING)) { - throw new AsymmetricException("The message decryption can't be accomplished due to an unknown error", 20); - } - - //join the current unencrypted chunk to the complete message - $message .= (string) $msgChunk; - } - - //return the decrypted message - return $message; - } - - /** - * Generate a digital signature for the given message. - * - * The digital signature can be used to authenticate the message because - * a different message will produce a different digital signature. - * - * You will be using the public key corresponding to the given private key - * to check the digital signature. - * - * Example usage: - * - * $message = "who knows if this message will be modified....."; - * - * //get the default private key - * $privKey = new PrivateKey(); - * - * //generate the digital signature - * $signature = Cryptography::generateDigitalSignature($privKey, $message); - * - * //transmit the digital signature - * - * - * @param PrivateKey $key the priate key to be used to generate the message - * @param string $message the message to be signed - * - * @return string the generate digital signature - * - * @throws \InvalidArgumentException the given message is not a valid string - * @throws AsymmetricException the error occurred while generating the message - */ - public static function generateDigitalSignature(PrivateKey &$key, $message) - { - //check the message type - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The message to be signed must be a non-empty string'); - } - - //check for the private key - if (!$key->isLoaded()) { - throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); - } - - //get the managed version of the native key - $managedKey = $key(); - - //generate the digital signature - $digitalSignature = null; - if (!openssl_sign($message, $digitalSignature, $managedKey['key'], 'sha256WithRSAEncryption')) { - throw new AsymmetricException('It is impossible to generate the digital signature due to an unknown error', 12); - } - - //return the signature in a binary-safe format - return base64_encode($digitalSignature); - } - - /** - * Check if the given digital signature belongs to the given message. - * - * You should be calling this function with a digital signature generated with - * the generateDigitalSignature() function. - * - * Usage example (continuation of the generateDigitalSignature() example): - * - * - * //get the default public key - * $pubKey = new PublicKey(); - * - * if (Cryptography::verifyDigitalSignature($pubKey, $message, $signature)) { - * echo "the message was not modified"; - * } else { - * echo "the message have been modified"; - * } - * - * - * @param PublicKey $key the public key associated with the private key used to generate the signature - * @param string $message the message to be checked - * @param string $signature the digital signature of the given message - * - * @return bool true if the message digitaly signed it equal to the digital signature - * - * @throws \InvalidArgumentException the given message or the given signature are not a valid string - * @throws AsymmetricException the error occurred while checking the message - */ - public static function verifyDigitalSignature(PublicKey &$key, $message, $signature) - { - //check the message type - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The message to be checked must be a non-empty string'); - } - - //check the message type - if ((!is_string($signature)) || (strlen($signature) <= 0)) { - throw new \InvalidArgumentException('The digital signature of the message must be a non-empty string'); - } - - //check for the private key - if (!$key->isLoaded()) { - throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 13); - } - - //get the signature result - $binSignature = base64_decode($signature); - - //attempt to verify the digital signature - $verificationResult = openssl_verify($message, $binSignature, $key()['key'], OPENSSL_ALGO_SHA256); - - //check for errors in the process - if (($verificationResult !== 0) && ($verificationResult !== 1)) { - throw new AsymmetricException('An unknown error has occurred while verifying the digital signature', 14); - } - - //return the result - return $verificationResult != 0; - } -} + + */ +abstract class Cryptography +{ + /** + * Encrypt the given message using the given private key. + * + * You will need the public key to decrypt the encrypted content. + * + * You can decrypt an encrypted content with the decrypt() function. + * + * An example of usage can be: + * + * + * $default_privkey = new PrivateKey(); + * $encrypted_message = Cryptography::encrypt($default_privkey, "this is my important message from my beloved half"); + * + * echo "Take good care of this and give it to my GF: " . $encrypted_message; + * + * + * @param PrivateKey $key the private key to be used to encrypt the plain message + * @param string $message the message to be encrypted + * + * @return string the encrypted message + * + * @throws \InvalidArgumentException the plain message is not a string + * @throws AsymmetricException an error occurred while encrypting the given message + */ + public static function encrypt(PrivateKey &$key, $message) + { + //check the plain message type + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The plain message to be encrypted must be given as a non-empty string'); + } + + //check for the private key + if (!$key->isLoaded()) { + throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); + } + + //get the key in native format and its length + $managedKey = $key(); + + //encrypt the complete message + $completeMsg = ''; + foreach (str_split($message, $managedKey['byteLength'] / 2) as $msgChunk) { + //the encrypted message + $encryptedChunk = null; + + //encrypt the current message and check for failure + if (!openssl_private_encrypt($msgChunk, $encryptedChunk, $managedKey['key'], OPENSSL_PKCS1_PADDING)) { + throw new AsymmetricException("The message encryption can't be accomplished due to an unknown error", 4); + } + + //join the current encrypted chunk to the encrypted message + $completeMsg .= (string) $encryptedChunk; + } + + //return the encrypted message base64-encoded + return base64_encode($completeMsg); + } + + /** + * Encrypt the given message using the given public key. + * + * You will need the private key to decrypt the encrypted content. + * + * You can decrypt an encrypted content with the decryptReverse() function. + * + * An example of usage can be: + * + * + * $default_pubkey = new PublicKey(); + * $encrypted_message = Cryptography::encryptReverse($default_pubkey, "this is my important message from my beloved half"); + * + * echo "Take good care of this and give it to my GF: " . $encrypted_message; + * + * + * @param PublicKey $key the public key to be used to encrypt the plain message + * @param string $message the message to be encrypted + * + * @return string the encrypted message + * + * @throws \InvalidArgumentException the plain message is not a string + * @throws AsymmetricException an error occurred while encrypting the given message + */ + public static function encryptReverse(PublicKey &$key, $message) + { + //check the plain message type + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The plain message to be encrypted must be given as a non-empty string'); + } + + //check for the public key + if (!$key->isLoaded()) { + throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); + } + + //get the key in native format and its length + $managedKey = $key(); + + //encrypt the complete message + $completeMsg = ''; + foreach (str_split($message, $managedKey['byteLength'] / 2) as $msgChunk) { + //the encrypted message + $encryptedChunk = null; + + //encrypt the current message and check for failure + if (!openssl_public_encrypt($msgChunk, $encryptedChunk, $managedKey['key'], OPENSSL_PKCS1_PADDING)) { + throw new AsymmetricException("The message encryption can't be accomplished due to an unknown error", 15); + } + + //join the current encrypted chunk to the encrypted message + $completeMsg .= (string) $encryptedChunk; + } + + //return the encrypted message base64-encoded + return base64_encode($completeMsg); + } + + /** + * Decrypt an encrypted message created using the encrypt() function. + * + * The used public key must be decoupled from the private key used to generate the message. + * + * En example usage can be: + * + * + * //load the default public key + * $default_pubkey = new PublicKey(); + * + * //this is a message encrypted with the application's default key + * $encrypted_message = "..."; + * + * //decrypt the message + * $plain_message = Cryptography::decrypt($default_pubkey, $encrypted_message); + * + * echo $encrypted_message; + * + * + * + * @param PublicKey $key the public key to be used to decrypt the encrypted message + * @param string $encryptedMsg the message to be decrypted + * + * @return string the encrypted message + * + * @throws \InvalidArgumentException the encrypted message is not a string + * @throws AsymmetricException an error occurred while decrypting the given message + */ + public static function decrypt(PublicKey &$key, $encryptedMsg) + { + //check the encrypted message type + if ((!is_string($encryptedMsg)) || (strlen($encryptedMsg) <= 0)) { + throw new \InvalidArgumentException('The encrypted message to be decrypted must be given as a non-empty string'); + } + + //check for the public key + if (!$key->isLoaded()) { + throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); + } + + //base64-decode of the encrypted message + $completeMsg = base64_decode($encryptedMsg); + + //get the key in native format and its length + $managedKey = $key(); + + //encrypt the complete message + $message = ''; + foreach (str_split($completeMsg, $managedKey['byteLength']) as $encryptedChunk) { + $msgChunk = null; + + //decrypt the current chunk of encrypted message + if (!openssl_public_decrypt($encryptedChunk, $msgChunk, $managedKey['key'], OPENSSL_PKCS1_PADDING)) { + throw new AsymmetricException("The message decryption can't be accomplished due to an unknown error", 5); + } + + //join the current unencrypted chunk to the complete message + $message .= (string) $msgChunk; + } + + //return the decrypted message + return $message; + } + + /** + * Decrypt an encrypted message created using the encryptReverse() function. + * + * The used private key must be must be the corresponding public key used to generate the message. + * + * En example usage can be: + * + * + * //load the default private key + * $default_pubkey = new PrivateKey(); + * + * //this is a message encrypted with the application's default key + * $encrypted_message = "..."; + * + * //decrypt the message + * $plain_message = Cryptography::decryptReverse($default_privkey, $encrypted_message); + * + * echo $encrypted_message; + * + * + * + * @param PrivateKey $key the public key to be used to decrypt the encrypted message + * @param string $encryptedMsg the message to be decrypted + * + * @return string the encrypted message + * + * @throws \InvalidArgumentException the encrypted message is not a string + * @throws AsymmetricException an error occurred while decrypting the given message + */ + public static function decryptReverse(PrivateKey &$key, $encryptedMsg) + { + //check the encrypted message type + if ((!is_string($encryptedMsg)) || (strlen($encryptedMsg) <= 0)) { + throw new \InvalidArgumentException('The encrypted message to be decrypted must be given as a non-empty string'); + } + + //check for the private key + if (!$key->isLoaded()) { + throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); + } + + //base64-decode of the encrypted message + $completeMsg = base64_decode($encryptedMsg); + + //get the key in native format and its length + $managedKey = $key(); + + //check if the message can be decrypted + /*if (($completeMsg % $managedKey['byteLength']) != 0) { + throw new AsymmetricException('The message decryption cannot take place because the given message is malformed', 6); + }*/ + + //encrypt the complete message + $message = ''; + foreach (str_split($completeMsg, $managedKey['byteLength']) as $encryptedChunk) { + $msgChunk = null; + + //decrypt the current chunk of encrypted message + if (!openssl_private_decrypt($encryptedChunk, $msgChunk, $managedKey['key'], OPENSSL_PKCS1_PADDING)) { + throw new AsymmetricException("The message decryption can't be accomplished due to an unknown error", 20); + } + + //join the current unencrypted chunk to the complete message + $message .= (string) $msgChunk; + } + + //return the decrypted message + return $message; + } + + /** + * Generate a digital signature for the given message. + * + * The digital signature can be used to authenticate the message because + * a different message will produce a different digital signature. + * + * You will be using the public key corresponding to the given private key + * to check the digital signature. + * + * Example usage: + * + * $message = "who knows if this message will be modified....."; + * + * //get the default private key + * $privKey = new PrivateKey(); + * + * //generate the digital signature + * $signature = Cryptography::generateDigitalSignature($privKey, $message); + * + * //transmit the digital signature + * + * + * @param PrivateKey $key the priate key to be used to generate the message + * @param string $message the message to be signed + * + * @return string the generate digital signature + * + * @throws \InvalidArgumentException the given message is not a valid string + * @throws AsymmetricException the error occurred while generating the message + */ + public static function generateDigitalSignature(PrivateKey &$key, $message) + { + //check the message type + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be signed must be a non-empty string'); + } + + //check for the private key + if (!$key->isLoaded()) { + throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 11); + } + + //get the managed version of the native key + $managedKey = $key(); + + //generate the digital signature + $digitalSignature = null; + if (!openssl_sign($message, $digitalSignature, $managedKey['key'], 'sha256WithRSAEncryption')) { + throw new AsymmetricException('It is impossible to generate the digital signature due to an unknown error', 12); + } + + //return the signature in a binary-safe format + return base64_encode($digitalSignature); + } + + /** + * Check if the given digital signature belongs to the given message. + * + * You should be calling this function with a digital signature generated with + * the generateDigitalSignature() function. + * + * Usage example (continuation of the generateDigitalSignature() example): + * + * + * //get the default public key + * $pubKey = new PublicKey(); + * + * if (Cryptography::verifyDigitalSignature($pubKey, $message, $signature)) { + * echo "the message was not modified"; + * } else { + * echo "the message have been modified"; + * } + * + * + * @param PublicKey $key the public key associated with the private key used to generate the signature + * @param string $message the message to be checked + * @param string $signature the digital signature of the given message + * + * @return bool true if the message digitaly signed it equal to the digital signature + * + * @throws \InvalidArgumentException the given message or the given signature are not a valid string + * @throws AsymmetricException the error occurred while checking the message + */ + public static function verifyDigitalSignature(PublicKey &$key, $message, $signature) + { + //check the message type + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be checked must be a non-empty string'); + } + + //check the message type + if ((!is_string($signature)) || (strlen($signature) <= 0)) { + throw new \InvalidArgumentException('The digital signature of the message must be a non-empty string'); + } + + //check for the private key + if (!$key->isLoaded()) { + throw new AsymmetricException('It is impossible to generate a digital signature with an unloaded key', 13); + } + + //get the signature result + $binSignature = base64_decode($signature); + + //attempt to verify the digital signature + $verificationResult = openssl_verify($message, $binSignature, $key()['key'], OPENSSL_ALGO_SHA256); + + //check for errors in the process + if (($verificationResult !== 0) && ($verificationResult !== 1)) { + throw new AsymmetricException('An unknown error has occurred while verifying the digital signature', 14); + } + + //return the result + return $verificationResult != 0; + } +} diff --git a/Gishiki/Security/Encryption/Asymmetric/PrivateKey.php b/Gishiki/Security/Encryption/Asymmetric/PrivateKey.php index 63928b73..c2476219 100644 --- a/Gishiki/Security/Encryption/Asymmetric/PrivateKey.php +++ b/Gishiki/Security/Encryption/Asymmetric/PrivateKey.php @@ -1,294 +1,294 @@ - - */ -final class PrivateKey -{ - //RSA keys length - const RSA512 = 512; - const RSA1024 = 1024; - const RSA2048 = 2048; - const RSA4096 = 4096; - const RSAEXTREME = 16384; - - /** - * Create a random private key of the given length (in bits). - * - * You can use predefined constants to have valid keylength values. - * - * The higher the key length, the higher the security the higher the required time to generate the key. - * - * Usage example: - * - * - * //give a name to the file containing the generation result - * $filename = APPLICATION_DIR."newkey.private.pem"; - * - * //generate the new key - * $serailized_key = PrivateKey::generate(PrivateKey::RSA4096); - * - * //export to file the serialized key - * file_put_contents($filename, $serailized_key); - * //NOTE: this example is really BAD because the file is NOT encrypted - * - * //yes, you can load private keys directly from file - * $private_key = new PrivateKey("file://".$filename); - * - * - * @param int $keyLength the length (in bits) of the private key to e generated - * - * @return string the serialized private key - * - * @throws \InvalidArgumentException the given key length is not an integer power of two - * @throws AsymmetricException the error occurred while generating and exporting the new private key - */ - public static function generate($keyLength = self::RSA4096) - { - if (!is_integer($keyLength)) { - throw new \InvalidArgumentException('The key length must be given as an integer number which is a power of two'); - } elseif ((($keyLength & ($keyLength - 1)) != 0) || ($keyLength == 0)) { - throw new \InvalidArgumentException('The key length must be a power of two'); - } - - //if the key length is extremely long, a very long time will be needed to generate the key, and it is necessary ti set no time limits to generate it - //if ($keyLength == self::RSAEXTREME) { - set_time_limit(0); - //} - - //build the configuration array - $config = [ - 'digest_alg' => 'sha512', - 'private_key_bits' => $keyLength, - 'private_key_type' => OPENSSL_KEYTYPE_RSA, - ]; - //use the application openssl configuration - if (!is_null(Environment::getCurrentEnvironment())) { - $config = array_merge( - $config, - ['config' => (file_exists(Environment::getCurrentEnvironment()->getConfigurationProperty('APPLICATION_DIR').'openssl.cnf')) ? - Environment::getCurrentEnvironment()->getConfigurationProperty('APPLICATION_DIR').'openssl.cnf' : null, ]); - } - - //create a new private key - $privateKey = openssl_pkey_new($config); - - //check the result - if (!is_resource($privateKey)) { - throw new AsymmetricException('The key generation is not possible due to an unknown '.openssl_error_string(), 8); - } - - //extract the private key string-encoded from the generated private key - $pKeyEncoded = ''; - openssl_pkey_export($privateKey, $pKeyEncoded, null, $config); - - //free the memory space used to hold the newly generated key - openssl_free_key($privateKey); - - //check for the result - if (!is_string($pKeyEncoded)) { - throw new AsymmetricException("The key generation was completed, but the result couldn't be exported", 9); - } - - //return the operation result - return $pKeyEncoded; - } - - /************************************************************************** - * * - * NON-static properties * - * * - **************************************************************************/ - - /** - * @var resource the private key ready to be used by OpenSSL - */ - private $key = null; - - /** - * Used to create a private key from the given string. - * - * If a string containing a serialized private key is - * not give, the framework default one will be used - * - * @param string|null $customKey the private key serialized as a string - * @param string $customKeyPassword the password to decrypt the serialized private key (if necessary) - * - * @throws \InvalidArgumentException the given key and/or password isn't a valid string - * @throws AsymmetricException the given key is invalid - */ - public function __construct($customKey = null, $customKeyPassword = '') - { - if (!is_string($customKeyPassword)) { - throw new \InvalidArgumentException('The private key password cannot be something else than a string'); - } - - if ((!is_string($customKey)) && (!is_null($customKey))) { - throw new \InvalidArgumentException('The serialized private key must be a string'); - } - - //get a string containing a serialized asymmetric key - $serialized_key = (is_string($customKey)) ? - $serialized_key = $customKey : - Environment::getCurrentEnvironment()->getConfigurationProperty('MASTER_ASYMMETRIC_KEY'); - - //get the beginning and ending of a private key (to stip out additional shit and check for key validity) - $is_encrypted = strpos($serialized_key, 'ENCRYPTED') !== false; - - //get the password of the serialized key - $serializedKeyPassword = ($is_encrypted) ? $customKeyPassword : ''; - - //load the private key - $this->key = openssl_pkey_get_private($serialized_key, $serializedKeyPassword); - - //check for errors - if (!$this->isLoaded()) { - throw new AsymmetricException('The private key could not be loaded', 0); - } - } - - /** - * Free resources used to hold this private key. - */ - public function __destruct() - { - if ($this->isLoaded()) { - openssl_free_key($this->key); - } - } - - /** - * Export the public key corresponding to this private key. - * - * @return string the public key exported from this private key - */ - public function exportPublicKey() - { - //get details of the current private key - $privateKeyDetails = openssl_pkey_get_details($this->key); - - //return the public key - return $privateKeyDetails['key']; - } - - /** - * Export this private key in a string format. - * - * The resulting string can be used to construct another PrivateKey instance: - * - * - * use Gishiki\Security\Encryption\Asymmetric\PrivateKey; - * - * //this is the exported private key - * $exported_key = "..."; - * - * //rebuild the private key - * $privateKey = new PrivateKey($exported_key); - * - * - * @param string $keyPassword the private key password - * - * @return string the serialized private key - * - * @throws \InvalidArgumentException the given password is not a string - * @throws AsymmetricException the given key is invalid - */ - public function export($keyPassword = '') - { - if (!is_string($keyPassword)) { - throw new \InvalidArgumentException('The private key password cannot be something else than a string'); - } elseif (!$this->isLoaded()) { - throw new AsymmetricException('It is impossible to serialize an unloaded private key: '.openssl_error_string(), 1); - } - - $serialized_key = ''; - - //build the configuration array - $config = [ - 'digest_alg' => 'sha512', - 'private_key_type' => OPENSSL_KEYTYPE_RSA, - ]; - //use the application openssl configuration - if (!is_null(Environment::getCurrentEnvironment())) { - $config = array_merge( - $config, - ['config' => (file_exists(Environment::getCurrentEnvironment()->getConfigurationProperty('APPLICATION_DIR').'openssl.cnf')) ? - Environment::getCurrentEnvironment()->getConfigurationProperty('APPLICATION_DIR').'openssl.cnf' : null, ]); - } - - //serialize the key and encrypt it if requested - if (strlen($keyPassword) > 0) { - openssl_pkey_export($this->key, $serialized_key, $keyPassword, $config); - } else { - openssl_pkey_export($this->key, $serialized_key, null, $config); - } - - //return the serialized key - return $serialized_key; - } - - /** - * Check if the key has been loaded. - * - * @return bool true if the key has been loaded - */ - public function isLoaded() - { - return is_resource($this->key); - } - - /** - * Proxy call to the export() function. - * - * @return string the serialized key - */ - public function __toString() - { - return $this->export(); - } - - /** - * Export a reference to the native private key and its length in bits. - * - * @return array the array that contains the key and its legth (in bytes) - * - * @throws AsymmetricException the key cannot be exported - */ - public function __invoke() - { - if (!$this->isLoaded()) { - throw new AsymmetricException('It is impossible to obtain an unloaded private key', 1); - } - - //get private key details - $details = openssl_pkey_get_details($this->key); - - return [ - 'key' => &$this->key, - 'byteLength' => $details['bits'] / 8, - ]; - } -} + + */ +final class PrivateKey +{ + //RSA keys length + const RSA512 = 512; + const RSA1024 = 1024; + const RSA2048 = 2048; + const RSA4096 = 4096; + const RSAEXTREME = 16384; + + /** + * Create a random private key of the given length (in bits). + * + * You can use predefined constants to have valid keylength values. + * + * The higher the key length, the higher the security the higher the required time to generate the key. + * + * Usage example: + * + * + * //give a name to the file containing the generation result + * $filename = APPLICATION_DIR."newkey.private.pem"; + * + * //generate the new key + * $serailized_key = PrivateKey::generate(PrivateKey::RSA4096); + * + * //export to file the serialized key + * file_put_contents($filename, $serailized_key); + * //NOTE: this example is really BAD because the file is NOT encrypted + * + * //yes, you can load private keys directly from file + * $private_key = new PrivateKey("file://".$filename); + * + * + * @param int $keyLength the length (in bits) of the private key to e generated + * + * @return string the serialized private key + * + * @throws \InvalidArgumentException the given key length is not an integer power of two + * @throws AsymmetricException the error occurred while generating and exporting the new private key + */ + public static function generate($keyLength = self::RSA4096) + { + if (!is_integer($keyLength)) { + throw new \InvalidArgumentException('The key length must be given as an integer number which is a power of two'); + } elseif ((($keyLength & ($keyLength - 1)) != 0) || ($keyLength == 0)) { + throw new \InvalidArgumentException('The key length must be a power of two'); + } + + //if the key length is extremely long, a very long time will be needed to generate the key, and it is necessary ti set no time limits to generate it + //if ($keyLength == self::RSAEXTREME) { + set_time_limit(0); + //} + + //build the configuration array + $config = [ + 'digest_alg' => 'sha512', + 'private_key_bits' => $keyLength, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]; + //use the application openssl configuration + if (!is_null(Environment::getCurrentEnvironment())) { + $config = array_merge( + $config, + ['config' => (file_exists(Environment::getCurrentEnvironment()->getConfigurationProperty('APPLICATION_DIR').'openssl.cnf')) ? + Environment::getCurrentEnvironment()->getConfigurationProperty('APPLICATION_DIR').'openssl.cnf' : null, ]); + } + + //create a new private key + $privateKey = openssl_pkey_new($config); + + //check the result + if (!is_resource($privateKey)) { + throw new AsymmetricException('The key generation is not possible due to an unknown '.openssl_error_string(), 8); + } + + //extract the private key string-encoded from the generated private key + $pKeyEncoded = ''; + openssl_pkey_export($privateKey, $pKeyEncoded, null, $config); + + //free the memory space used to hold the newly generated key + openssl_free_key($privateKey); + + //check for the result + if (!is_string($pKeyEncoded)) { + throw new AsymmetricException("The key generation was completed, but the result couldn't be exported", 9); + } + + //return the operation result + return $pKeyEncoded; + } + + /************************************************************************** + * * + * NON-static properties * + * * + **************************************************************************/ + + /** + * @var resource the private key ready to be used by OpenSSL + */ + private $key = null; + + /** + * Used to create a private key from the given string. + * + * If a string containing a serialized private key is + * not give, the framework default one will be used + * + * @param string|null $customKey the private key serialized as a string + * @param string $customKeyPassword the password to decrypt the serialized private key (if necessary) + * + * @throws \InvalidArgumentException the given key and/or password isn't a valid string + * @throws AsymmetricException the given key is invalid + */ + public function __construct($customKey = null, $customKeyPassword = '') + { + if (!is_string($customKeyPassword)) { + throw new \InvalidArgumentException('The private key password cannot be something else than a string'); + } + + if ((!is_string($customKey)) && (!is_null($customKey))) { + throw new \InvalidArgumentException('The serialized private key must be a string'); + } + + //get a string containing a serialized asymmetric key + $serialized_key = (is_string($customKey)) ? + $serialized_key = $customKey : + Environment::getCurrentEnvironment()->getConfigurationProperty('MASTER_ASYMMETRIC_KEY'); + + //get the beginning and ending of a private key (to stip out additional shit and check for key validity) + $is_encrypted = strpos($serialized_key, 'ENCRYPTED') !== false; + + //get the password of the serialized key + $serializedKeyPassword = ($is_encrypted) ? $customKeyPassword : ''; + + //load the private key + $this->key = openssl_pkey_get_private($serialized_key, $serializedKeyPassword); + + //check for errors + if (!$this->isLoaded()) { + throw new AsymmetricException('The private key could not be loaded', 0); + } + } + + /** + * Free resources used to hold this private key. + */ + public function __destruct() + { + if ($this->isLoaded()) { + openssl_free_key($this->key); + } + } + + /** + * Export the public key corresponding to this private key. + * + * @return string the public key exported from this private key + */ + public function exportPublicKey() + { + //get details of the current private key + $privateKeyDetails = openssl_pkey_get_details($this->key); + + //return the public key + return $privateKeyDetails['key']; + } + + /** + * Export this private key in a string format. + * + * The resulting string can be used to construct another PrivateKey instance: + * + * + * use Gishiki\Security\Encryption\Asymmetric\PrivateKey; + * + * //this is the exported private key + * $exported_key = "..."; + * + * //rebuild the private key + * $privateKey = new PrivateKey($exported_key); + * + * + * @param string $keyPassword the private key password + * + * @return string the serialized private key + * + * @throws \InvalidArgumentException the given password is not a string + * @throws AsymmetricException the given key is invalid + */ + public function export($keyPassword = '') + { + if (!is_string($keyPassword)) { + throw new \InvalidArgumentException('The private key password cannot be something else than a string'); + } elseif (!$this->isLoaded()) { + throw new AsymmetricException('It is impossible to serialize an unloaded private key: '.openssl_error_string(), 1); + } + + $serialized_key = ''; + + //build the configuration array + $config = [ + 'digest_alg' => 'sha512', + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]; + //use the application openssl configuration + if (!is_null(Environment::getCurrentEnvironment())) { + $config = array_merge( + $config, + ['config' => (file_exists(Environment::getCurrentEnvironment()->getConfigurationProperty('APPLICATION_DIR').'openssl.cnf')) ? + Environment::getCurrentEnvironment()->getConfigurationProperty('APPLICATION_DIR').'openssl.cnf' : null, ]); + } + + //serialize the key and encrypt it if requested + if (strlen($keyPassword) > 0) { + openssl_pkey_export($this->key, $serialized_key, $keyPassword, $config); + } else { + openssl_pkey_export($this->key, $serialized_key, null, $config); + } + + //return the serialized key + return $serialized_key; + } + + /** + * Check if the key has been loaded. + * + * @return bool true if the key has been loaded + */ + public function isLoaded() + { + return is_resource($this->key); + } + + /** + * Proxy call to the export() function. + * + * @return string the serialized key + */ + public function __toString() + { + return $this->export(); + } + + /** + * Export a reference to the native private key and its length in bits. + * + * @return array the array that contains the key and its legth (in bytes) + * + * @throws AsymmetricException the key cannot be exported + */ + public function __invoke() + { + if (!$this->isLoaded()) { + throw new AsymmetricException('It is impossible to obtain an unloaded private key', 1); + } + + //get private key details + $details = openssl_pkey_get_details($this->key); + + return [ + 'key' => &$this->key, + 'byteLength' => $details['bits'] / 8, + ]; + } +} diff --git a/Gishiki/Security/Encryption/Asymmetric/PublicKey.php b/Gishiki/Security/Encryption/Asymmetric/PublicKey.php index f0615435..7bbdc5d9 100644 --- a/Gishiki/Security/Encryption/Asymmetric/PublicKey.php +++ b/Gishiki/Security/Encryption/Asymmetric/PublicKey.php @@ -1,111 +1,111 @@ - - */ -final class PublicKey -{ - /** - * @var resource the public key ready to be used by OpenSSL - */ - private $key = null; - - /** - * Used to create a public key from the given string. - * - * If a string containing a serialized public key is - * not give, the framework default one will be used - * - * @param string|null $custom_key the public key serialized as a string - * - * @throws \InvalidArgumentException the given key isn't a valid serialized key - * @throws AsymmetricException the given key is invalid - */ - public function __construct($custom_key = null) - { - $serialized_key = ''; - - if (is_null($custom_key)) { - //create the default private key - $default_private_key = new PrivateKey(); - - //and retrive the public key from the default private key - $serialized_key = $default_private_key->retrivePublicKey(); - } elseif (is_string($custom_key)) { - $serialized_key = $custom_key; - } else { - throw new \InvalidArgumentException('The serialized public key must be a string'); - } - - //load the public key - $this->key = openssl_pkey_get_public($serialized_key); - - //check for errors - if (!$this->isLoaded()) { - throw new AsymmetricException('The public key could not be loaded', 2); - } - } - - /** - * Free resources used to hold this private key. - */ - public function __destruct() - { - if ($this->isLoaded()) { - openssl_free_key($this->key); - } - } - - /** - * Check if the key has been loaded. - * - * @return bool true if the key has been loaded - */ - public function isLoaded() - { - return is_resource($this->key); - } - - /** - * Export a reference to the native private key and its length in bits. - * - * @return array the array that contains the key and its legth (in bytes) - * - * @throws AsymmetricException the key cannot be exported - */ - public function __invoke() - { - if (!$this->isLoaded()) { - throw new AsymmetricException('It is impossible to obtain an unloaded public key', 1); - } - - //get private key details - $details = openssl_pkey_get_details($this->key); - - return [ - 'key' => &$this->key, - 'byteLength' => $details['bits'] / 8, - ]; - } -} + + */ +final class PublicKey +{ + /** + * @var resource the public key ready to be used by OpenSSL + */ + private $key = null; + + /** + * Used to create a public key from the given string. + * + * If a string containing a serialized public key is + * not give, the framework default one will be used + * + * @param string|null $custom_key the public key serialized as a string + * + * @throws \InvalidArgumentException the given key isn't a valid serialized key + * @throws AsymmetricException the given key is invalid + */ + public function __construct($custom_key = null) + { + $serialized_key = ''; + + if (is_null($custom_key)) { + //create the default private key + $default_private_key = new PrivateKey(); + + //and retrive the public key from the default private key + $serialized_key = $default_private_key->retrivePublicKey(); + } elseif (is_string($custom_key)) { + $serialized_key = $custom_key; + } else { + throw new \InvalidArgumentException('The serialized public key must be a string'); + } + + //load the public key + $this->key = openssl_pkey_get_public($serialized_key); + + //check for errors + if (!$this->isLoaded()) { + throw new AsymmetricException('The public key could not be loaded', 2); + } + } + + /** + * Free resources used to hold this private key. + */ + public function __destruct() + { + if ($this->isLoaded()) { + openssl_free_key($this->key); + } + } + + /** + * Check if the key has been loaded. + * + * @return bool true if the key has been loaded + */ + public function isLoaded() + { + return is_resource($this->key); + } + + /** + * Export a reference to the native private key and its length in bits. + * + * @return array the array that contains the key and its legth (in bytes) + * + * @throws AsymmetricException the key cannot be exported + */ + public function __invoke() + { + if (!$this->isLoaded()) { + throw new AsymmetricException('It is impossible to obtain an unloaded public key', 1); + } + + //get private key details + $details = openssl_pkey_get_details($this->key); + + return [ + 'key' => &$this->key, + 'byteLength' => $details['bits'] / 8, + ]; + } +} diff --git a/Gishiki/Security/Encryption/Symmetric/Cryptography.php b/Gishiki/Security/Encryption/Symmetric/Cryptography.php index e5e9443e..f84bb849 100644 --- a/Gishiki/Security/Encryption/Symmetric/Cryptography.php +++ b/Gishiki/Security/Encryption/Symmetric/Cryptography.php @@ -1,160 +1,160 @@ - - */ -abstract class Cryptography -{ - /****************************************************** - * List of known algorithms * - ******************************************************/ - const AES_CBC_128 = 'aes-128-cbc'; - const AES_CBC_192 = 'aes-192-cbc'; - const AES_CBC_256 = 'aes-256-cbc'; - - /** - * Encrypt the given content using the given secure key (that should be - * prepared using pbkdf2). - * - * The resulting IV (automatically generated if null is passed) is - * base64 encoded. - * - * The resulting encrypted content is base64 encoded. - * - * Example usage: - * - * //prepare the secret key for the symmetric cipher - * $key = new SecretKey( ... ); - * //Note: the key is NOT the password - * - * //encrypt the content - * $enc_result = Cryptography::encrypt($key, "this is the message to be encrypted"); - * - * //transmit the IV_base64 and Encryption to decrypt the content - * //if you used a cistom IV you don't need to pass the IV - * - * - * @param SecretKey $key the key to be used to encrypt the given message - * @param string $message the message to be encrypted - * @param string|null $initVector the base64 representation of the IV to be used (pick a random one if null) - * @param string $algorithm the name of the algorithm to be used - * - * @return array the base64 of the raw encryption result and the used IV - * - * @throws \InvalidArgumentException one or more arguments are invalid - * @throws SymmetricException the error occurred while encrypting the content - */ - public static function encrypt(SecretKey &$key, $message, $initVector = null, $algorithm = self::AES_CBC_128) - { - //check the plain message type - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The plain message to be encrypted must be given as a non-empty string'); - } - - //get the managed kersion of the key - $managedKey = $key(); - - //check for the key length - if (($algorithm == self::AES_CBC_128) && ($managedKey['byteLength'] != 16)) { - throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/16)', 0); - } elseif (($algorithm == self::AES_CBC_192) && ($managedKey['byteLength'] != 24)) { - throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/24)', 1); - } elseif (($algorithm == self::AES_CBC_256) && ($managedKey['byteLength'] != 32)) { - throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/32)', 2); - } - - //generate and store a random IV - $decodedIv = (is_null($initVector)) ? openssl_random_pseudo_bytes(openssl_cipher_iv_length($algorithm)) : base64_decode($initVector); - - //get the encrypted data - $encrypted = openssl_encrypt($message, $algorithm, $managedKey['key'], OPENSSL_RAW_DATA, $decodedIv); - - //return the encryption result and the randomly generated IV - return [ - 'Encryption' => base64_encode($encrypted), - 'IV_base64' => base64_encode($decodedIv), - 'IV_hex' => bin2hex($decodedIv), - ]; - } - - /** - * Decrypt the given encrypted content using the given secure key - * (that should be prepared using pbkdf2 Ahead Of Time). - * - * Example Usage: - * - * //this is the key encoded in hex format required to create the SecureKey - * $key_hex_encoded = " ... "; //make sure this is the key used when encrypting - * //Note: the key is NOT the password - * - * //build the key - * $key = new SecretKey($key_hex_encoded); - * - * //this is the IV encoded in base64: it is returned by the encrypt() function - * $initVector_base_encoded = " ... "; - * - * //$message will hold the original plaintext message - * $message = Cryptography::decrypt($key, $encryptedMessage, $initVector_base_encoded); - * - * - * @param SecretKey $key the key that has been used to encrypt the message - * @param string $encryptedMessage the encryption result (must be base64-encoded) - * @param string $initVector the iv represented in base64 - * @param string $algorithm the name of the algorithm to be used - * - * @return string the decrypted content - * - * @throws \InvalidArgumentException one or more arguments are invalid - * @throws SymmetricException the error occurred while decrypting the content - */ - public static function decrypt(SecretKey &$key, $encryptedMessage, $initVector, $algorithm = self::AES_CBC_128) - { - //check the plain message type - if ((!is_string($encryptedMessage)) || (strlen($encryptedMessage) <= 0)) { - throw new \InvalidArgumentException('The encrypted message to be decrypted must be given as a non-empty string'); - } - - //get the managed version of the key - $managedKey = $key(); - - //check for the key length - if (($algorithm == self::AES_CBC_128) && ($managedKey['byteLength'] != 16)) { - throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/16)', 3); - } elseif (($algorithm == self::AES_CBC_192) && ($managedKey['byteLength'] != 24)) { - throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/24)', 4); - } elseif (($algorithm == self::AES_CBC_256) && ($managedKey['byteLength'] != 32)) { - throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/32)', 5); - } - - //get the IV - $decodedIv = base64_decode($initVector); - - //get the data to decrypt - $encrypted = base64_decode($encryptedMessage); - - //decrypt and return the result - return openssl_decrypt($encrypted, $algorithm, $managedKey['key'], OPENSSL_RAW_DATA, $decodedIv); - } -} + + */ +abstract class Cryptography +{ + /****************************************************** + * List of known algorithms * + ******************************************************/ + const AES_CBC_128 = 'aes-128-cbc'; + const AES_CBC_192 = 'aes-192-cbc'; + const AES_CBC_256 = 'aes-256-cbc'; + + /** + * Encrypt the given content using the given secure key (that should be + * prepared using pbkdf2). + * + * The resulting IV (automatically generated if null is passed) is + * base64 encoded. + * + * The resulting encrypted content is base64 encoded. + * + * Example usage: + * + * //prepare the secret key for the symmetric cipher + * $key = new SecretKey( ... ); + * //Note: the key is NOT the password + * + * //encrypt the content + * $enc_result = Cryptography::encrypt($key, "this is the message to be encrypted"); + * + * //transmit the IV_base64 and Encryption to decrypt the content + * //if you used a cistom IV you don't need to pass the IV + * + * + * @param SecretKey $key the key to be used to encrypt the given message + * @param string $message the message to be encrypted + * @param string|null $initVector the base64 representation of the IV to be used (pick a random one if null) + * @param string $algorithm the name of the algorithm to be used + * + * @return array the base64 of the raw encryption result and the used IV + * + * @throws \InvalidArgumentException one or more arguments are invalid + * @throws SymmetricException the error occurred while encrypting the content + */ + public static function encrypt(SecretKey &$key, $message, $initVector = null, $algorithm = self::AES_CBC_128) + { + //check the plain message type + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The plain message to be encrypted must be given as a non-empty string'); + } + + //get the managed kersion of the key + $managedKey = $key(); + + //check for the key length + if (($algorithm == self::AES_CBC_128) && ($managedKey['byteLength'] != 16)) { + throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/16)', 0); + } elseif (($algorithm == self::AES_CBC_192) && ($managedKey['byteLength'] != 24)) { + throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/24)', 1); + } elseif (($algorithm == self::AES_CBC_256) && ($managedKey['byteLength'] != 32)) { + throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/32)', 2); + } + + //generate and store a random IV + $decodedIv = (is_null($initVector)) ? openssl_random_pseudo_bytes(openssl_cipher_iv_length($algorithm)) : base64_decode($initVector); + + //get the encrypted data + $encrypted = openssl_encrypt($message, $algorithm, $managedKey['key'], OPENSSL_RAW_DATA, $decodedIv); + + //return the encryption result and the randomly generated IV + return [ + 'Encryption' => base64_encode($encrypted), + 'IV_base64' => base64_encode($decodedIv), + 'IV_hex' => bin2hex($decodedIv), + ]; + } + + /** + * Decrypt the given encrypted content using the given secure key + * (that should be prepared using pbkdf2 Ahead Of Time). + * + * Example Usage: + * + * //this is the key encoded in hex format required to create the SecureKey + * $key_hex_encoded = " ... "; //make sure this is the key used when encrypting + * //Note: the key is NOT the password + * + * //build the key + * $key = new SecretKey($key_hex_encoded); + * + * //this is the IV encoded in base64: it is returned by the encrypt() function + * $initVector_base_encoded = " ... "; + * + * //$message will hold the original plaintext message + * $message = Cryptography::decrypt($key, $encryptedMessage, $initVector_base_encoded); + * + * + * @param SecretKey $key the key that has been used to encrypt the message + * @param string $encryptedMessage the encryption result (must be base64-encoded) + * @param string $initVector the iv represented in base64 + * @param string $algorithm the name of the algorithm to be used + * + * @return string the decrypted content + * + * @throws \InvalidArgumentException one or more arguments are invalid + * @throws SymmetricException the error occurred while decrypting the content + */ + public static function decrypt(SecretKey &$key, $encryptedMessage, $initVector, $algorithm = self::AES_CBC_128) + { + //check the plain message type + if ((!is_string($encryptedMessage)) || (strlen($encryptedMessage) <= 0)) { + throw new \InvalidArgumentException('The encrypted message to be decrypted must be given as a non-empty string'); + } + + //get the managed version of the key + $managedKey = $key(); + + //check for the key length + if (($algorithm == self::AES_CBC_128) && ($managedKey['byteLength'] != 16)) { + throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/16)', 3); + } elseif (($algorithm == self::AES_CBC_192) && ($managedKey['byteLength'] != 24)) { + throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/24)', 4); + } elseif (($algorithm == self::AES_CBC_256) && ($managedKey['byteLength'] != 32)) { + throw new SymmetricException('You must be using a key with the correct length for the choosen algorithm ('.$managedKey['byteLength'].'/32)', 5); + } + + //get the IV + $decodedIv = base64_decode($initVector); + + //get the data to decrypt + $encrypted = base64_decode($encryptedMessage); + + //decrypt and return the result + return openssl_decrypt($encrypted, $algorithm, $managedKey['key'], OPENSSL_RAW_DATA, $decodedIv); + } +} diff --git a/Gishiki/Security/Encryption/Symmetric/SecretKey.php b/Gishiki/Security/Encryption/Symmetric/SecretKey.php index 552160d9..f7453d5a 100644 --- a/Gishiki/Security/Encryption/Symmetric/SecretKey.php +++ b/Gishiki/Security/Encryption/Symmetric/SecretKey.php @@ -1,140 +1,140 @@ - - */ -final class SecretKey -{ - /** - * Generate the hexadecimal representation of a secure key - * using the pbkdf2 algorithm in order to derive it from the - * given password. - * - * Note: this function MAY throw exceptions - * (the same exceptions Algorithm::pbkdf2() can throw) - * - * @param string $password the password to be derived - * @param int $key_length the final length of the key (in bytes) - * - * @return string an hex representation of the generated key - */ - public static function generate($password, $key_length = 16) - { - //generate some random characters - $salt = openssl_random_pseudo_bytes(2 * $key_length); - - //generate the pbkdf2 key - return Algorithm::pbkdf2($password, $salt, $key_length, 20000, Algorithm::SHA256); - } - - /************************************************************************** - * * - * NON-static properties * - * * - **************************************************************************/ - - /** - * @var string the key in the native format - */ - private $key; - - /** - * @var int the key length (in bytes) - */ - private $keyLength; - - /** - * Create an encryption key using the given serialized key. - * - * A serialized key is the hexadecimal representation of key. - * - * You can use the generate() function to retrive a really - * secure key from the password (the same key derivation - * algorithm that openssl internally uses). - * - * Usage example: - * - * - * //generate a secure pbkdf2-derived key and use it as the encryption key - * $my_key = new SecretKey(SecretKey::generate("mypassword")); - * - * //you MUST save the generated key, because it won't be possible to - * //generate the same key once again (even using the same password)! - * $precious_key = (string) $my_key; - * - * - * @param string $key the password to be used in a HEX encoded format - */ - public function __construct($key = null) - { - //check for the input - if (((!is_string($key)) || (strlen($key) <= 2)) && (!is_null($key))) { - throw new \InvalidArgumentException('The secure key must be given as a non-empty string that is the hex representation of the real key'); - } - - //get the symmetric key to be used - $key = (!is_null($key)) ? $key : Environment::getCurrentEnvironment()->getConfigurationProperty('MASTER_SYMMETRIC_KEY'); - - //get the real encryption key - $this->keyLength = strlen($key) / 2; - $this->key = hex2bin($key); - } - - /** - * Export the currently loaded key. - * - * @return string the hex representation of the loaded key - */ - public function export() - { - return bin2hex($this->key); - } - - /** - * Proxy call to the export() function. - * - * @return string the serialized key - */ - public function __toString() - { - return $this->export(); - } - - /** - * Export a reference to the native private key and its length in bits. - * - * @return array the array that contains the key and its legth (in bytes) - */ - public function __invoke() - { - //get & return secure key details - return [ - 'key' => $this->key, - 'byteLength' => $this->keyLength, - ]; - } -} + + */ +final class SecretKey +{ + /** + * Generate the hexadecimal representation of a secure key + * using the pbkdf2 algorithm in order to derive it from the + * given password. + * + * Note: this function MAY throw exceptions + * (the same exceptions Algorithm::pbkdf2() can throw) + * + * @param string $password the password to be derived + * @param int $key_length the final length of the key (in bytes) + * + * @return string an hex representation of the generated key + */ + public static function generate($password, $key_length = 16) + { + //generate some random characters + $salt = openssl_random_pseudo_bytes(2 * $key_length); + + //generate the pbkdf2 key + return Algorithm::pbkdf2($password, $salt, $key_length, 20000, Algorithm::SHA256); + } + + /************************************************************************** + * * + * NON-static properties * + * * + **************************************************************************/ + + /** + * @var string the key in the native format + */ + private $key; + + /** + * @var int the key length (in bytes) + */ + private $keyLength; + + /** + * Create an encryption key using the given serialized key. + * + * A serialized key is the hexadecimal representation of key. + * + * You can use the generate() function to retrive a really + * secure key from the password (the same key derivation + * algorithm that openssl internally uses). + * + * Usage example: + * + * + * //generate a secure pbkdf2-derived key and use it as the encryption key + * $my_key = new SecretKey(SecretKey::generate("mypassword")); + * + * //you MUST save the generated key, because it won't be possible to + * //generate the same key once again (even using the same password)! + * $precious_key = (string) $my_key; + * + * + * @param string $key the password to be used in a HEX encoded format + */ + public function __construct($key = null) + { + //check for the input + if (((!is_string($key)) || (strlen($key) <= 2)) && (!is_null($key))) { + throw new \InvalidArgumentException('The secure key must be given as a non-empty string that is the hex representation of the real key'); + } + + //get the symmetric key to be used + $key = (!is_null($key)) ? $key : Environment::getCurrentEnvironment()->getConfigurationProperty('MASTER_SYMMETRIC_KEY'); + + //get the real encryption key + $this->keyLength = strlen($key) / 2; + $this->key = hex2bin($key); + } + + /** + * Export the currently loaded key. + * + * @return string the hex representation of the loaded key + */ + public function export() + { + return bin2hex($this->key); + } + + /** + * Proxy call to the export() function. + * + * @return string the serialized key + */ + public function __toString() + { + return $this->export(); + } + + /** + * Export a reference to the native private key and its length in bits. + * + * @return array the array that contains the key and its legth (in bytes) + */ + public function __invoke() + { + //get & return secure key details + return [ + 'key' => $this->key, + 'byteLength' => $this->keyLength, + ]; + } +} diff --git a/Gishiki/Security/Encryption/Symmetric/SymmetricException.php b/Gishiki/Security/Encryption/Symmetric/SymmetricException.php index f5c45f61..c83eff0d 100644 --- a/Gishiki/Security/Encryption/Symmetric/SymmetricException.php +++ b/Gishiki/Security/Encryption/Symmetric/SymmetricException.php @@ -1,38 +1,38 @@ - - */ -final class SymmetricException extends \Gishiki\Core\Exception -{ - /** - * Create the symmetric-related exception. - * - * @param string $message the error message - * @param int $errorCode the symmetric encryption error code - */ - public function __construct($message, $errorCode) - { - parent::__construct($message, $errorCode); - } -} + + */ +final class SymmetricException extends \Gishiki\Core\Exception +{ + /** + * Create the symmetric-related exception. + * + * @param string $message the error message + * @param int $errorCode the symmetric encryption error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } +} diff --git a/Gishiki/Security/Hashing/Algorithm.php b/Gishiki/Security/Hashing/Algorithm.php index c590f5ea..0436d8b7 100644 --- a/Gishiki/Security/Hashing/Algorithm.php +++ b/Gishiki/Security/Hashing/Algorithm.php @@ -1,263 +1,338 @@ - - */ -abstract class Algorithm -{ - /************************************************************************** - * Common hashing algorithms * - **************************************************************************/ - const CRC32 = 'crc32'; - const MD4 = 'md4'; - const MD5 = 'md5'; - const SHA1 = 'sha1'; - const SHA256 = 'sha256'; - const SHA328 = 'sha384'; - const SHA512 = 'sha512'; - const ROT13 = 'rot13'; - const BCRYPT = 'bcrypt'; - - /** - * Generate the message digest for the given message using the OpenSSL library - * - * An example usage is: - * - * - * $message = "this is the message to be hashed"; - * - * $test_gishiki_md5 = Algorithm::opensslHash($message, Algorithm::MD5); - * - * echo "The hash of the message is: $test_gishiki_md5"; - * - * - * This function should be called from an Hasher object. - * - * @param string $message the string to be hashed - * @param string $algorithm the name of the hashing algorithm - * - * @return string the result of the hash algorithm - * - * @throws \InvalidArgumentException the message is given as a non-string or an empty string - * @throws HashingException the error occurred while generating the hash for the given message - */ - public static function opensslHash($message, $algorithm) - { - //check for the message - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); - } - - //calculate the hash for the given message - $result = ((in_array($algorithm, openssl_get_md_methods()))) ? openssl_digest($message, $algorithm, false) : hash($algorithm, $algorithm, false); - - //check for errors - if ($result === false) { - throw new HashingException('An unknown error occurred while generating the hash', 1); - } - - //return the calculated message digest - return $result; - } - - /** - * Check if the digest is the hash of the given message (using OpenSSL algorithms). - * - * This function should be called from an Hasher object. - * - * @param string $message the string to be checked against the message digest - * @param string $digest the message digest to be checked - * @return string the result of the hash algorithm - * - * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string - */ - public static function opensslVerify($message, $digest, $algorithm) { - //check for the message - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); - } - - //check for the digest - if ((!is_string($digest)) || (strlen($digest) <= 0)) { - throw new \InvalidArgumentException('The message digest to be checked must be given as a valid non-empty string'); - } - - return (strcmp(self::opensslHash($message, $algorithm), $digest) == 0); - } - - /** - * Generate the rot13 for the given message. - * - * An example usage is: - * - * - * echo "You should watch Star Wars VII to find out that " . Algorithm::rot13Hash("Han Solo dies.", 'rot13'); - * - * - * This function should be called from an Hasher object. - * - * @param string $message the string to be hashed - * - * @return string the result of the hash algorithm - * - * @throws \InvalidArgumentException the message is given as a non-string or an empty string - */ - public static function rot13Hash($message) { - //check for the message - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); - } - - return str_rot13($message); - } - - /** - * Check if the digest is rot13 hash of the given message. - * - * This function should be called from an Hasher object. - * - * @param string $message the string to be checked against the message digest - * @param string $digest the message digest to be checked - * @return string the result of the hash algorithm - * - * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string - */ - public static function rot13Verify($message, $digest) { - //check for the message - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); - } - - //check for the digest - if ((!is_string($digest)) || (strlen($digest) <= 0)) { - throw new \InvalidArgumentException('The message digest to be checked must be given as a valid non-empty string'); - } - - return (strcmp(str_rot13($digest), $message) == 0); - } - - /** - * Generate the message digest for the given message using the default PHP bcrypt implementation. - * - * The BCrypt algorithm is thought to provide a secure way of storing passwords. - * This function should be *NEVER* called directly: use an instance of the Hasher class! - * - * @param string $message the string to be hashed - * - * @return string the result of the hash algorithm - * - * @throws \InvalidArgumentException the message is given as a non-string or an empty string - * @throws HashingException the error occurred while generating the hash for the given message - */ - public static function bcryptHash($message) { - //check for the message - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); - } - - $result = password_hash($message, PASSWORD_BCRYPT); - - if ($result === false) { - throw new HashingException('An unknown error occurred while generating the hash', 1); - } - - return $result; - } - - /** - * Check if the digest is bcrypt hash of the given message. - * - * This function should be called from an Hasher object. - * - * @param string $message the string to be checked against the message digest - * @param string $digest the message digest to be checked - * @return string the result of the hash algorithm - * - * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string - */ - public static function bcryptVerify($message, $digest) { - //check for the message - if ((!is_string($message)) || (strlen($message) <= 0)) { - throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); - } - - //check for the digest - if ((!is_string($digest)) || (strlen($digest) <= 0)) { - throw new \InvalidArgumentException('The message digest to be checked must be given as a valid non-empty string'); - } - - return password_verify($message, $digest); - } - - /** - * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt. - * - * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt - * - * This implementation of PBKDF2 was originally created by https://defuse.ca - * With improvements by http://www.variations-of-shadow.com - * - * @param string $password the password - * @param string $salt a salt that is unique to the password - * @param string $keyLength the length of the derived key in bytes - * @param string $count iteration count. Higher is better, but slower. Recommended: At least 1000 - * @param string $algorithm the hash algorithm to use. Recommended: SHA256 - * - * @return string the key derived from the password and salt - * - * @throws \InvalidArgumentException invalid arguments have been passed - * @throws HashingException the error occurred while generating the requested hashing algorithm - */ - public static function pbkdf2($password, $salt, $keyLength, $count, $algorithm = self::SHA1) - { - if ((!is_integer($count)) || ($count <= 0)) { - throw new \InvalidArgumentException('The iteration number for the PBKDF2 function must be a positive non-zero integer', 2); - } - - if ((!is_integer($keyLength)) || ($keyLength <= 0)) { - throw new \InvalidArgumentException('The resulting key length for the PBKDF2 function must be a positive non-zero integer', 2); - } - - if ((!is_string($algorithm)) || (strlen($algorithm) <= 0)) { - throw new \InvalidArgumentException('The hashing algorithm for the PBKDF2 function must be a non-empty string', 2); - } - - //an algorithm is represented as a string of only lowercase chars - $algorithm = strtolower($algorithm); - - //the raw output of the max legth (beyond the $keyLength algorithm) - $output = ''; - - /* execute the native openssl_pbkdf2 */ - - //check if the algorithm is valid - if (!in_array($algorithm, openssl_get_md_methods(true), true)) { - throw new HashingException('Invalid algorithm: the choosen algorithm is not valid for the PBKDF2 function', 2); - } - - $output = openssl_pbkdf2($password, $salt, $keyLength, $count, $algorithm); - - return bin2hex(substr($output, 0, $keyLength)); - } -} + + */ +abstract class Algorithm +{ + /************************************************************************** + * Common hashing algorithms * + **************************************************************************/ + const CRC32 = 'crc32'; + const MD4 = 'md4'; + const MD5 = 'md5'; + const SHA1 = 'sha1'; + const SHA256 = 'sha256'; + const SHA328 = 'sha384'; + const SHA512 = 'sha512'; + const ROT13 = 'rot13'; + const BCRYPT = 'bcrypt'; + const PBKDF2 = 'pbkdf2'; + + /** + * Generate the message digest for the given message using the OpenSSL library + * + * An example usage is: + * + * + * $message = "this is the message to be hashed"; + * + * $test_gishiki_md5 = Algorithm::opensslHash($message, Algorithm::MD5); + * + * echo "The hash of the message is: $test_gishiki_md5"; + * + * + * This function should be called from an Hasher object. + * + * @param string $message the string to be hashed + * @param string $algorithm the name of the hashing algorithm + * + * @return string the result of the hash algorithm + * + * @throws \InvalidArgumentException the message is given as a non-string or an empty string + * @throws HashingException the error occurred while generating the hash for the given message + */ + public static function opensslHash($message, $algorithm) + { + //check for the message + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); + } + + //calculate the hash for the given message + $result = ((in_array($algorithm, openssl_get_md_methods()))) ? openssl_digest($message, $algorithm, false) : hash($algorithm, $algorithm, false); + + //check for errors + if ($result === false) { + throw new HashingException('An unknown error occurred while generating the hash', 1); + } + + //return the calculated message digest + return $result; + } + + /** + * Check if the digest is the hash of the given message (using OpenSSL algorithms). + * + * This function should be called from an Hasher object. + * + * @param string $message the string to be checked against the message digest + * @param string $digest the message digest to be checked + * @return string the result of the hash algorithm + * + * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string + */ + public static function opensslVerify($message, $digest, $algorithm) + { + //check for the message + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); + } + + //check for the digest + if ((!is_string($digest)) || (strlen($digest) <= 0)) { + throw new \InvalidArgumentException('The message digest to be checked must be given as a valid non-empty string'); + } + + return (strcmp(self::opensslHash($message, $algorithm), $digest) == 0); + } + + /** + * Generate the rot13 for the given message. + * + * An example usage is: + * + * + * echo "You should watch Star Wars VII to find out that " . Algorithm::rot13Hash("Han Solo dies.", 'rot13'); + * + * + * This function should be called from an Hasher object. + * + * @param string $message the string to be hashed + * + * @return string the result of the hash algorithm + * + * @throws \InvalidArgumentException the message is given as a non-string or an empty string + */ + public static function rot13Hash($message) + { + //check for the message + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); + } + + return str_rot13($message); + } + + /** + * Check if the digest is rot13 hash of the given message. + * + * This function should be called from an Hasher object. + * + * @param string $message the string to be checked against the message digest + * @param string $digest the message digest to be checked + * @return string the result of the hash algorithm + * + * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string + */ + public static function rot13Verify($message, $digest) + { + //check for the message + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); + } + + //check for the digest + if ((!is_string($digest)) || (strlen($digest) <= 0)) { + throw new \InvalidArgumentException('The message digest to be checked must be given as a valid non-empty string'); + } + + return (strcmp(str_rot13($digest), $message) == 0); + } + + /** + * Generate the message digest for the given message using the default PHP bcrypt implementation. + * + * The BCrypt algorithm is thought to provide a secure way of storing passwords. + * This function should be *NEVER* called directly: use an instance of the Hasher class! + * + * @param string $message the string to be hashed + * + * @return string the result of the hash algorithm + * + * @throws \InvalidArgumentException the message is given as a non-string or an empty string + * @throws HashingException the error occurred while generating the hash for the given message + */ + public static function bcryptHash($message) + { + //check for the message + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); + } + + $result = password_hash($message, PASSWORD_BCRYPT); + + if ($result === false) { + throw new HashingException('An unknown error occurred while generating the hash', 1); + } + + return $result; + } + + /** + * Check if the digest is bcrypt hash of the given message. + * + * This function should be called from an Hasher object. + * + * @param string $message the string to be checked against the message digest + * @param string $digest the message digest to be checked + * @return string the result of the hash algorithm + * + * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string + */ + public static function bcryptVerify($message, $digest) + { + //check for the message + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); + } + + //check for the digest + if ((!is_string($digest)) || (strlen($digest) <= 0)) { + throw new \InvalidArgumentException('The message digest to be checked must be given as a valid non-empty string'); + } + + return password_verify($message, $digest); + } + + /** + * Generate the message digest for the given message using the pbkdf2 algorithm. + * + * The pbkdf2 algorithm is thought to be slow and produce an hash. + * This function should be *NEVER* called directly: use an instance of the Hasher class! + * + * @param string $message the string to be hashed + * + * @return string the result of the hash algorithm + * + * @throws \InvalidArgumentException the message is given as a non-string or an empty string + * @throws HashingException the error occurred while generating the hash for the given message + */ + public static function pbkdf2Hash($message) + { + $it = 16777216; + $hashingAlgorithm = 'sha512'; + + $salt = Base64::encode(openssl_random_pseudo_bytes(64)); + + $hash = Base64::encode(self::pbkdf2($message, $salt, 64, $it, $hashingAlgorithm)); + + return '|pbkdf2%'.$hashingAlgorithm.'%'.$it.'%'.$salt.'%'.$hash; + } + + /** + * Check if the digest is the pbkdf2 hash of the given message. + * + * This function should be called from an Hasher object. + * + * @param string $message the string to be checked against the message digest + * @param string $digest the message digest to be checked + * @return string the result of the hash algorithm + * + * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string + */ + public static function pbkdf2Verify($message, $digest) + { + //check for the message + if ((!is_string($message)) || (strlen($message) <= 0)) { + throw new \InvalidArgumentException('The message to be hashed must be given as a valid non-empty string'); + } + + //check for the digest + if ((!is_string($digest)) || (strlen($digest) <= 0)) { + throw new \InvalidArgumentException('The message digest to be checked must be given as a valid non-empty string'); + } + + $params = explode('%', $digest); + + if (count($params) < 5) { + return false; + } + + if (strcmp($params[0], "|pbkdf2") != 0) { + return false; + } + + $hashingAlgorithm = $params[1]; + $it = intval($params[2]); + $salt = $params[3]; + + $hashRecalc = Base64::encode(self::pbkdf2($message, $salt, 64, $it, $hashingAlgorithm)); + + return (strcmp($digest, $hashRecalc) == 0); + } + + /** + * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt. + * + * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt + * + * This implementation of PBKDF2 was originally created by https://defuse.ca + * With improvements by http://www.variations-of-shadow.com + * + * @param string $password the password + * @param string $salt a salt that is unique to the password + * @param string $keyLength the length of the derived key in bytes + * @param string $count iteration count. Higher is better, but slower. Recommended: At least 1000 + * @param string $algorithm the hash algorithm to use. Recommended: SHA256 + * + * @return string the key derived from the password and salt + * + * @throws \InvalidArgumentException invalid arguments have been passed + * @throws HashingException the error occurred while generating the requested hashing algorithm + */ + public static function pbkdf2($password, $salt, $keyLength, $count, $algorithm = self::SHA256) + { + if ((!is_integer($count)) || ($count <= 0)) { + throw new \InvalidArgumentException('The iteration number for the PBKDF2 function must be a positive non-zero integer', 2); + } + + if ((!is_integer($keyLength)) || ($keyLength <= 0)) { + throw new \InvalidArgumentException('The resulting key length for the PBKDF2 function must be a positive non-zero integer', 2); + } + + if ((!is_string($algorithm)) || (strlen($algorithm) <= 0)) { + throw new \InvalidArgumentException('The hashing algorithm for the PBKDF2 function must be a non-empty string', 2); + } + + //an algorithm is represented as a string of only lowercase chars + $algorithm = strtolower($algorithm); + + //the raw output of the max length (beyond the $keyLength algorithm) + $output = ''; + + /* execute the native openssl_pbkdf2 */ + + //check if the algorithm is valid + if (!in_array($algorithm, openssl_get_md_methods(true), true)) { + throw new HashingException('Invalid algorithm: the choosen algorithm is not valid for the PBKDF2 function', 2); + } + + $output = openssl_pbkdf2($password, $salt, $keyLength, $count, $algorithm); + + return bin2hex(substr($output, 0, $keyLength)); + } +} diff --git a/Gishiki/Security/Hashing/Hasher.php b/Gishiki/Security/Hashing/Hasher.php index e1a89fce..5bc87868 100644 --- a/Gishiki/Security/Hashing/Hasher.php +++ b/Gishiki/Security/Hashing/Hasher.php @@ -1,111 +1,116 @@ - - */ -final class Hasher -{ - /** - * @var integer|null the hashing algorithm, or null on error - */ - private $algorithm = null; - - /** - * @var bool true if the algorithm name has to be passed - */ - private $algorithmRequired = false; - - /** - * @var string the name of the function to be called to produce a message digest - */ - private $hashCallback; - - /** - * @var string the name of the function to be called to verify a message digest - */ - private $verifyCallback; - - /** - * Create an object that provides an easy and unique interface to use any of the supported algorithms - * - * @param string $algorithm the algorithm to be used - * @throws HashingException the given algorithm is unsupported - */ - public function __construct($algorithm = Algorithm::BCRYPT) { - //check if the hashing algorithm is supported - if (strcmp($algorithm, Algorithm::BCRYPT) == 0) { - $this->algorithm = $algorithm; - $this->hashCallback = Algorithm::class."::".Algorithm::BCRYPT."Hash"; - $this->verifyCallback = Algorithm::class."::".Algorithm::BCRYPT."Verify"; - } - else if (strcmp($algorithm, Algorithm::ROT13) == 0) { - $this->algorithm = $algorithm; - $this->hashCallback = Algorithm::class."::".Algorithm::BCRYPT."Hash"; - $this->verifyCallback = Algorithm::class."::".Algorithm::BCRYPT."Verify"; - } - else if ((in_array($algorithm, openssl_get_md_methods())) && (in_array($algorithm, hash_algos()))) { - $this->algorithm = $algorithm; - $this->algorithmRequired = true; - $this->hashCallback = Algorithm::class."::opensslHash"; - $this->verifyCallback = Algorithm::class."::opensslVerify"; - } - - if (is_null($this->algorithm)) { - throw new HashingException('Unsupported hashing algorithm', 0); - } - } - - /** - * Generate the hash of the given message using the chosen algorithm - * - * @param string $message the message to be hashed - * - * @return string the result of the hashing algorithm - * - * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string - * @throws HashingException the error occurred while generating the hash for the given message - */ - public function hash($message) { - $callbackParams = ($this->algorithmRequired) ? [$message, $this->algorithm] : [$message]; - - return call_user_func_array($this->hashCallback, $callbackParams); - } - - /** - * Check the hash of the given message using the chosen algorithm - * - * @param string $message the message to be checked against the digest - * @param string $digest the message digest to be checked - * - * @return bool the result of the check: true on success, false otherwise - * - * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string - * @throws HashingException the error occurred while generating the hash for the given message - */ - public function verify($message, $digest) { - $callbackParams = ($this->algorithmRequired) ? [$message, $digest, $this->algorithm] : [$message, $digest]; - - return call_user_func_array($this->verifyCallback, $callbackParams); - } + + */ +final class Hasher +{ + /** + * @var integer|null the hashing algorithm, or null on error + */ + private $algorithm = null; + + /** + * @var bool true if the algorithm name has to be passed + */ + private $algorithmRequired = false; + + /** + * @var string the name of the function to be called to produce a message digest + */ + private $hashCallback; + + /** + * @var string the name of the function to be called to verify a message digest + */ + private $verifyCallback; + + /** + * Create an object that provides an easy and unique interface to use any of the supported algorithms + * + * @param string $algorithm the algorithm to be used + * @throws HashingException the given algorithm is unsupported + */ + public function __construct($algorithm = Algorithm::BCRYPT) { + //check if the hashing algorithm is supported + if (strcmp($algorithm, Algorithm::BCRYPT) == 0) { + $this->algorithm = $algorithm; + $this->hashCallback = Algorithm::class."::".Algorithm::BCRYPT."Hash"; + $this->verifyCallback = Algorithm::class."::".Algorithm::BCRYPT."Verify"; + } + else if (strcmp($algorithm, Algorithm::PBKDF2) == 0) { + $this->algorithm = $algorithm; + $this->hashCallback = Algorithm::class."::".Algorithm::BCRYPT."Hash"; + $this->verifyCallback = Algorithm::class."::".Algorithm::BCRYPT."Verify"; + } + else if (strcmp($algorithm, Algorithm::ROT13) == 0) { + $this->algorithm = $algorithm; + $this->hashCallback = Algorithm::class."::".Algorithm::BCRYPT."Hash"; + $this->verifyCallback = Algorithm::class."::".Algorithm::BCRYPT."Verify"; + } + else if ((in_array($algorithm, openssl_get_md_methods())) && (in_array($algorithm, hash_algos()))) { + $this->algorithm = $algorithm; + $this->algorithmRequired = true; + $this->hashCallback = Algorithm::class."::opensslHash"; + $this->verifyCallback = Algorithm::class."::opensslVerify"; + } + + if (is_null($this->algorithm)) { + throw new HashingException('Unsupported hashing algorithm', 0); + } + } + + /** + * Generate the hash of the given message using the chosen algorithm + * + * @param string $message the message to be hashed + * + * @return string the result of the hashing algorithm + * + * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string + * @throws HashingException the error occurred while generating the hash for the given message + */ + public function hash($message) { + $callbackParams = ($this->algorithmRequired) ? [$message, $this->algorithm] : [$message]; + + return call_user_func_array($this->hashCallback, $callbackParams); + } + + /** + * Check the hash of the given message using the chosen algorithm + * + * @param string $message the message to be checked against the digest + * @param string $digest the message digest to be checked + * + * @return bool the result of the check: true on success, false otherwise + * + * @throws \InvalidArgumentException the message or the message digest is given as a non-string or an empty string + * @throws HashingException the error occurred while generating the hash for the given message + */ + public function verify($message, $digest) { + $callbackParams = ($this->algorithmRequired) ? [$message, $digest, $this->algorithm] : [$message, $digest]; + + return call_user_func_array($this->verifyCallback, $callbackParams); + } } \ No newline at end of file diff --git a/Gishiki/Security/Hashing/HashingException.php b/Gishiki/Security/Hashing/HashingException.php index 783e035c..3526e8ec 100644 --- a/Gishiki/Security/Hashing/HashingException.php +++ b/Gishiki/Security/Hashing/HashingException.php @@ -1,38 +1,40 @@ - - */ -class HashingException extends \Gishiki\Core\Exception -{ - /** - * Create the hashing-related exception. - * - * @param string $message the error message - * @param int $errorCode the hashing error code - */ - public function __construct($message, $errorCode) - { - parent::__construct($message, $errorCode); - } -} + + */ +class HashingException extends Exception +{ + /** + * Create the hashing-related exception. + * + * @param string $message the error message + * @param int $errorCode the hashing error code + */ + public function __construct($message, $errorCode) + { + parent::__construct($message, $errorCode); + } +} diff --git a/README.md b/README.md index ab93d01d..c3d46bf2 100755 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ | Code Metrics | [![Code Climate](https://codeclimate.com/github/NeroReflex/Gishiki/badges/gpa.svg)](https://codeclimate.com/github/NeroReflex/Gishiki) | | Coverage | [![Test Coverage](https://codeclimate.com/github/NeroReflex/Gishiki/badges/coverage.svg)](https://codeclimate.com/github/NeroReflex/Gishiki/coverage) | | Test | [![Build Status](https://travis-ci.org/NeroReflex/Gishiki.svg?branch=master)](https://travis-ci.org/NeroReflex/Gishiki) | - +| Stable | [![Latest Stable Version](https://poser.pugx.org/neroreflex/gishiki/v/stable)](https://packagist.org/packages/neroreflex/gishiki) | +| Downloads | [![Total Downloads](https://poser.pugx.org/neroreflex/gishiki/downloads)](https://packagist.org/packages/neroreflex/gishiki) | +| License | [![License](https://poser.pugx.org/neroreflex/gishiki/license)](https://packagist.org/packages/neroreflex/gishiki) | _*Gishiki*_: a modern and elegant MVC framework for modern versions of PHP. @@ -29,8 +31,7 @@ Do you want to have it working in the less time possible? OK! ```shell composer init # setup your new project -nano composer.json # or any other text editor to add "neroreflex/gishiki": "dev-master" on "require" -composer install --no-dev +composer require neroreflex/gishiki ./vendor/bin/gishiki new application ``` diff --git a/TODO b/TODO index bfc28a40..b8715955 100644 --- a/TODO +++ b/TODO @@ -5,4 +5,5 @@ - increase code coverage! - implement a fast ORM - implement a fast templating engine +- write an input validator - better datatypes for RDBMS \ No newline at end of file diff --git a/Vagrantfile b/Vagrantfile index 2531a140..3a77e4d3 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,55 +1,55 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -Vagrant.configure(2) do |config| - # Every Vagrant development environment requires a box. You can search for - # boxes at https://atlas.hashicorp.com/search. - config.vm.box = "NeroReflex/Gishiki" - - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - config.vm.network "public_network" - - # Provider-specific configuration so you can fine-tune various - # backing providers for Vagrant. These expose provider-specific options. - config.vm.provider "virtualbox" do |vb| - # Display the VirtualBox GUI when booting the machine - vb.gui = false - - vb.name = "Gishiki" - # Customize the amount of memory on the VM: - vb.memory = "1024" - - # Avoid ubuntu network problems at boot - vb.customize ["modifyvm", :id, "--cableconnected1", "on"] - - # Limit CPU usage - vb.customize ["modifyvm", :id, "--cpuexecutioncap", "100"] - - # Open and forward port for admin tools - config.vm.network "forwarded_port", guest: 88, host: 8088 - end - - # Enable USB Controller on VirtualBox - config.vm.provider "virtualbox" do |vb| - vb.customize ["modifyvm", :id, "--usb", "on"] - vb.customize ["modifyvm", :id, "--usbehci", "on"] - end - - ############################################################### - config.vm.provision "shell", inline: <<-SHELL - composer self-update - - # link volume to home user folder - ln -s /vagrant Gishiki - - # installing the framework - cd /vagrant - composer install - - SHELL - - - -end +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure(2) do |config| + # Every Vagrant development environment requires a box. You can search for + # boxes at https://atlas.hashicorp.com/search. + config.vm.box = "NeroReflex/Gishiki" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + config.vm.network "public_network" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + config.vm.provider "virtualbox" do |vb| + # Display the VirtualBox GUI when booting the machine + vb.gui = false + + vb.name = "Gishiki" + # Customize the amount of memory on the VM: + vb.memory = "1024" + + # Avoid ubuntu network problems at boot + vb.customize ["modifyvm", :id, "--cableconnected1", "on"] + + # Limit CPU usage + vb.customize ["modifyvm", :id, "--cpuexecutioncap", "100"] + + # Open and forward port for admin tools + config.vm.network "forwarded_port", guest: 88, host: 8088 + end + + # Enable USB Controller on VirtualBox + #config.vm.provider "virtualbox" do |vb| + # vb.customize ["modifyvm", :id, "--usb", "on"] + # vb.customize ["modifyvm", :id, "--usbehci", "on"] + #end + + ############################################################### + config.vm.provision "shell", inline: <<-SHELL + composer self-update + + # link volume to home user folder + ln -s /vagrant Gishiki + + # installing the framework + cd /vagrant + composer install + + SHELL + + + +end diff --git a/composer.json b/composer.json index b5e76207..c7a6df4f 100755 --- a/composer.json +++ b/composer.json @@ -22,15 +22,16 @@ "ext-libxml": "*", "ext-curl": "*", "ext-SimpleXML": "*", - "psr/http-message": "^1.0", "ext-Reflection": "*", - "symfony/yaml": "v2.8.6", + "psr/http-message": "^1.0", + "zendframework/zend-diactoros": ">=1.6.0", + "symfony/yaml": "^3.3.0", "monolog/monolog": "^1.22" }, "require-dev": { - "codacy/coverage": "dev-master", - "codeclimate/php-test-reporter": "dev-master", + "codacy/coverage": ">=1.3.0", + "codeclimate/php-test-reporter": ">=v0.4.4", "squizlabs/php_codesniffer": "^2.5", "phpunit/phpunit": ">=6.0", "ext-mbstring": "*" @@ -61,6 +62,6 @@ ], "scripts": { - "test": "@php vendor/bin/phpunit --configuration phpunit.xml --coverage-clover build/logs/clover.xml" + "test": "./vendor/bin/phpunit --configuration phpunit.xml --coverage-clover build/logs/clover.xml" } } diff --git a/phpcs.xml b/phpcs.xml index 2be7b0a4..003f4f48 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,17 +1,17 @@ - - - Gishiki coding standard - - - - - - - - - - - - Gishiki - tests + + + Gishiki coding standard + + + + + + + + + + + + Gishiki + tests \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index 56d6064b..0cd726e0 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,29 +1,29 @@ - - - - - - ./tests/ - - - - - - ./Gishiki/ - - + + + + + + ./tests/ + + + + + + ./Gishiki/ + + \ No newline at end of file diff --git a/tests/Algorithms/Base64Test.php b/tests/Algorithms/Base64Test.php index c229451f..eab42ffe 100644 --- a/tests/Algorithms/Base64Test.php +++ b/tests/Algorithms/Base64Test.php @@ -1,72 +1,72 @@ -expectException(\InvalidArgumentException::class); - Base64::encode(1); - } - - public function testDecodeBadMessage() - { - $this->expectException(\InvalidArgumentException::class); - Base64::decode(1); - } - - public function testURLUnsafeEncodes() - { - for ($i = 1; $i < 100; ++$i) { - $message = bin2hex(openssl_random_pseudo_bytes($i)); - - $binsafe_message = Base64::encode($message, false); - - $this->assertEquals($message, Base64::decode($binsafe_message)); - } - } - - public function testURLSafeEncodes() - { - for ($i = 1; $i < 100; ++$i) { - $message = bin2hex(openssl_random_pseudo_bytes($i)); - - $urlsafe_message = Base64::encode($message, true); - - $this->assertEquals($urlsafe_message, urlencode($urlsafe_message)); - - $this->assertEquals($message, Base64::decode($urlsafe_message)); - } - } - - public function testCompatibility() - { - for ($i = 1; $i < 100; ++$i) { - $message = bin2hex(openssl_random_pseudo_bytes($i)); - - $safe_message = base64_encode($message); - - $this->assertEquals($message, Base64::decode($safe_message)); - } - } -} +expectException(\InvalidArgumentException::class); + Base64::encode(1); + } + + public function testDecodeBadMessage() + { + $this->expectException(\InvalidArgumentException::class); + Base64::decode(1); + } + + public function testURLUnsafeEncodes() + { + for ($i = 1; $i < 100; ++$i) { + $message = bin2hex(openssl_random_pseudo_bytes($i)); + + $binsafe_message = Base64::encode($message, false); + + $this->assertEquals($message, Base64::decode($binsafe_message)); + } + } + + public function testURLSafeEncodes() + { + for ($i = 1; $i < 100; ++$i) { + $message = bin2hex(openssl_random_pseudo_bytes($i)); + + $urlsafe_message = Base64::encode($message, true); + + $this->assertEquals($urlsafe_message, urlencode($urlsafe_message)); + + $this->assertEquals($message, Base64::decode($urlsafe_message)); + } + } + + public function testCompatibility() + { + for ($i = 1; $i < 100; ++$i) { + $message = bin2hex(openssl_random_pseudo_bytes($i)); + + $safe_message = base64_encode($message); + + $this->assertEquals($message, Base64::decode($safe_message)); + } + } +} diff --git a/tests/Algorithms/Collections/GenericCollectionTest.php b/tests/Algorithms/Collections/GenericCollectionTest.php index 6386e992..0e347036 100644 --- a/tests/Algorithms/Collections/GenericCollectionTest.php +++ b/tests/Algorithms/Collections/GenericCollectionTest.php @@ -1,180 +1,180 @@ -expectException(\InvalidArgumentException::class); - - new GenericCollection(null); - } - - public function testEmpty() - { - $collection = new GenericCollection([ - 5, 6, 'ciao', [true, false, true, true] - ]); - - $this->assertEquals(false, $collection->empty()); - $collection->clear(); - $this->assertEquals(true, $collection->empty()); - } - - public function testIteration() - { - //this is the native collection - $native_collection = [ - 'test1' => 7, - 'test2' => 'my string', - 0 => 'first', - 1 => 'third', - 'test3' => json_encode( - [ - 'author' => 'Benato Denis', - 'title' => 'Example Book', - 'tags' => [ - 'development', 'PHP', 'framework', 'Gishiki', - ], - ]), ]; - - //build a managed collection from the native one - $collection_to_test = new GenericCollection($native_collection); - - //try rebuilding the collection iterating over each element - $rebuilt_collection = []; - foreach ($collection_to_test->getIterator() as $key => $value) { - $rebuilt_collection[$key] = $value; - } - - //test if the collection rebuild process has given the right result - $this->assertEquals($rebuilt_collection, $native_collection); - } - - public function testObjectNotation() - { - $collection = new GenericCollection([ - 'tags' => 'tag1' - ]); - - $this->assertEquals('tag1', $collection->tags); - - $collection->tags = ['tag1', 'tag2']; - - $this->assertEquals(['tag1', 'tag2'], $collection->tags); - } - - public function testClear() - { - $arr = [ - "foo" => "bar", - 42 => 24, - "multi" => [ - "dimensional" => [ - "array" => "foo" - ] - ]]; - - $collection = new GenericCollection($arr); - - $this->assertEquals($arr, $collection->all()); - - $collection->clear(); - - $this->assertEquals([], $collection->all()); - } - - public function testKeys() - { - $collection = new GenericCollection([ - "key1" => "val1", - "Key2" => "val2", - "Key3" => [ - "Val3", - null - ] - ]); - - $this->assertEquals(["key1", "Key2", "Key3"], $collection->keys()); - } - - public function testReplace() - { - $arr = [ - "v1" => 10, - "v2" => 3, - "v3" => 4.5, - "v4" => 9, - "v5" => 430, - "v7" => 0.3 - ]; - - $collection = new GenericCollection($arr); - - foreach ($arr as &$current) { - $current += 1.25; - } - - $collection->replace($arr); - - $this->assertEquals($arr, $collection->all()); - - $this->assertEquals(6, $collection->count()); - } - - public function testNativeAsClassCall() - { - //this is the native collection - $native_collection = [ - 'test1' => 7, - 'test2' => 'my string', - 'test3' => json_encode( - [ - 'author' => 'Benato Denis', - 'title' => 'Example Book', - 'tags' => [ - 'development', 'PHP', 'framework', 'Gishiki', - ], - ]), - 20 => 'testkey', - ]; - - //build a managed collection from the native one - $collection_to_test = new GenericCollection($native_collection); - - //try rebuilding the collection iterating over each element - $rebuilt_collection = $collection_to_test(); - - //test if the collection rebuild process has given the right result - $this->assertEquals($native_collection, $rebuilt_collection); - - $this->assertEquals(true, $collection_to_test->offsetExists('test1')); - - $collection_to_test->offsetUnset('test1'); - - $this->assertEquals(false, $collection_to_test->offsetExists('test1')); - - $collection_to_test->offsetSet('test1', false); - - $this->assertEquals(true, $collection_to_test->offsetExists('test1')); - } -} +expectException(\InvalidArgumentException::class); + + new GenericCollection(null); + } + + public function testEmpty() + { + $collection = new GenericCollection([ + 5, 6, 'ciao', [true, false, true, true] + ]); + + $this->assertEquals(false, $collection->empty()); + $collection->clear(); + $this->assertEquals(true, $collection->empty()); + } + + public function testIteration() + { + //this is the native collection + $native_collection = [ + 'test1' => 7, + 'test2' => 'my string', + 0 => 'first', + 1 => 'third', + 'test3' => json_encode( + [ + 'author' => 'Benato Denis', + 'title' => 'Example Book', + 'tags' => [ + 'development', 'PHP', 'framework', 'Gishiki', + ], + ]), ]; + + //build a managed collection from the native one + $collection_to_test = new GenericCollection($native_collection); + + //try rebuilding the collection iterating over each element + $rebuilt_collection = []; + foreach ($collection_to_test->getIterator() as $key => $value) { + $rebuilt_collection[$key] = $value; + } + + //test if the collection rebuild process has given the right result + $this->assertEquals($rebuilt_collection, $native_collection); + } + + public function testObjectNotation() + { + $collection = new GenericCollection([ + 'tags' => 'tag1' + ]); + + $this->assertEquals('tag1', $collection->tags); + + $collection->tags = ['tag1', 'tag2']; + + $this->assertEquals(['tag1', 'tag2'], $collection->tags); + } + + public function testClear() + { + $arr = [ + "foo" => "bar", + 42 => 24, + "multi" => [ + "dimensional" => [ + "array" => "foo" + ] + ]]; + + $collection = new GenericCollection($arr); + + $this->assertEquals($arr, $collection->all()); + + $collection->clear(); + + $this->assertEquals([], $collection->all()); + } + + public function testKeys() + { + $collection = new GenericCollection([ + "key1" => "val1", + "Key2" => "val2", + "Key3" => [ + "Val3", + null + ] + ]); + + $this->assertEquals(["key1", "Key2", "Key3"], $collection->keys()); + } + + public function testReplace() + { + $arr = [ + "v1" => 10, + "v2" => 3, + "v3" => 4.5, + "v4" => 9, + "v5" => 430, + "v7" => 0.3 + ]; + + $collection = new GenericCollection($arr); + + foreach ($arr as &$current) { + $current += 1.25; + } + + $collection->replace($arr); + + $this->assertEquals($arr, $collection->all()); + + $this->assertEquals(6, $collection->count()); + } + + public function testNativeAsClassCall() + { + //this is the native collection + $native_collection = [ + 'test1' => 7, + 'test2' => 'my string', + 'test3' => json_encode( + [ + 'author' => 'Benato Denis', + 'title' => 'Example Book', + 'tags' => [ + 'development', 'PHP', 'framework', 'Gishiki', + ], + ]), + 20 => 'testkey', + ]; + + //build a managed collection from the native one + $collection_to_test = new GenericCollection($native_collection); + + //try rebuilding the collection iterating over each element + $rebuilt_collection = $collection_to_test(); + + //test if the collection rebuild process has given the right result + $this->assertEquals($native_collection, $rebuilt_collection); + + $this->assertEquals(true, $collection_to_test->offsetExists('test1')); + + $collection_to_test->offsetUnset('test1'); + + $this->assertEquals(false, $collection_to_test->offsetExists('test1')); + + $collection_to_test->offsetSet('test1', false); + + $this->assertEquals(true, $collection_to_test->offsetExists('test1')); + } +} diff --git a/tests/Algorithms/Collections/SerializationCollectionTest.php b/tests/Algorithms/Collections/SerializationCollectionTest.php index 3a7fa048..0b2840c3 100644 --- a/tests/Algorithms/Collections/SerializationCollectionTest.php +++ b/tests/Algorithms/Collections/SerializationCollectionTest.php @@ -1,343 +1,343 @@ - 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]); - - $this->assertEquals($collection, SerializableCollection::deserialize($collection)); - } - - public function testCollectionDefault() - { - $expected = [ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]; - - $collection = new SerializableCollection($expected); - - $this->assertEquals($expected, SerializableCollection::deserialize((string) $collection)->all()); - } - - public function testCollection() - { - $collection = new SerializableCollection(); - $collection->set('a', 1); - $collection->set('b', 5.50); - $collection->set('c', 'srf'); - $collection->set('e', true); - $collection->set('f', [1, 2, 3, 4]); - - $this->assertEquals([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ], $collection->all()); - } - - public function testJsonSerialization() - { - $collection = new SerializableCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]); - - $serializationResult = /*json_encode*/($collection->serialize()); - $serialization = json_decode($serializationResult, true); - - $this->assertEquals([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ], $serialization); - } - - public function testXmlSerialization() - { - $collection = new SerializableCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]); - - $serializationResult = $collection->serialize(SerializableCollection::XML); - $serialization = SerializableCollection::deserialize($serializationResult, SerializableCollection::XML); - - $this->assertEquals([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ], $serialization->all()); - } - - public function testYamlSerialization() - { - $collection = new SerializableCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]); - - $serializationResult = $collection->serialize(SerializableCollection::YAML); - $serialization = \Symfony\Component\Yaml\Yaml::parse($serializationResult); - - $this->assertEquals([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ], $serialization); - } - - public function testJsonDeserialization() - { - $this->assertEquals(new SerializableCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]), SerializableCollection::deserialize('{"a":1,"b":5.5,"c":"srf","e":true,"f":[1,2,3,4]}')); - } - - public function testYamlDeserialization() - { - $yaml = <<<'EOD' ---- !clarkevans.com/^invoice -invoice: 34843 -date: "2001-01-23" -bill-to: &id001 - given: Chris - family: Dumars - address: - lines: |- - 458 Walkman Dr. Suite #292 - city: Royal Oak - state: MI - postal: 48046 -ship-to: *id001 -product: -- sku: BL394D - quantity: 4 - description: Basketball - price: 450 -- sku: BL4438H - quantity: 1 - description: Super Hoop - price: 2392 -tax: 251.420000 -total: 4443.520000 -comments: Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338. -... -EOD; - - $this->assertEquals([ - 'invoice' => 34843, - 'date' => '2001-01-23', - 'bill-to' => [ - 'given' => 'Chris', - 'family' => 'Dumars', - 'address' => [ - 'lines' => '458 Walkman Dr. Suite #292', - 'city' => 'Royal Oak', - 'state' => 'MI', - 'postal' => 48046, - ], - ], - 'ship-to' => [ - 'given' => 'Chris', - 'family' => 'Dumars', - 'address' => [ - 'lines' => '458 Walkman Dr. Suite #292', - 'city' => 'Royal Oak', - 'state' => 'MI', - 'postal' => 48046, - ], - ], - 'product' => [ - 0 => [ - 'sku' => 'BL394D', - 'quantity' => 4, - 'description' => 'Basketball', - 'price' => 450, - ], - 1 => [ - 'sku' => 'BL4438H', - 'quantity' => 1, - 'description' => 'Super Hoop', - 'price' => 2392, - ], - ], - 'tax' => 251.42, - 'total' => 4443.52, - 'comments' => 'Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.', -], SerializableCollection::deserialize($yaml, SerializableCollection::YAML)->all()); - } - - public function testLoadFromAnotherGenericCollection() - { - $collection = new SerializableCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]); - - $this->assertEquals($collection, new SerializableCollection($collection)); - } - - public function testLoadFromAnotherSerializableCollection() - { - $collection = new GenericCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]); - - $this->assertEquals($collection->all(), (new SerializableCollection($collection))->all()); - } - - public function testNotStringJsonDeserialization() - { - $this->expectException(DeserializationException::class); - SerializableCollection::deserialize(9.70, SerializableCollection::JSON); - } - - public function testNotStringXmlDeserialization() - { - $this->expectException(DeserializationException::class); - SerializableCollection::deserialize(new \stdClass(), SerializableCollection::XML); - } - - public function testNotStringYamlDeserialization() - { - $this->expectException(DeserializationException::class); - SerializableCollection::deserialize(false, SerializableCollection::YAML); - } - - public function testBadDeserializator() - { - $this->expectException(DeserializationException::class); - SerializableCollection::deserialize('{---', 'this cannot be a valid deserializator!'); - } - - public function testBadYamlDeserialization() - { - $this->expectException(DeserializationException::class); - $badYaml = -'x -language:'; - - SerializableCollection::deserialize($badYaml, SerializableCollection::YAML); - } - - public function testBadXmlDeserialization() - { - $this->expectException(DeserializationException::class); - $badXml = <<<'XML' -probl - - -XML; - - SerializableCollection::deserialize($badXml, SerializableCollection::XML); - } - - public function testBadJsonDeserialization() - { - $this->expectException(DeserializationException::class); - SerializableCollection::deserialize('bad json', SerializableCollection::JSON); - } - - public function testUnknownSerializationException() - { - $this->expectException(SerializationException::class); - (new SerializableCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]))->serialize(255); - } - - public function testInvalidSerializationException() - { - $this->expectException(\InvalidArgumentException::class); - (new SerializableCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ]))->serialize('WTF?!?'); - } - - public function testBadSerializationException() - { - $this->expectException(SerializationException::class); - $obj = (new SerializableCollection([ - 'a' => 1, - 'b' => 5.50, - 'c' => 'srf', - 'e' => true, - 'f' => [1, 2, 3, 4], - ])); - - $obj->set('bad_chars', "\xB1\x31"); - - $obj->serialize(); - } -} + 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]); + + $this->assertEquals($collection, SerializableCollection::deserialize($collection)); + } + + public function testCollectionDefault() + { + $expected = [ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]; + + $collection = new SerializableCollection($expected); + + $this->assertEquals($expected, SerializableCollection::deserialize((string) $collection)->all()); + } + + public function testCollection() + { + $collection = new SerializableCollection(); + $collection->set('a', 1); + $collection->set('b', 5.50); + $collection->set('c', 'srf'); + $collection->set('e', true); + $collection->set('f', [1, 2, 3, 4]); + + $this->assertEquals([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ], $collection->all()); + } + + public function testJsonSerialization() + { + $collection = new SerializableCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]); + + $serializationResult = /*json_encode*/($collection->serialize()); + $serialization = json_decode($serializationResult, true); + + $this->assertEquals([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ], $serialization); + } + + public function testXmlSerialization() + { + $collection = new SerializableCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]); + + $serializationResult = $collection->serialize(SerializableCollection::XML); + $serialization = SerializableCollection::deserialize($serializationResult, SerializableCollection::XML); + + $this->assertEquals([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ], $serialization->all()); + } + + public function testYamlSerialization() + { + $collection = new SerializableCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]); + + $serializationResult = $collection->serialize(SerializableCollection::YAML); + $serialization = \Symfony\Component\Yaml\Yaml::parse($serializationResult); + + $this->assertEquals([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ], $serialization); + } + + public function testJsonDeserialization() + { + $this->assertEquals(new SerializableCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]), SerializableCollection::deserialize('{"a":1,"b":5.5,"c":"srf","e":true,"f":[1,2,3,4]}')); + } + + public function testYamlDeserialization() + { + $yaml = <<<'EOD' +--- !clarkevans.com/^invoice +invoice: 34843 +date: "2001-01-23" +bill-to: &id001 + given: Chris + family: Dumars + address: + lines: |- + 458 Walkman Dr. Suite #292 + city: Royal Oak + state: MI + postal: 48046 +ship-to: *id001 +product: +- sku: BL394D + quantity: 4 + description: Basketball + price: 450 +- sku: BL4438H + quantity: 1 + description: Super Hoop + price: 2392 +tax: 251.420000 +total: 4443.520000 +comments: Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338. +... +EOD; + + $this->assertEquals([ + 'invoice' => 34843, + 'date' => '2001-01-23', + 'bill-to' => [ + 'given' => 'Chris', + 'family' => 'Dumars', + 'address' => [ + 'lines' => '458 Walkman Dr. Suite #292', + 'city' => 'Royal Oak', + 'state' => 'MI', + 'postal' => 48046, + ], + ], + 'ship-to' => [ + 'given' => 'Chris', + 'family' => 'Dumars', + 'address' => [ + 'lines' => '458 Walkman Dr. Suite #292', + 'city' => 'Royal Oak', + 'state' => 'MI', + 'postal' => 48046, + ], + ], + 'product' => [ + 0 => [ + 'sku' => 'BL394D', + 'quantity' => 4, + 'description' => 'Basketball', + 'price' => 450, + ], + 1 => [ + 'sku' => 'BL4438H', + 'quantity' => 1, + 'description' => 'Super Hoop', + 'price' => 2392, + ], + ], + 'tax' => 251.42, + 'total' => 4443.52, + 'comments' => 'Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.', +], SerializableCollection::deserialize($yaml, SerializableCollection::YAML)->all()); + } + + public function testLoadFromAnotherGenericCollection() + { + $collection = new SerializableCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]); + + $this->assertEquals($collection, new SerializableCollection($collection)); + } + + public function testLoadFromAnotherSerializableCollection() + { + $collection = new GenericCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]); + + $this->assertEquals($collection->all(), (new SerializableCollection($collection))->all()); + } + + public function testNotStringJsonDeserialization() + { + $this->expectException(DeserializationException::class); + SerializableCollection::deserialize(9.70, SerializableCollection::JSON); + } + + public function testNotStringXmlDeserialization() + { + $this->expectException(DeserializationException::class); + SerializableCollection::deserialize(new \stdClass(), SerializableCollection::XML); + } + + public function testNotStringYamlDeserialization() + { + $this->expectException(DeserializationException::class); + SerializableCollection::deserialize(false, SerializableCollection::YAML); + } + + public function testBadDeserializator() + { + $this->expectException(DeserializationException::class); + SerializableCollection::deserialize('{---', 'this cannot be a valid deserializator!'); + } + + public function testBadYamlDeserialization() + { + $this->expectException(DeserializationException::class); + $badYaml = +'x +language:'; + + SerializableCollection::deserialize($badYaml, SerializableCollection::YAML); + } + + public function testBadXmlDeserialization() + { + $this->expectException(DeserializationException::class); + $badXml = <<<'XML' +probl + + +XML; + + SerializableCollection::deserialize($badXml, SerializableCollection::XML); + } + + public function testBadJsonDeserialization() + { + $this->expectException(DeserializationException::class); + SerializableCollection::deserialize('bad json', SerializableCollection::JSON); + } + + public function testUnknownSerializationException() + { + $this->expectException(SerializationException::class); + (new SerializableCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]))->serialize(255); + } + + public function testInvalidSerializationException() + { + $this->expectException(\InvalidArgumentException::class); + (new SerializableCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ]))->serialize('WTF?!?'); + } + + public function testBadSerializationException() + { + $this->expectException(SerializationException::class); + $obj = (new SerializableCollection([ + 'a' => 1, + 'b' => 5.50, + 'c' => 'srf', + 'e' => true, + 'f' => [1, 2, 3, 4], + ])); + + $obj->set('bad_chars', "\xB1\x31"); + + $obj->serialize(); + } +} diff --git a/tests/Algorithms/Collections/StackCollectionTest.php b/tests/Algorithms/Collections/StackCollectionTest.php index 2f1740a4..39f3f932 100644 --- a/tests/Algorithms/Collections/StackCollectionTest.php +++ b/tests/Algorithms/Collections/StackCollectionTest.php @@ -1,98 +1,98 @@ -push(null); - $collection->push(5); - $collection->push('Hello, World!'); - $this->assertEquals('Hello, World!', $collection->top()); - $this->assertEquals('Hello, World!', $collection->pop()); - } - - public function testUnderflow() - { - $this->expectException(StackException::class); - - $collection = new StackCollection(); - $collection->pop(); - } - - public function testOverflow() - { - $this->expectException(StackException::class); - - $collection = new StackCollection([1], 1); - $collection->push(2); - } - - public function testInvalidSet() - { - $this->expectException(StackException::class); - - $collection = new StackCollection(); - $collection->set(0, 'not working'); - } - - public function testInvalidGet() - { - $this->expectException(StackException::class); - - $collection = new StackCollection(['not working']); - $collection->get(0); - } - - public function testReverse() - { - $collection = new StackCollection([1, 3, 5]); - $this->assertEquals(5, $collection->top()); - - $collection->push(null); - - $collection->reverse(); - - $this->assertEquals(1, $collection->pop()); - $this->assertEquals(3, $collection->pop()); - $this->assertEquals(5, $collection->pop()); - - } - - public function testBadConstructorParam() - { - $this->expectException(\InvalidArgumentException::class); - - $collection = new StackCollection(null); - } - - public function testBadConstructorLimit() - { - $this->expectException(\InvalidArgumentException::class); - - $collection = new StackCollection([], null); - } +push(null); + $collection->push(5); + $collection->push('Hello, World!'); + $this->assertEquals('Hello, World!', $collection->top()); + $this->assertEquals('Hello, World!', $collection->pop()); + } + + public function testUnderflow() + { + $this->expectException(StackException::class); + + $collection = new StackCollection(); + $collection->pop(); + } + + public function testOverflow() + { + $this->expectException(StackException::class); + + $collection = new StackCollection([1], 1); + $collection->push(2); + } + + public function testInvalidSet() + { + $this->expectException(StackException::class); + + $collection = new StackCollection(); + $collection->set(0, 'not working'); + } + + public function testInvalidGet() + { + $this->expectException(StackException::class); + + $collection = new StackCollection(['not working']); + $collection->get(0); + } + + public function testReverse() + { + $collection = new StackCollection([1, 3, 5]); + $this->assertEquals(5, $collection->top()); + + $collection->push(null); + + $collection->reverse(); + + $this->assertEquals(1, $collection->pop()); + $this->assertEquals(3, $collection->pop()); + $this->assertEquals(5, $collection->pop()); + + } + + public function testBadConstructorParam() + { + $this->expectException(\InvalidArgumentException::class); + + $collection = new StackCollection(null); + } + + public function testBadConstructorLimit() + { + $this->expectException(\InvalidArgumentException::class); + + $collection = new StackCollection([], null); + } } \ No newline at end of file diff --git a/tests/Algorithms/ManipulationTest.php b/tests/Algorithms/Strings/ManipulationTest.php similarity index 97% rename from tests/Algorithms/ManipulationTest.php rename to tests/Algorithms/Strings/ManipulationTest.php index 6af58c73..92832f1e 100644 --- a/tests/Algorithms/ManipulationTest.php +++ b/tests/Algorithms/Strings/ManipulationTest.php @@ -15,11 +15,11 @@ limitations under the License. *****************************************************************************/ -namespace Gishiki\tests\Algorithms; +namespace Gishiki\tests\Algorithms\Strings; use PHPUnit\Framework\TestCase; -use Gishiki\Algorithms\Manipulation; +use Gishiki\Algorithms\Strings\Manipulation; class ManipulationTest extends TestCase { diff --git a/tests/Algorithms/Strings/SimpleLexerTest.php b/tests/Algorithms/Strings/SimpleLexerTest.php new file mode 100644 index 00000000..7c1cf08c --- /dev/null +++ b/tests/Algorithms/Strings/SimpleLexerTest.php @@ -0,0 +1,64 @@ +assertEquals(false, SimpleLexer::isEmail(null)); + $this->assertEquals(false, SimpleLexer::isEmail(".example@example.com")); + $this->assertEquals(false, SimpleLexer::isEmail(".example@exam@ple.com")); + $this->assertEquals(false, SimpleLexer::isEmail("John..Doe@exam@ple.com")); + + $this->assertEquals(true, SimpleLexer::isEmail("\"John..Doe\"@example.com")); + $this->assertEquals(true, SimpleLexer::isEmail("\".example\"@example.com")); + $this->assertEquals(true, SimpleLexer::isEmail("++example@exm.com")); + } + + public function testFloat() + { + $this->assertEquals(false, SimpleLexer::isFloat(null)); + $this->assertEquals(false, SimpleLexer::isFloat("0.0.1")); + $this->assertEquals(false, SimpleLexer::isFloat("+0a5")); + $this->assertEquals(false, SimpleLexer::isFloat("-45.")); + + $this->assertEquals(true, SimpleLexer::isFloat("+1")); + $this->assertEquals(true, SimpleLexer::isFloat("2")); + $this->assertEquals(true, SimpleLexer::isFloat("0.5")); + $this->assertEquals(true, SimpleLexer::isFloat("-50.75")); + } + + public function testSignedInteger() + { + $this->assertEquals(false, SimpleLexer::isSignedInteger(null)); + $this->assertEquals(false, SimpleLexer::isSignedInteger("0.0")); + $this->assertEquals(false, SimpleLexer::isSignedInteger("+0a5")); + $this->assertEquals(false, SimpleLexer::isSignedInteger("-45.")); + + $this->assertEquals(true, SimpleLexer::isSignedInteger("8")); + $this->assertEquals(true, SimpleLexer::isSignedInteger("+1")); + $this->assertEquals(true, SimpleLexer::isSignedInteger("-2")); + $this->assertEquals(true, SimpleLexer::isSignedInteger("-57")); + $this->assertEquals(true, SimpleLexer::isSignedInteger("+50")); + } +} \ No newline at end of file diff --git a/tests/CLI/ConsoleTest.php b/tests/CLI/ConsoleTest.php deleted file mode 100644 index 6bc9a875..00000000 --- a/tests/CLI/ConsoleTest.php +++ /dev/null @@ -1,90 +0,0 @@ - - */ -class ConsoleTest extends TestCase -{ - public function testWriteBooleanFalse() - { - $this->expectOutputString('false'); - - Console::colorsEnable(false); - - Console::write(false); - } - - public function testWriteBooleanTrue() - { - $this->expectOutputString('true'); - - Console::colorsEnable(false); - - Console::write(true); - } - - public function testWriteNull() - { - $this->expectOutputString('null'); - - Console::colorsEnable(false); - - Console::write(null); - } - - public function testWriteArray() - { - $arr = ['Hello, ', 'World!', "It's ", time(), ' Already']; - - $this->expectOutputString(implode('', $arr)); - - Console::colorsEnable(false); - - Console::write($arr); - } - - public function testWriteLine() - { - $this->expectOutputString("The sum is: 53\n"); - - Console::colorsEnable(false); - - Console::writeLine('The sum is'.': '.(50 + 3)); - } - - public function testColorsSupportChange() - { - Console::colorsEnable(false); - $this->assertEquals(false, Console::colorsEnabled()); - - Console::colorsEnable(true); - $this->assertEquals(true, Console::colorsEnabled()); - - Console::colorsEnable(false); - $this->assertEquals(false, Console::colorsEnabled()); - } -} diff --git a/tests/Core/MVC/Controller/ControllerTest.php b/tests/Core/MVC/Controller/ControllerTest.php new file mode 100644 index 00000000..52dac52a --- /dev/null +++ b/tests/Core/MVC/Controller/ControllerTest.php @@ -0,0 +1,75 @@ + + */ +class ControllerTest extends TestCase +{ + public function testInvalidPlugins() + { + $this->expectException(ControllerException::class); + + $request = new Request(); + $response = new Response(); + $collection = new GenericCollection([]); + $plugins = [ + 0 => \Gishiki\Core\MVC\Controller\Plugins\ResponseAssembler::class, + 1 => 'bye bye :)' + ]; + + new \FakeController($request, $response, $collection, $plugins); + } + + public function testGetRequest() + { + $request = new Request(); + $request = $request->withHeader('Testing-Header', 'Active'); + $response = new Response(); + $collection = new GenericCollection([]); + $plugins = []; + + $controller = new \FakeController($request, $response, $collection, $plugins); + $this->assertEquals('Active', $controller->getRequest()->getHeader('Testing-Header')[0]); + } + + public function testGetResponse() + { + $request = new Request(); + $response = new Response(); + $response = $response->withHeader('Testing-Header', 'Active'); + $collection = new GenericCollection([]); + $plugins = []; + + $controller = new \FakeController($request, $response, $collection, $plugins); + $this->assertEquals('Active', $controller->getResponse()->getHeader('Testing-Header')[0]); + } +} \ No newline at end of file diff --git a/tests/Core/MVC/Controller/Plugins/RequestDeserializerTest.php b/tests/Core/MVC/Controller/Plugins/RequestDeserializerTest.php new file mode 100644 index 00000000..c0a453e0 --- /dev/null +++ b/tests/Core/MVC/Controller/Plugins/RequestDeserializerTest.php @@ -0,0 +1,127 @@ + + */ +class RequestDeserializerTest extends TestCase +{ + public static function generateTestingData() + { + srand(null); + + $data = [ + "int_test" => rand(0, 150), + "str_test" => base64_encode(openssl_random_pseudo_bytes(32)), + "float_test" => rand(0, 3200) + (rand(0, 9) / 10), + "array_test" => [ + base64_encode(openssl_random_pseudo_bytes(32)), + base64_encode(openssl_random_pseudo_bytes(32)), + base64_encode(openssl_random_pseudo_bytes(32)), + base64_encode(openssl_random_pseudo_bytes(32)) + ], + ]; + + return $data; + } + + public function testJsonDeserialization() + { + $data = self::generateTestingData(); + + $request = new Request(); + $request = $request->withHeader('Content-Type', 'application/json;charset=utf-8'); + $request->getBody()->write( + json_encode($data) + ); + $request->getBody()->rewind(); + + $response = new Response(); + + $collection = new GenericCollection([]); + $plugins = [ + 0 => \Gishiki\Core\MVC\Controller\Plugins\RequestDeserializer::class + ]; + + $controller = new \FakeController($request, $response, $collection, $plugins); + + $this->assertEquals($data, $controller->getRequestDeserialized()->all()); + } + + public function testYamlDeserialization() + { + $data = self::generateTestingData(); + + $request = new Request(); + $request = $request->withHeader('Content-Type', 'text/x-yaml'); + $request->getBody()->write( + Yaml::dump($data) + ); + $request->getBody()->rewind(); + + $response = new Response(); + + $collection = new GenericCollection([]); + $plugins = [ + 0 => \Gishiki\Core\MVC\Controller\Plugins\RequestDeserializer::class + ]; + + $controller = new \FakeController($request, $response, $collection, $plugins); + + $this->assertEquals($data, $controller->getRequestDeserialized()->all()); + } + + public function testXmlDeserialization() + { + $data = self::generateTestingData(); + + $xml = new SerializableCollection($data); + + $request = new Request(); + $request = $request->withHeader('Content-Type', 'text/xml'); + $request->getBody()->write( + $xml->serialize(SerializableCollection::XML) + ); + $request->getBody()->rewind(); + + $response = new Response(); + + $collection = new GenericCollection([]); + $plugins = [ + 0 => \Gishiki\Core\MVC\Controller\Plugins\RequestDeserializer::class + ]; + + $controller = new \FakeController($request, $response, $collection, $plugins); + + $this->assertEquals($data, $controller->getRequestDeserialized()->all()); + } +} \ No newline at end of file diff --git a/tests/Core/RouteTest.php b/tests/Core/RouteTest.php deleted file mode 100644 index dd8e8044..00000000 --- a/tests/Core/RouteTest.php +++ /dev/null @@ -1,400 +0,0 @@ - - */ -class RouteTest extends TestCase -{ - public function testRegexRouter() - { - $test_route = new Route('/user/{username}/post/{post:number}', function () { - throw new \Exception('Bad Test!'); - }); - - //check the generated regex - $this->assertEquals("/^\/user\/([^\/]+)\/post\/((\\+|\\-)?(\\d)+)$/", $test_route->getRegex()['regex']); - - //and additional info - $this->assertEquals(['username', 'post'], $test_route->getRegex()['params']); - - $test_partregex_route = new Route('/user/new/{address:email}', function () { - throw new \Exception('Bad Test!'); - }); - - //check the generated regex - $this->assertEquals($test_partregex_route->getRegex()['regex'], "/^\/user\/new\/(([a-zA-Z0-9_\\-.+]+)\\@([a-zA-Z0-9-]+)\\.([a-zA-Z]+)((\\.([a-zA-Z]+))?))$/"); - - //and additional info - $this->assertEquals($test_partregex_route->getRegex()['params'], ['address']); - } - - public function testFailbackRouter() - { - $not_found = new Route(Route::NOT_FOUND, function () { - throw new \Exception('Bad Test!'); - }); - - //check the generated regex - $this->assertEquals('', $not_found->getRegex()['regex']); - $this->assertEquals(4, count($not_found->getRegex())); - $this->assertEquals(Route::NOT_FOUND, $not_found->isSpecialCallback()); - } - - public function testMatchingRouter() - { - //test an email - $email_route = new Route('/send/{address:email}', function () { - throw new \Exception('Bad Test!'); - }); - - //test some email address - $this->assertEquals( - new SerializableCollection(['address' => 'test_ing+s3m4il@sp4c3.com']), - $email_route->matchURI('/send/test_ing+s3m4il@sp4c3.com', 'GET')); - $this->assertEquals( - new SerializableCollection(['address' => 'test3m4il@sp4c3.co.uk']), - $email_route->matchURI('/send/test3m4il@sp4c3.co.uk', 'GET')); - $this->assertEquals( - new SerializableCollection(['address' => 'benato.denis96@gmail.com']), - $email_route->matchURI('/send/benato.denis96@gmail.com', 'GET')); - - //test using a number - $number_route = new Route('/MyNumber/{random:number}', function () { - throw new \Exception('Bad Test!'); - }); - - $random_number = '-'.strval(rand()); - - $this->assertEquals( - new SerializableCollection(['random' => $random_number]), - $number_route->matchURI('/MyNumber/'.$random_number, 'GET')); - } - - public function testBrokenRoute() - { - $number_route = new Route('/MyNumber/{random:number}', function () { - throw new \Exception('Bad Test!'); - }); - - $random_number = strval(rand()); - - $this->assertEquals( - null, - $number_route->matchURI('/MyNum/problem/ber/'.$random_number, 'GET')); - } - - public function testMultipleMatching() - { - $email_route = new Route('/send/{address:email}/{test}/{test_num:inteGer}', function () { - throw new \Exception('Bad Test!'); - }); - - //test the multiple rules matcher - $this->assertEquals( - $email_route->getRegex()['regex'], - '/^\/send\/(([a-zA-Z0-9_\-.+]+)\@([a-zA-Z0-9-]+)\.([a-zA-Z]+)((\.([a-zA-Z]+))?))\/([^\/]+)\/((\+|\-)?(\d)+)$/'); - - $this->assertEquals( - new SerializableCollection([ - 'address' => 'test_ing+s3m4il@sp4c3.com', - 'test' => 'uuuuh... likeit! :)', - 'test_num' => 32, ]), - $email_route->matchURI('/send/test_ing+s3m4il@sp4c3.com/uuuuh... likeit! :)/+32', 'GET')); - } - - public function testTypeHandler() - { - $email_route = new Route('/send/{address:email}/{test}/{test_num:inteGer}/{another_mail:mail}', function () { - throw new \Exception('Bad Test!'); - }); - - //test the multiple rules matcher - $this->assertEquals( - 4, - count($email_route->getRegex()['param_types'])); - $this->assertEquals( - ['email', 'default', 'signed_integer', 'email'], - $email_route->getRegex()['param_types']); - } - - public function testRouteExecution() - { - $this->setUp(); - - $test_route = new Route('/add/{num_1:integer}/{num_2:integer}', function (Request $request, Response &$response, SerializableCollection &$params) { - $result = $params->num_1 + $params->num_2; - - $response->write(strval($result)); - $response = $response->withStatus(500); - }); - - $match_result = $test_route->matchURI('/add/+59/-9', Route::GET); - - $this->assertEquals( - new SerializableCollection([ - 'num_1' => +59, - 'num_2' => -9, ]), - $match_result - ); - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/foo/bar/index.php', - 'REQUEST_URI' => '/foo/bar?abc=123', - ]); - $request = Request::createFromEnvironment($env); - $response = new Response(); - $test_route($request, $response, $match_result); - - $body = $response->getBody(); - $body->rewind(); - $data = ''; - while (!$body->eof()) { - $data .= $body->read(1); - } - - $this->assertEquals(500, $response->getStatusCode()); - - $this->assertEquals(50, intval($data)); - } - - public function testFullRouterExecution() - { - $this->setUp(); - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/23/post/testing', - ]); - $reqestToFulfill = Request::createFromEnvironment($env); - - Route::post('/should_not_match', function (Request &$request, Response &$response, SerializableCollection &$collection) { - $response->write('Bad error!'); - }); - - Route::get('/{user_mail:email}/post/{postname}', function (Request &$request, Response &$response, SerializableCollection &$collection) { - $response->write('Bad error!'); - }); - - Route::match([Route::GET], '/{user_id:number}/post/{postname}', function (Request &$request, Response &$response, SerializableCollection &$collection) { - $response->write('Searched post '.$collection->postname.' by user '.$collection->user_id); - }); - - Route::head('/{user_id:number}/post/{postname}', function (Request &$request, Response &$response, SerializableCollection &$collection) { - $response->write('Created at: '.time()); - }); - - Route::delete('/{user_id:number}/post/{postname}', function (Request &$request, Response &$response, SerializableCollection &$collection) { - $response->write('It is not possible to remove posts by user '.$collection->user_id); - }); - - $responseFilled = Route::run($reqestToFulfill); - - $body = $responseFilled->getBody(); - $body->rewind(); - $data = ''; - while (!$body->eof()) { - $data .= $body->read(1); - } - - $this->assertEquals('Searched post testing by user 23', $data); - } - - - public function testBadMatchParams() - { - $this->expectException(\InvalidArgumentException::class); - Route::match(Route::GET, '/{user_id:number}/post/{postname}', function (Request &$request, Response &$response, SerializableCollection &$collection) { - $response->write('Searched post '.$collection->postname.' by user '.$collection->user_id); - }); - } - - public function testReturningAddress() - { - $test_route = new Route('/add/{num_1:integer}/{num_2:integer}', function (Request $request, Response &$response, SerializableCollection &$params) { - $result = $params->num_1 + $params->num_2; - - $response->write(strval($result)); - $response = $response->withStatus(500); - }); - - $this->assertSame($test_route, Route::addRoute($test_route)); - } - - public function testSpecialRouting() - { - $this->setUp(); - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/this_cannot_be_found', - ]); - $reqestToFulfill = Request::createFromEnvironment($env); - - Route::put('/should_not_match', function (Request &$request, Response &$response, GenericCollection &$collection) { - $response->write('Bad error!'); - }); - - Route::get('/{user_mail:email}/post/{postname}', function (Request &$request, Response &$response, GenericCollection &$collection) { - $response->write('Bad error!'); - }); - - Route::match([Route::GET], '/{user_id:number}/post/{postname}', function (Request &$request, Response &$response, GenericCollection &$collection) { - $response->write('Searched post '.$collection->postname.' by user '.$collection->user_id); - }); - - Route::head('/{user_id:number}/post/{postname}', function (Request &$request, Response &$response, GenericCollection &$collection) { - $response->write('Created at: '.time()); - }); - - Route::delete('/{user_id:number}/post/{postname}', function (Request &$request, Response &$response, SerializableCollection &$collection) { - $response->write('It is not possible to remove posts by user '.$collection->user_id); - }); - - Route::any(Route::NOT_FOUND, function (&$request, &$response) { - $response->write('404 Not Found'); - }); - - $responseFilled = Route::run($reqestToFulfill); - - $body = $responseFilled->getBody(); - $body->rewind(); - $data = ''; - while (!$body->eof()) { - $data .= $body->read(1); - } - - $this->assertEquals('404 Not Found', $data); - } - - public function testControllerRouting() - { - $this->setUp(); - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/benato.denis96@gmail.com', - ]); - $reqestToFulfill = Request::createFromEnvironment($env); - - Route::get('/{mail:email}', 'Gishiki\tests\Application\FakeController->myAction'); - - $responseFilled = Route::run($reqestToFulfill); - - $body = $responseFilled->getBody(); - $body->rewind(); - $data = ''; - while (!$body->eof()) { - $data .= $body->read(1); - } - - $this->assertEquals('My email is: benato.denis96@gmail.com', $data); - } - - public function testNonexistentControllerRouting() - { - $this->expectException(\InvalidArgumentException::class); - - $this->setUp(); - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/benato.denis96@gmail.com/bad', - ]); - $reqestToFulfill = Request::createFromEnvironment($env); - - Route::get('/{mail:email}/bad', 'badController->badAction'); - - //this will trigger the expected exception: no class badController! - Route::run($reqestToFulfill); - } - - public function testBadNameControllerRouting() - { - $this->expectException(\InvalidArgumentException::class); - - $this->setUp(); - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/benato.denis96@gmail.com/badname', - ]); - $reqestToFulfill = Request::createFromEnvironment($env); - - Route::get('/{mail:email}/badname', 'badController->'); - - //this will trigger the expected exception: no class badController! - Route::run($reqestToFulfill); - } - - public function testBadControllerIdentifierRouting() - { - $this->expectException(\InvalidArgumentException::class); - - $this->setUp(); - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/benato.denis96@gmail.com/badid', - ]); - $reqestToFulfill = Request::createFromEnvironment($env); - - Route::get('/{mail:email}/badid', 'badController'); - - //this will trigger the expected exception: no class badController! - Route::run($reqestToFulfill); - } - - public function testControllerStaticInvokationRouting() - { - $this->setUp(); - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/t3st1n9@fake.co.uk/quick', - ]); - $reqestToFulfill = Request::createFromEnvironment($env); - - Route::get('/{mail:email}/quick', 'Gishiki\tests\Application\FakeController::quickAction'); - - $responseFilled = Route::run($reqestToFulfill); - - $body = $responseFilled->getBody(); - $body->rewind(); - $data = ''; - while (!$body->eof()) { - $data .= $body->read(1); - } - - $this->assertEquals('should I send an email to t3st1n9@fake.co.uk?', $data); - } -} diff --git a/tests/Core/Router/RouteTest.php b/tests/Core/Router/RouteTest.php new file mode 100644 index 00000000..f3d765a0 --- /dev/null +++ b/tests/Core/Router/RouteTest.php @@ -0,0 +1,210 @@ + + */ +class RouteTest extends TestCase +{ + + public function testRoute() + { + $verb = Route::GET; + $uri = "/index"; + $status = Route::OK; + + $route = new Route([ + "verbs" => [ + $verb + ], + "uri" => $uri, + "status" => $status, + "controller" => "FakeController", + "action" => 'none', + ]); + + $this->assertEquals([$verb], $route->getMethods()); + $this->assertEquals($uri, $route->getURI()); + $this->assertEquals($status, $route->getStatus()); + } + + public function testRouteBadVerb() + { + $this->expectException(RouterException::class); + + new Route([ + "verbs" => 'hello', + "uri" => "/", + "status" => 200, + "controller" => \FakeController::class, + "action" => 'none', + ]); + } + + public function testRouteBadUri() + { + $this->expectException(RouterException::class); + + new Route([ + "verbs" => [ Route::GET ], + "uri" => null, + "status" => 200, + "controller" => "FakeController", + "action" => 'none', + ]); + } + + public function testRouteBadStatus() + { + $this->expectException(RouterException::class); + + new Route([ + "verbs" => [ Route::GET ], + "uri" => "/", + "status" => ":( I shouldn't be here", + "controller" => \FakeController::class, + "action" => 'none', + ]); + } + + public function testRouteBadController() + { + $this->expectException(RouterException::class); + + new Route([ + "verbs" => [ Route::GET ], + "uri" => "/", + "status" => 200, + "controller" => "I don't exists :)", + "action" => 'none', + ]); + } + + public function testRouteBadAction() + { + $this->expectException(RouterException::class); + + new Route([ + "verbs" => [ Route::GET ], + "uri" => "/", + "status" => 200, + "controller" => "I don't exists :)", + "action" => 'none', + ]); + } + + public function testRouteInvoke() + { + $verb = Route::GET; + $uri = "/do"; + $status = Route::NOT_ALLOWED; + + $route = new Route([ + "verbs" => [ + $verb + ], + "uri" => $uri, + "status" => $status, + "controller" => \FakeController::class, + "action" => 'do', + ]); + + $this->assertEquals([$verb], $route->getMethods()); + $this->assertEquals($uri, $route->getURI()); + $this->assertEquals($status, $route->getStatus()); + + //generate a request to be passed + $request = new Request( + $uri, + 'GET', + 'php://memory', + [] + ); + + //generate a response that will be changed + $response = new Response(); + + //generate a meaningless collection to be passed + $coll = new GenericCollection(); + + $route($request, $response, $coll); + + $body = $response->getBody(); + $body->rewind(); + + $output = $body->getContents(); + + $this->assertEquals('Th1s 1s 4 t3st', $output); + } + + public function testRouteInvokeWithParam() + { + $value = "example.mail@gmail.com"; + + $route = new Route([ + "verbs" => [ + Route::GET, Route::POST + ], + "uri" => "/mail", + "status" => Route::OK, + "controller" => \FakeController::class, + "action" => 'myAction', + ]); + + $this->assertEquals([ Route::GET, Route::POST ], $route->getMethods()); + $this->assertEquals("/mail", $route->getURI()); + $this->assertEquals(Route::OK, $route->getStatus()); + + //generate a request to be passed + $request = new Request( + 'https://example.com:443/main/', + 'GET', + 'php://memory', + [] + ); + + //generate a response that will be changed + $response = new Response(); + + //generate a meaningless collection to be passed + $coll = new GenericCollection([ + "mail" => $value + ]); + + $route($request, $response, $coll); + + $body = $response->getBody(); + $body->rewind(); + $this->assertEquals("My email is: ".$value, $body->getContents()); + + $this->assertEquals(200, $response->getStatusCode()); + } +} \ No newline at end of file diff --git a/tests/Core/Router/RouterTest.php b/tests/Core/Router/RouterTest.php new file mode 100644 index 00000000..88e6209a --- /dev/null +++ b/tests/Core/Router/RouterTest.php @@ -0,0 +1,153 @@ + + */ +class RouterTest extends TestCase +{ + public function testCompleteRouting() + { + $route = new Route([ + "verbs" => [ + Route::GET, Route::POST + ], + "uri" => "/email/{mail:email}", + "status" => Route::OK, + "controller" => "FakeController", + "action" => "quickAction" + ]); + + $router = new Router(); + $router->register($route); + + $request = new Request( + 'https://example.com:443/email/nicemail@live.com', + 'GET', + 'php://memory', + [] + ); + + $response = $router->run($request); + $body = $response->getBody(); + $body->rewind(); + + $this->assertEquals('should I send an email to nicemail@live.com?', $body->getContents()); + } + + public function testStrangeMatch() + { + $expr = null; + + $result = Router::matches("/home", "/", $expr); + + $this->assertEquals(false, $result); + } + + public function testBadUrl() + { + $expr = null; + + $this->expectException(\InvalidArgumentException::class); + Router::matches(null, "/", $expr); + } + + public function testBadUri() + { + $expr = null; + + $this->expectException(\InvalidArgumentException::class); + Router::matches("/home", null, $expr); + } + + public function testStatic() + { + $expr = null; + + $this->assertEquals(true, Router::matches("/home/hello/test", "/home/hello/test", $expr)); + } + + public function testDynamicEmail() + { + $expr = null; + + $this->assertEquals(true, Router::matches("/email/{address:email}", "/email/example@gmail.com", $expr)); + $this->assertEquals([ + "address" => "example@gmail.com" + ], $expr); + } + + public function testDynamicUint() + { + $expr = null; + + $this->assertEquals(true, Router::matches("/uint/{number:uint}", "/uint/54", $expr)); + $this->assertEquals([ + "number" => 54 + ], $expr); + } + + public function testDynamicSint() + { + $expr = null; + + $this->assertEquals(true, Router::matches("/sint/{number:int}", "/sint/-55", $expr)); + $this->assertEquals([ + "number" => -55 + ], $expr); + } + + public function testDynamicFloat() + { + $expr = null; + + $this->assertEquals(true, Router::matches("/float/{number:float}", "/float/-55.25", $expr)); + $this->assertEquals([ + "number" => -55.25 + ], $expr); + } + + public function testDynamicComplex() + { + $expr = null; + + $this->assertEquals(true, Router::matches("/cplx/{id:uint}/{mail:email}/set", "/cplx/9/example@xmpl.com/set", $expr)); + $this->assertEquals([ + "id" => 9, + "mail" => "example@xmpl.com" + ], $expr); + } + + public function testDynamicBadSplitNumber() + { + $expr = null; + + $this->assertEquals(false, Router::matches("/cplx/{id:uint}", "/cplx", $expr)); + } +} \ No newline at end of file diff --git a/tests/Database/Adapters/DatabaseRelationalTest.php b/tests/Database/Adapters/DatabaseRelationalTest.php index 11c35ffd..632df4e7 100644 --- a/tests/Database/Adapters/DatabaseRelationalTest.php +++ b/tests/Database/Adapters/DatabaseRelationalTest.php @@ -1,569 +1,569 @@ - - */ -class DatabaseRelationalTest extends TestCase -{ - protected function getDatabase() - { - return new Sqlite(":memory:"); - } - - public function testMultipleOperationsWithRelations() - { - // build the author table - $authorTable = new Table('authors'); - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setAutoIncrement(true); - $idColumn->setPrimaryKey(true); - $idColumn->setNotNull(true); - $authorTable->addColumn($idColumn); - $nameColumn = new Column('names', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $authorTable->addColumn($nameColumn); - - // build the book table - $bookTable = new Table('books'); - $bookIdColumn = new Column('id', ColumnType::INTEGER); - $bookIdColumn->setPrimaryKey(true); - $bookIdColumn->setNotNull(true); - $bookTable->addColumn($bookIdColumn); - $authorIdColumn = new Column('author_id', ColumnType::INTEGER); - $authorIdColumn->getRelation(new ColumnRelation($authorTable, $idColumn)); - $authorIdColumn->setNotNull(true); - $bookTable->addColumn($authorIdColumn); - $bookTitleColumn = new Column('title', ColumnType::TEXT); - $bookTitleColumn->setNotNull(true); - $bookTable->addColumn($bookTitleColumn); - $bookDateColumn = new Column('publication_date', ColumnType::TEXT); - $bookDateColumn->setNotNull(false); - $bookTable->addColumn($bookDateColumn); - $bookPriceColumn = new Column('price', ColumnType::NUMERIC); - $bookPriceColumn->setNotNull(true); - $bookTable->addColumn($bookPriceColumn); - - // create tables - $connection = $this->getDatabase(); - $connection->createTable($authorTable); - $connection->createTable($bookTable); - - $ARMBookId = $connection->create( - 'books', - [ - 'id' => 1, - 'author_id' => $connection->create('authors', [ 'names' => 'Stephen B. Furber' ]), - 'title' => 'ARM System-On-Chip Architecture', - 'publication_date' => '2000', - 'price' => 68.52 - ] - ); - - $ARMBookAuthorId = $connection->readSelective('books', ['author_id'], SelectionCriteria::select(['title' => 'ARM System-On-Chip Architecture']), ResultModifier::initialize()->limit(1))[0]['author_id']; - - $this->assertEquals(1, $connection->delete('books', SelectionCriteria::select(['author_id' => $ARMBookAuthorId]))); - $this->assertEquals(1, $connection->delete('authors', SelectionCriteria::select(['id' => $ARMBookAuthorId]))); - } - - public function testCreateTableOnClosedDatabase() - { - $this->expectException(DatabaseException::class); - - $closedConnection = null; - try { - $closedConnection = $this->getDatabase(); - $closedConnection->close(); - } catch (\InvalidArgumentException $ex) { } - - $table = new Table(__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setAutoIncrement(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - - $closedConnection->createTable($table); - } - - public function testBadCreateTable() - { - $this->expectException(DatabaseException::class); - - $table = new Table("from"); - - $idColumn = new Column('where', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setAutoIncrement(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - - $connection = $this->getDatabase(); - $connection->createTable($table); - } - - public function testNoRelationsAndNoID() - { - $table = new Table("User".__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setAutoIncrement(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('name', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $surnameColumn = new Column('surname', ColumnType::TEXT); - $surnameColumn->setNotNull(true); - $table->addColumn($surnameColumn); - $passwordColumn = new Column('password', ColumnType::TEXT); - $passwordColumn->setNotNull(true); - $table->addColumn($passwordColumn); - $creditColumn = new Column('credit', ColumnType::NUMERIC); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('registered', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $connection = $this->getDatabase(); - $connection->createTable($table); - - $userExample = [ - "name" => "Mario", - "surname" => "Rossi", - "password" => sha1("asdfgh"), - "credit" => 15.68, - "registered" => time() - ]; - - $currentID = $connection->create( - "User".__FUNCTION__, - new SerializableCollection($userExample)); - - $readSelectiveResult = $connection->readSelective("User".__FUNCTION__, ["name", "surname"], - SelectionCriteria::select(["name" => "Mario"]), - ResultModifier::initialize()); - - $this->assertEquals([[ - "name" => "Mario", - "surname" => "Rossi"]], $readSelectiveResult); - - $readResult = $connection->read( - "User".__FUNCTION__, - SelectionCriteria::select(["name" => "Mario"]), - ResultModifier::initialize()); - - $this->assertEquals([array_merge($userExample, ['id' => $currentID])], $readResult); - - $this->assertEquals(1, $connection->deleteAll("User".__FUNCTION__)); - } - - public function testUpdateOnClosedConnection() - { - $this->expectException(DatabaseException::class); - - $closedConnection = $this->getDatabase(); - $closedConnection->close(); - - $closedConnection->update(__FUNCTION__, ["status" => "lol"], SelectionCriteria::select()); - } - - public function testUpdateBadCollectionName() - { - $this->expectException(\InvalidArgumentException::class); - - $connection = $this->getDatabase(); - - $connection->update(null, ["status" => "lol"], SelectionCriteria::select()); - } - - public function testUpdateBadCollectionData() - { - $this->expectException(\InvalidArgumentException::class); - - $connection = $this->getDatabase(); - - $connection->update(__FUNCTION__, "nonsense :)", SelectionCriteria::select()); - } - - public function testDeleteOnClosedConnection() - { - $this->expectException(DatabaseException::class); - - $closedConnection = $this->getDatabase(); - $closedConnection->close(); - - $closedConnection->delete(__FUNCTION__, SelectionCriteria::select(["status" => "unwanted"])); - } - - public function testDeleteBadCollectionName() - { - $this->expectException(\InvalidArgumentException::class); - - $connection = $this->getDatabase(); - - $connection->delete(null, SelectionCriteria::select(["status" => "unwanted"])); - } - - public function testDeleteAllOnClosedConnection() - { - $this->expectException(DatabaseException::class); - - $closedConnection = $this->getDatabase(); - $closedConnection->close(); - - $closedConnection->deleteAll(__FUNCTION__); - } - - public function testDeleteAllBadCollectionName() - { - $this->expectException(\InvalidArgumentException::class); - - $connection = $this->getDatabase(); - - $connection->deleteAll(null); - } - - public function testCreateOnClosedConnection() - { - $this->expectException(DatabaseException::class); - - $closedConnection = $this->getDatabase(); - $closedConnection->close(); - - $closedConnection->create(__FUNCTION__, ["status" => "unwanted"]); - } - - public function testCreateBadCollectionName() - { - $this->expectException(\InvalidArgumentException::class); - - $connection = $this->getDatabase(); - - $connection->create(null, ["status" => "lol"]); - } - - public function testCreateBadCollectionValue() - { - $this->expectException(\InvalidArgumentException::class); - - $connection = $this->getDatabase(); - - $connection->create(__FUNCTION__, 69); - } - - public function testBadCreate() - { - $this->expectException(DatabaseException::class); - - $connection = $this->getDatabase(); - - $connection->create(__FUNCTION__, ["status" => "unwanted"]); - } - - public function testBadReadSelective() - { - $this->expectException(DatabaseException::class); - - $connection = $this->getDatabase(); - - $connection->readSelective(__FUNCTION__, ['id' => 7], SelectionCriteria::select(), ResultModifier::initialize()); - } - - public function testBadRead() - { - $this->expectException(DatabaseException::class); - - $connection = $this->getDatabase(); - - $connection->read(__FUNCTION__, SelectionCriteria::select(), ResultModifier::initialize()); - } - - public function testBadDelete() - { - $this->expectException(DatabaseException::class); - - $connection = $this->getDatabase(); - - $connection->delete(__FUNCTION__, SelectionCriteria::select(["id" => 10])); - } - - public function testBadDeleteAll() - { - $this->expectException(DatabaseException::class); - - $connection = $this->getDatabase(); - - $connection->deleteAll(__FUNCTION__); - } - - public function testReadBadCollectionName() - { - $this->expectException(\InvalidArgumentException::class); - - $connection = $this->getDatabase(); - - $connection->read(null, SelectionCriteria::select(['id' => 7]), ResultModifier::initialize()); - } - - public function testReadSelectiveBadCollectionName() - { - $this->expectException(\InvalidArgumentException::class); - - $connection = $this->getDatabase(); - - $connection->readSelective(null, ['id'],SelectionCriteria::select(['id' => 7]), ResultModifier::initialize()); - } - - public function testReadSelectiveOnClosedConnection() - { - $this->expectException(DatabaseException::class); - - $closedConnection = $this->getDatabase(); - $closedConnection->close(); - - $closedConnection->readSelective(__FUNCTION__, ['id'],SelectionCriteria::select(['id' => 7]), ResultModifier::initialize()); - } - - public function testReadOnClosedConnection() - { - $this->expectException(DatabaseException::class); - - $closedConnection = $this->getDatabase(); - $closedConnection->close(); - - $closedConnection->read(__FUNCTION__, SelectionCriteria::select(['id' => 7]), ResultModifier::initialize()); - } - - public function testBadUpdate() - { - $this->expectException(DatabaseException::class); - - $connection = $this->getDatabase(); - - $connection->update(__FUNCTION__, ["price" => 9.00], SelectionCriteria::select()); - } - - public function testDeleteNoRelationNoID() - { - $table = new Table("Books".__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setAutoIncrement(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('title', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $authorColumn = new Column('author', ColumnType::TEXT); - $table->addColumn($authorColumn); - $priceColumn = new Column('price', ColumnType::MONEY); - $priceColumn->setNotNull(true); - $table->addColumn($priceColumn); - - $connection = $this->getDatabase(); - $connection->createTable($table); - - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Compilers: Principles, Techniques, and Tools', - 'author' => 'Alfred V. Aho, Monica S. Lam, Ravi Sethi, and Jeffrey D. Ullman', - 'price' => 50.99 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Bible', - 'price' => 12.99 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => '1984', - 'author' => 'George Orwell', - 'price' => 13.40 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Animal Farm', - 'author' => 'George Orwell', - 'price' => 25.99 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Programming in ANSI C Deluxe Revised', - 'price' => 8.71 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'C Programming Language, 2nd Edition', - 'price' => 14.46 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Modern Operating Systems', - 'author' => 'Andrew S. Tanenbaum', - 'price' => 70.89 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Embedded C Coding Standard', - 'price' => 5.38 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'C Programming for Embedded Microcontrollers', - 'price' => 20.00 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'ARM Assembly Language', - 'price' => 17.89 - ]); - - - $this->assertEquals(7, $connection->delete("Books".__FUNCTION__, - SelectionCriteria::select()->AndWhere('price', FieldRelation::LESS_THAN, 20.99) - )); - - $this->assertEquals(3, $connection->deleteAll("Books".__FUNCTION__)); - } - - public function testUpdateNoRelationNoID() - { - $table = new Table("Books".__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setAutoIncrement(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('title', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $authorColumn = new Column('author', ColumnType::TEXT); - $table->addColumn($authorColumn); - $priceColumn = new Column('price', ColumnType::MONEY); - $priceColumn->setNotNull(true); - $table->addColumn($priceColumn); - - $connection = $this->getDatabase(); - $connection->createTable($table); - - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Compilers: Principles, Techniques, and Tools', - 'author' => 'Alfred V. Aho, Monica S. Lam, Ravi Sethi, and Jeffrey D. Ullman', - 'price' => 50.99 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Bible', - 'price' => 12.99 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => '1984', - 'author' => 'George Orwell', - 'price' => 13.40 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Animal Farm', - 'author' => 'George Orwell', - 'price' => 25.99 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Programming in ANSI C Deluxe Revised', - 'price' => 8.71 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'C Programming Language, 2nd Edition', - 'price' => 14.46 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Modern Operating Systems', - 'author' => 'Andrew S. Tanenbaum', - 'price' => 70.89 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'Embedded C Coding Standard', - 'price' => 5.38 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'C Programming for Embedded Microcontrollers', - 'price' => 20.00 - ]); - $connection->create( - "Books".__FUNCTION__, - [ - 'title' => 'ARM Assembly Language', - 'price' => 17.89 - ]); - - - $this->assertEquals(5, $connection->update("Books".__FUNCTION__, ['price' => 10.00], - SelectionCriteria::select() - ->AndWhere('price', FieldRelation::LESS_THAN, 20.99) - ->AndWhere('price', FieldRelation::GREATER_OR_EQUAL_THAN, 10.50) - )); - } + + */ +class DatabaseRelationalTest extends TestCase +{ + protected function getDatabase() + { + return new Sqlite(":memory:"); + } + + public function testMultipleOperationsWithRelations() + { + // build the author table + $authorTable = new Table('authors'); + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setAutoIncrement(true); + $idColumn->setPrimaryKey(true); + $idColumn->setNotNull(true); + $authorTable->addColumn($idColumn); + $nameColumn = new Column('names', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $authorTable->addColumn($nameColumn); + + // build the book table + $bookTable = new Table('books'); + $bookIdColumn = new Column('id', ColumnType::INTEGER); + $bookIdColumn->setPrimaryKey(true); + $bookIdColumn->setNotNull(true); + $bookTable->addColumn($bookIdColumn); + $authorIdColumn = new Column('author_id', ColumnType::INTEGER); + $authorIdColumn->getRelation(new ColumnRelation($authorTable, $idColumn)); + $authorIdColumn->setNotNull(true); + $bookTable->addColumn($authorIdColumn); + $bookTitleColumn = new Column('title', ColumnType::TEXT); + $bookTitleColumn->setNotNull(true); + $bookTable->addColumn($bookTitleColumn); + $bookDateColumn = new Column('publication_date', ColumnType::TEXT); + $bookDateColumn->setNotNull(false); + $bookTable->addColumn($bookDateColumn); + $bookPriceColumn = new Column('price', ColumnType::NUMERIC); + $bookPriceColumn->setNotNull(true); + $bookTable->addColumn($bookPriceColumn); + + // create tables + $connection = $this->getDatabase(); + $connection->createTable($authorTable); + $connection->createTable($bookTable); + + $ARMBookId = $connection->create( + 'books', + [ + 'id' => 1, + 'author_id' => $connection->create('authors', [ 'names' => 'Stephen B. Furber' ]), + 'title' => 'ARM System-On-Chip Architecture', + 'publication_date' => '2000', + 'price' => 68.52 + ] + ); + + $ARMBookAuthorId = $connection->readSelective('books', ['author_id'], SelectionCriteria::select(['title' => 'ARM System-On-Chip Architecture']), ResultModifier::initialize()->limit(1))[0]['author_id']; + + $this->assertEquals(1, $connection->delete('books', SelectionCriteria::select(['author_id' => $ARMBookAuthorId]))); + $this->assertEquals(1, $connection->delete('authors', SelectionCriteria::select(['id' => $ARMBookAuthorId]))); + } + + public function testCreateTableOnClosedDatabase() + { + $this->expectException(DatabaseException::class); + + $closedConnection = null; + try { + $closedConnection = $this->getDatabase(); + $closedConnection->close(); + } catch (\InvalidArgumentException $ex) { } + + $table = new Table(__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setAutoIncrement(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + + $closedConnection->createTable($table); + } + + public function testBadCreateTable() + { + $this->expectException(DatabaseException::class); + + $table = new Table("from"); + + $idColumn = new Column('where', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setAutoIncrement(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + + $connection = $this->getDatabase(); + $connection->createTable($table); + } + + public function testNoRelationsAndNoID() + { + $table = new Table("User".__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setAutoIncrement(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('name', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $surnameColumn = new Column('surname', ColumnType::TEXT); + $surnameColumn->setNotNull(true); + $table->addColumn($surnameColumn); + $passwordColumn = new Column('password', ColumnType::TEXT); + $passwordColumn->setNotNull(true); + $table->addColumn($passwordColumn); + $creditColumn = new Column('credit', ColumnType::NUMERIC); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('registered', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $connection = $this->getDatabase(); + $connection->createTable($table); + + $userExample = [ + "name" => "Mario", + "surname" => "Rossi", + "password" => sha1("asdfgh"), + "credit" => 15.68, + "registered" => time() + ]; + + $currentID = $connection->create( + "User".__FUNCTION__, + new SerializableCollection($userExample)); + + $readSelectiveResult = $connection->readSelective("User".__FUNCTION__, ["name", "surname"], + SelectionCriteria::select(["name" => "Mario"]), + ResultModifier::initialize()); + + $this->assertEquals([[ + "name" => "Mario", + "surname" => "Rossi"]], $readSelectiveResult); + + $readResult = $connection->read( + "User".__FUNCTION__, + SelectionCriteria::select(["name" => "Mario"]), + ResultModifier::initialize()); + + $this->assertEquals([array_merge($userExample, ['id' => $currentID])], $readResult); + + $this->assertEquals(1, $connection->deleteAll("User".__FUNCTION__)); + } + + public function testUpdateOnClosedConnection() + { + $this->expectException(DatabaseException::class); + + $closedConnection = $this->getDatabase(); + $closedConnection->close(); + + $closedConnection->update(__FUNCTION__, ["status" => "lol"], SelectionCriteria::select()); + } + + public function testUpdateBadCollectionName() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = $this->getDatabase(); + + $connection->update(null, ["status" => "lol"], SelectionCriteria::select()); + } + + public function testUpdateBadCollectionData() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = $this->getDatabase(); + + $connection->update(__FUNCTION__, "nonsense :)", SelectionCriteria::select()); + } + + public function testDeleteOnClosedConnection() + { + $this->expectException(DatabaseException::class); + + $closedConnection = $this->getDatabase(); + $closedConnection->close(); + + $closedConnection->delete(__FUNCTION__, SelectionCriteria::select(["status" => "unwanted"])); + } + + public function testDeleteBadCollectionName() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = $this->getDatabase(); + + $connection->delete(null, SelectionCriteria::select(["status" => "unwanted"])); + } + + public function testDeleteAllOnClosedConnection() + { + $this->expectException(DatabaseException::class); + + $closedConnection = $this->getDatabase(); + $closedConnection->close(); + + $closedConnection->deleteAll(__FUNCTION__); + } + + public function testDeleteAllBadCollectionName() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = $this->getDatabase(); + + $connection->deleteAll(null); + } + + public function testCreateOnClosedConnection() + { + $this->expectException(DatabaseException::class); + + $closedConnection = $this->getDatabase(); + $closedConnection->close(); + + $closedConnection->create(__FUNCTION__, ["status" => "unwanted"]); + } + + public function testCreateBadCollectionName() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = $this->getDatabase(); + + $connection->create(null, ["status" => "lol"]); + } + + public function testCreateBadCollectionValue() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = $this->getDatabase(); + + $connection->create(__FUNCTION__, 69); + } + + public function testBadCreate() + { + $this->expectException(DatabaseException::class); + + $connection = $this->getDatabase(); + + $connection->create(__FUNCTION__, ["status" => "unwanted"]); + } + + public function testBadReadSelective() + { + $this->expectException(DatabaseException::class); + + $connection = $this->getDatabase(); + + $connection->readSelective(__FUNCTION__, ['id' => 7], SelectionCriteria::select(), ResultModifier::initialize()); + } + + public function testBadRead() + { + $this->expectException(DatabaseException::class); + + $connection = $this->getDatabase(); + + $connection->read(__FUNCTION__, SelectionCriteria::select(), ResultModifier::initialize()); + } + + public function testBadDelete() + { + $this->expectException(DatabaseException::class); + + $connection = $this->getDatabase(); + + $connection->delete(__FUNCTION__, SelectionCriteria::select(["id" => 10])); + } + + public function testBadDeleteAll() + { + $this->expectException(DatabaseException::class); + + $connection = $this->getDatabase(); + + $connection->deleteAll(__FUNCTION__); + } + + public function testReadBadCollectionName() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = $this->getDatabase(); + + $connection->read(null, SelectionCriteria::select(['id' => 7]), ResultModifier::initialize()); + } + + public function testReadSelectiveBadCollectionName() + { + $this->expectException(\InvalidArgumentException::class); + + $connection = $this->getDatabase(); + + $connection->readSelective(null, ['id'],SelectionCriteria::select(['id' => 7]), ResultModifier::initialize()); + } + + public function testReadSelectiveOnClosedConnection() + { + $this->expectException(DatabaseException::class); + + $closedConnection = $this->getDatabase(); + $closedConnection->close(); + + $closedConnection->readSelective(__FUNCTION__, ['id'],SelectionCriteria::select(['id' => 7]), ResultModifier::initialize()); + } + + public function testReadOnClosedConnection() + { + $this->expectException(DatabaseException::class); + + $closedConnection = $this->getDatabase(); + $closedConnection->close(); + + $closedConnection->read(__FUNCTION__, SelectionCriteria::select(['id' => 7]), ResultModifier::initialize()); + } + + public function testBadUpdate() + { + $this->expectException(DatabaseException::class); + + $connection = $this->getDatabase(); + + $connection->update(__FUNCTION__, ["price" => 9.00], SelectionCriteria::select()); + } + + public function testDeleteNoRelationNoID() + { + $table = new Table("Books".__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setAutoIncrement(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('title', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $authorColumn = new Column('author', ColumnType::TEXT); + $table->addColumn($authorColumn); + $priceColumn = new Column('price', ColumnType::MONEY); + $priceColumn->setNotNull(true); + $table->addColumn($priceColumn); + + $connection = $this->getDatabase(); + $connection->createTable($table); + + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Compilers: Principles, Techniques, and Tools', + 'author' => 'Alfred V. Aho, Monica S. Lam, Ravi Sethi, and Jeffrey D. Ullman', + 'price' => 50.99 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Bible', + 'price' => 12.99 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => '1984', + 'author' => 'George Orwell', + 'price' => 13.40 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Animal Farm', + 'author' => 'George Orwell', + 'price' => 25.99 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Programming in ANSI C Deluxe Revised', + 'price' => 8.71 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'C Programming Language, 2nd Edition', + 'price' => 14.46 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Modern Operating Systems', + 'author' => 'Andrew S. Tanenbaum', + 'price' => 70.89 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Embedded C Coding Standard', + 'price' => 5.38 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'C Programming for Embedded Microcontrollers', + 'price' => 20.00 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'ARM Assembly Language', + 'price' => 17.89 + ]); + + + $this->assertEquals(7, $connection->delete("Books".__FUNCTION__, + SelectionCriteria::select()->AndWhere('price', FieldRelation::LESS_THAN, 20.99) + )); + + $this->assertEquals(3, $connection->deleteAll("Books".__FUNCTION__)); + } + + public function testUpdateNoRelationNoID() + { + $table = new Table("Books".__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setAutoIncrement(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('title', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $authorColumn = new Column('author', ColumnType::TEXT); + $table->addColumn($authorColumn); + $priceColumn = new Column('price', ColumnType::MONEY); + $priceColumn->setNotNull(true); + $table->addColumn($priceColumn); + + $connection = $this->getDatabase(); + $connection->createTable($table); + + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Compilers: Principles, Techniques, and Tools', + 'author' => 'Alfred V. Aho, Monica S. Lam, Ravi Sethi, and Jeffrey D. Ullman', + 'price' => 50.99 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Bible', + 'price' => 12.99 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => '1984', + 'author' => 'George Orwell', + 'price' => 13.40 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Animal Farm', + 'author' => 'George Orwell', + 'price' => 25.99 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Programming in ANSI C Deluxe Revised', + 'price' => 8.71 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'C Programming Language, 2nd Edition', + 'price' => 14.46 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Modern Operating Systems', + 'author' => 'Andrew S. Tanenbaum', + 'price' => 70.89 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'Embedded C Coding Standard', + 'price' => 5.38 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'C Programming for Embedded Microcontrollers', + 'price' => 20.00 + ]); + $connection->create( + "Books".__FUNCTION__, + [ + 'title' => 'ARM Assembly Language', + 'price' => 17.89 + ]); + + + $this->assertEquals(5, $connection->update("Books".__FUNCTION__, ['price' => 10.00], + SelectionCriteria::select() + ->AndWhere('price', FieldRelation::LESS_THAN, 20.99) + ->AndWhere('price', FieldRelation::GREATER_OR_EQUAL_THAN, 10.50) + )); + } } \ No newline at end of file diff --git a/tests/Database/Adapters/MysqlTest.php b/tests/Database/Adapters/MysqlTest.php index ef948c65..b69ed857 100644 --- a/tests/Database/Adapters/MysqlTest.php +++ b/tests/Database/Adapters/MysqlTest.php @@ -1,54 +1,57 @@ - - */ -class MysqlTest extends DatabaseRelationalTest -{ - - protected function getDatabase() - { - $mysqlConnectionStr = (getenv("CI")) ? - getenv("MYSQL_CONN") : - "host=localhost;dbname=travis;user=root;password="; - - return new Mysql($mysqlConnectionStr); - } - - public function testBadConnectionParam() - { - $this->expectException(\InvalidArgumentException::class); - - new Mysql(null); - } - - public function testBadConnection() - { - $this->expectException(DatabaseException::class); - - new Mysql("database=doesntExists;user=poo"); - } + + */ +class MysqlTest extends DatabaseRelationalTest +{ + + /** + * @return Mysql the valid database connection + */ + protected function getDatabase() + { + $mysqlConnectionStr = (getenv("CI")) ? + getenv("MYSQL_CONN") : + "host=localhost;dbname=travis;user=root;password="; + + return new Mysql($mysqlConnectionStr); + } + + public function testBadConnectionParam() + { + $this->expectException(\InvalidArgumentException::class); + + new Mysql(null); + } + + public function testBadConnection() + { + $this->expectException(DatabaseException::class); + + new Mysql("database=doesntExists;user=poo"); + } } \ No newline at end of file diff --git a/tests/Database/Adapters/PgsqlTest.php b/tests/Database/Adapters/PgsqlTest.php index 8058f2c5..1d7af92c 100644 --- a/tests/Database/Adapters/PgsqlTest.php +++ b/tests/Database/Adapters/PgsqlTest.php @@ -1,54 +1,54 @@ - - */ -class PgsqlTest extends DatabaseRelationalTest -{ - - protected function getDatabase() - { - $postgreConnectionStr = (getenv("CI")) ? - getenv("PG_CONN") : - "host=localhost;port=5432;dbname=travis;user=vagrant;password=vagrant"; - - return new Pgsql($postgreConnectionStr); - } - - public function testBadConnectionParam() - { - $this->expectException(\InvalidArgumentException::class); - - new Pgsql(null); - } - - public function testBadConnection() - { - $this->expectException(DatabaseException::class); - - new Pgsql("database=doesntExists;user=postgres"); - } + + */ +class PgsqlTest extends DatabaseRelationalTest +{ + + protected function getDatabase() + { + $postgreConnectionStr = (getenv("CI")) ? + getenv("PG_CONN") : + "host=localhost;port=5432;dbname=travis;user=vagrant;password=vagrant"; + + return new Pgsql($postgreConnectionStr); + } + + public function testBadConnectionParam() + { + $this->expectException(\InvalidArgumentException::class); + + new Pgsql(null); + } + + public function testBadConnection() + { + $this->expectException(DatabaseException::class); + + new Pgsql("database=doesntExists;user=postgres"); + } } \ No newline at end of file diff --git a/tests/Database/Adapters/SqliteTest.php b/tests/Database/Adapters/SqliteTest.php index 66b322b4..4ffcf78d 100644 --- a/tests/Database/Adapters/SqliteTest.php +++ b/tests/Database/Adapters/SqliteTest.php @@ -1,40 +1,40 @@ - - */ -class SqliteTest extends DatabaseRelationalTest -{ - protected function getDatabase() - { - return new Sqlite("tests/db_test.sqlite"); - } - - public function testBadConnectionParam() - { - $this->expectException(\InvalidArgumentException::class); - - new Sqlite(null); - } + + */ +class SqliteTest extends DatabaseRelationalTest +{ + protected function getDatabase() + { + return new Sqlite("tests/db_test.sqlite"); + } + + public function testBadConnectionParam() + { + $this->expectException(\InvalidArgumentException::class); + + new Sqlite(null); + } } \ No newline at end of file diff --git a/tests/Database/Adapters/Utils/MySQLQueryBuilderTest.php b/tests/Database/Adapters/Utils/MySQLQueryBuilderTest.php index 2ce64a5e..efcf8e68 100644 --- a/tests/Database/Adapters/Utils/MySQLQueryBuilderTest.php +++ b/tests/Database/Adapters/Utils/MySQLQueryBuilderTest.php @@ -1,134 +1,134 @@ - - */ -class MySQLQueryBuilderTest extends TestCase -{ - public function testCreateTableWithNoForeignKey() - { - $table = new Table(__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('name', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $creditColumn = new Column('credit', ColumnType::NUMERIC); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('registered', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new MySQLWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(MySQLWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' - .'id INTEGER NOT NULL, ' - .'name TEXT NOT NULL, ' - .'credit DOUBLE NOT NULL, ' - .'registered INTEGER, ' - .'PRIMARY KEY (id)' - .')'), MySQLWrapper::beautify($query->exportQuery())); - } - - public function testCreateTableWithForeignKey() - { - $tableExtern = new Table('users'); - $userIdColumn = new Column('id', ColumnType::INTEGER); - $userIdColumn->setNotNull(true); - $userIdColumn->setPrimaryKey(true); - $tableExtern->addColumn($userIdColumn); - - $table = new Table('orders'); - - $relation = new ColumnRelation($tableExtern, $userIdColumn); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('customer_id', ColumnType::INTEGER); - $nameColumn->setNotNull(true); - $nameColumn->setRelation($relation); - $table->addColumn($nameColumn); - $creditColumn = new Column('spent', ColumnType::FLOAT); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('ship_date', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new MySQLWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(MySQLWrapper::beautify('CREATE TABLE IF NOT EXISTS orders (' - .'id INTEGER NOT NULL, ' - .'customer_id INTEGER NOT NULL, ' - .'FOREIGN KEY (customer_id) REFERENCES users(id), ' - .'spent FLOAT NOT NULL, ' - .'ship_date INTEGER, ' - .'PRIMARY KEY (id)' - .')'), MySQLWrapper::beautify($query->exportQuery())); - } - - public function testCreateTableWithAutoIncrementAndNoForeignKey() - { - $table = new Table(__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setAutoIncrement(true); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('name', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $creditColumn = new Column('credit', ColumnType::NUMERIC); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('registered', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new MySQLWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(MySQLWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' - .'id INTEGER AUTO_INCREMENT NOT NULL, ' - .'name TEXT NOT NULL, ' - .'credit DOUBLE NOT NULL, ' - .'registered INTEGER, ' - .'PRIMARY KEY (id)' - .')'), MySQLWrapper::beautify($query->exportQuery())); - } + + */ +class MySQLQueryBuilderTest extends TestCase +{ + public function testCreateTableWithNoForeignKey() + { + $table = new Table(__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('name', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $creditColumn = new Column('credit', ColumnType::NUMERIC); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('registered', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new MySQLWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(MySQLWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' + .'id INTEGER NOT NULL, ' + .'name TEXT NOT NULL, ' + .'credit DOUBLE NOT NULL, ' + .'registered INTEGER, ' + .'PRIMARY KEY (id)' + .')'), MySQLWrapper::beautify($query->exportQuery())); + } + + public function testCreateTableWithForeignKey() + { + $tableExtern = new Table('users'); + $userIdColumn = new Column('id', ColumnType::INTEGER); + $userIdColumn->setNotNull(true); + $userIdColumn->setPrimaryKey(true); + $tableExtern->addColumn($userIdColumn); + + $table = new Table('orders'); + + $relation = new ColumnRelation($tableExtern, $userIdColumn); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('customer_id', ColumnType::INTEGER); + $nameColumn->setNotNull(true); + $nameColumn->setRelation($relation); + $table->addColumn($nameColumn); + $creditColumn = new Column('spent', ColumnType::FLOAT); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('ship_date', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new MySQLWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(MySQLWrapper::beautify('CREATE TABLE IF NOT EXISTS orders (' + .'id INTEGER NOT NULL, ' + .'customer_id INTEGER NOT NULL, ' + .'FOREIGN KEY (customer_id) REFERENCES users(id), ' + .'spent FLOAT NOT NULL, ' + .'ship_date INTEGER, ' + .'PRIMARY KEY (id)' + .')'), MySQLWrapper::beautify($query->exportQuery())); + } + + public function testCreateTableWithAutoIncrementAndNoForeignKey() + { + $table = new Table(__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setAutoIncrement(true); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('name', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $creditColumn = new Column('credit', ColumnType::NUMERIC); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('registered', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new MySQLWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(MySQLWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' + .'id INTEGER AUTO_INCREMENT NOT NULL, ' + .'name TEXT NOT NULL, ' + .'credit DOUBLE NOT NULL, ' + .'registered INTEGER, ' + .'PRIMARY KEY (id)' + .')'), MySQLWrapper::beautify($query->exportQuery())); + } } \ No newline at end of file diff --git a/tests/Database/Adapters/Utils/PostgreSQLQueryBuilderTest.php b/tests/Database/Adapters/Utils/PostgreSQLQueryBuilderTest.php index 49a34201..2f52f264 100644 --- a/tests/Database/Adapters/Utils/PostgreSQLQueryBuilderTest.php +++ b/tests/Database/Adapters/Utils/PostgreSQLQueryBuilderTest.php @@ -1,130 +1,130 @@ - - */ -class PostgreSQLQueryBuilderTest extends TestCase -{ - public function testCreateTableWithNoForeignKey() - { - $table = new Table(__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('name', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $creditColumn = new Column('credit', ColumnType::NUMERIC); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('registered', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new PostgreSQLWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(PostgreSQLWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' - .'id integer PRIMARY KEY NOT NULL, ' - .'name text NOT NULL, ' - .'credit numeric NOT NULL, ' - .'registered integer' - .')'), PostgreSQLWrapper::beautify($query->exportQuery())); - } - - public function testCreateTableWithForeignKey() - { - $tableExtern = new Table('users'); - $userIdColumn = new Column('id', ColumnType::INTEGER); - $userIdColumn->setNotNull(true); - $userIdColumn->setPrimaryKey(true); - $tableExtern->addColumn($userIdColumn); - - $table = new Table('orders'); - - $relation = new ColumnRelation($tableExtern, $userIdColumn); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('customer_id', ColumnType::BIGINT); - $nameColumn->setNotNull(true); - $nameColumn->setRelation($relation); - $table->addColumn($nameColumn); - $creditColumn = new Column('spent', ColumnType::FLOAT); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('ship_date', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new PostgreSQLWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(PostgreSQLWrapper::beautify('CREATE TABLE IF NOT EXISTS orders (' - .'id integer PRIMARY KEY NOT NULL, ' - .'customer_id bigint NOT NULL REFERENCES users(id), ' - .'spent float NOT NULL, ' - .'ship_date integer' - .')'), PostgreSQLWrapper::beautify($query->exportQuery())); - } - - public function testCreateTableWithAutoIncrementAndNoForeignKey() - { - $table = new Table(__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setAutoIncrement(true); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('name', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $creditColumn = new Column('credit', ColumnType::NUMERIC); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('registered', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new PostgreSQLWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(PostgreSQLWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' - .'id serial PRIMARY KEY, ' - .'name text NOT NULL, ' - .'credit numeric NOT NULL, ' - .'registered integer' - .')'), PostgreSQLWrapper::beautify($query->exportQuery())); - } + + */ +class PostgreSQLQueryBuilderTest extends TestCase +{ + public function testCreateTableWithNoForeignKey() + { + $table = new Table(__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('name', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $creditColumn = new Column('credit', ColumnType::NUMERIC); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('registered', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new PostgreSQLWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(PostgreSQLWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' + .'id integer PRIMARY KEY NOT NULL, ' + .'name text NOT NULL, ' + .'credit numeric NOT NULL, ' + .'registered integer' + .')'), PostgreSQLWrapper::beautify($query->exportQuery())); + } + + public function testCreateTableWithForeignKey() + { + $tableExtern = new Table('users'); + $userIdColumn = new Column('id', ColumnType::INTEGER); + $userIdColumn->setNotNull(true); + $userIdColumn->setPrimaryKey(true); + $tableExtern->addColumn($userIdColumn); + + $table = new Table('orders'); + + $relation = new ColumnRelation($tableExtern, $userIdColumn); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('customer_id', ColumnType::BIGINT); + $nameColumn->setNotNull(true); + $nameColumn->setRelation($relation); + $table->addColumn($nameColumn); + $creditColumn = new Column('spent', ColumnType::FLOAT); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('ship_date', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new PostgreSQLWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(PostgreSQLWrapper::beautify('CREATE TABLE IF NOT EXISTS orders (' + .'id integer PRIMARY KEY NOT NULL, ' + .'customer_id bigint NOT NULL REFERENCES users(id), ' + .'spent float NOT NULL, ' + .'ship_date integer' + .')'), PostgreSQLWrapper::beautify($query->exportQuery())); + } + + public function testCreateTableWithAutoIncrementAndNoForeignKey() + { + $table = new Table(__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setAutoIncrement(true); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('name', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $creditColumn = new Column('credit', ColumnType::NUMERIC); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('registered', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new PostgreSQLWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(PostgreSQLWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' + .'id serial PRIMARY KEY, ' + .'name text NOT NULL, ' + .'credit numeric NOT NULL, ' + .'registered integer' + .')'), PostgreSQLWrapper::beautify($query->exportQuery())); + } } \ No newline at end of file diff --git a/tests/Database/Adapters/Utils/SQLQueryBuilderTest.php b/tests/Database/Adapters/Utils/SQLQueryBuilderTest.php index e0671eaa..c2798b7a 100644 --- a/tests/Database/Adapters/Utils/SQLQueryBuilderTest.php +++ b/tests/Database/Adapters/Utils/SQLQueryBuilderTest.php @@ -1,136 +1,136 @@ - - */ -class SQLQueryBuilderTest extends TestCase -{ - public function testBeautify() - { - $this->assertEquals( - 'SELECT * FROM test0 WHERE id = ? OR name = ? ORDER BY id DESC', - GenericSQL::beautify('SELECT * FROM test0 WHERE id = ? OR name = ? ORDER BY id DESC')); - } - - public function testDropTable() - { - $query = new GenericSQL(); - $query->dropTable(__FUNCTION__); - - $this->assertEquals(GenericSQL::beautify('DROP TABLE IF EXISTS '.__FUNCTION__), GenericSQL::beautify($query->exportQuery())); - } - - public function testSelectAllFrom() - { - $query = new GenericSQL(); - $query->selectAllFrom('test1'); - - $this->assertEquals(GenericSQL::beautify('SELECT * FROM test1'), GenericSQL::beautify($query->exportQuery())); - $this->assertEquals([], $query->exportParams()); - } - - public function testSelectAllFromWhere() - { - $query = new GenericSQL(); - $query->selectAllFrom('test1')->where(SelectionCriteria::select([ - 'id' => [5, 6, 7], - ])->OrWhere('name', FieldRelation::NOT_LIKE, '%inv%')); - - $this->assertEquals(GenericSQL::beautify('SELECT * FROM test1 WHERE id IN (?,?,?) OR name NOT LIKE ?'), GenericSQL::beautify($query->exportQuery())); - $this->assertEquals([5, 6, 7, '%inv%'], $query->exportParams()); - } - - public function testSelectAllFromWhereLimitOffsetOrderBy() - { - $query = new GenericSQL(); - $query->selectAllFrom('test1') - ->where(SelectionCriteria::select([ - 'id' => [5, 6, 7], - ])->OrWhere('price', FieldRelation::GREATER_THAN, 1.25)) - ->limitOffsetOrderBy(ResultModifier::initialize([ - 'limit' => 1024, - 'skip' => 100, - 'name' => FieldOrdering::ASC, - ])); - - $this->assertEquals(GenericSQL::beautify('SELECT * FROM test1 WHERE id IN (?,?,?) OR price > ? LIMIT 1024 OFFSET 100 ORDER BY name ASC'), GenericSQL::beautify($query->exportQuery())); - $this->assertEquals([5, 6, 7, 1.25], $query->exportParams()); - } - - public function testSelectFromWhereLimitOffsetOrderBy() - { - $query = new GenericSQL(); - $query->selectFrom('test1', ['name', 'surname']) - ->where(SelectionCriteria::select([ - 'id' => [5, 6, 7], - ])->OrWhere('price', FieldRelation::GREATER_THAN, 1.25)) - ->limitOffsetOrderBy(ResultModifier::initialize([ - 'limit' => 1024, - 'skip' => 100, - 'name' => FieldOrdering::ASC, - 'surname' => FieldOrdering::DESC, - ])); - - $this->assertEquals(GenericSQL::beautify('SELECT name, surname FROM test1 WHERE id IN (?,?,?) OR price > ? LIMIT 1024 OFFSET 100 ORDER BY name ASC, surname DESC'), GenericSQL::beautify($query->exportQuery())); - $this->assertEquals([5, 6, 7, 1.25], $query->exportParams()); - } - - public function testInsertIntoValues() - { - $query = new GenericSQL(); - $query->insertInto('users')->values([ - 'name' => 'Mario', - 'surname' => 'Rossi', - 'age' => 25, - 'time' => 56.04, - ]); - - $this->assertEquals(GenericSQL::beautify('INSERT INTO users (name, surname, age, time) VALUES (?,?,?,?)'), GenericSQL::beautify($query->exportQuery())); - $this->assertEquals(['Mario', 'Rossi', 25, 56.04], $query->exportParams()); - } - - public function testDeleteFrom() - { - $query = new GenericSQL(); - $query->deleteFrom('users'); - - $this->assertEquals(GenericSQL::beautify('DELETE FROM users'), GenericSQL::beautify($query->exportQuery())); - $this->assertEquals([], $query->exportParams()); - } - - public function testUpdateSetWhere() - { - $query = new GenericSQL(); - $query->update('users')->set(['name' => 'Gianni', 'surname' => 'Pinotto'])->where(SelectionCriteria::select(['id' => 200])); - - $this->assertEquals(GenericSQL::beautify('UPDATE users SET name = ?, surname = ? WHERE id = ?'), GenericSQL::beautify($query->exportQuery())); - $this->assertEquals(['Gianni', 'Pinotto', 200], $query->exportParams()); - } -} + + */ +class SQLQueryBuilderTest extends TestCase +{ + public function testBeautify() + { + $this->assertEquals( + 'SELECT * FROM test0 WHERE id = ? OR name = ? ORDER BY id DESC', + GenericSQL::beautify('SELECT * FROM test0 WHERE id = ? OR name = ? ORDER BY id DESC')); + } + + public function testDropTable() + { + $query = new GenericSQL(); + $query->dropTable(__FUNCTION__); + + $this->assertEquals(GenericSQL::beautify('DROP TABLE IF EXISTS '.__FUNCTION__), GenericSQL::beautify($query->exportQuery())); + } + + public function testSelectAllFrom() + { + $query = new GenericSQL(); + $query->selectAllFrom('test1'); + + $this->assertEquals(GenericSQL::beautify('SELECT * FROM test1'), GenericSQL::beautify($query->exportQuery())); + $this->assertEquals([], $query->exportParams()); + } + + public function testSelectAllFromWhere() + { + $query = new GenericSQL(); + $query->selectAllFrom('test1')->where(SelectionCriteria::select([ + 'id' => [5, 6, 7], + ])->OrWhere('name', FieldRelation::NOT_LIKE, '%inv%')); + + $this->assertEquals(GenericSQL::beautify('SELECT * FROM test1 WHERE id IN (?,?,?) OR name NOT LIKE ?'), GenericSQL::beautify($query->exportQuery())); + $this->assertEquals([5, 6, 7, '%inv%'], $query->exportParams()); + } + + public function testSelectAllFromWhereLimitOffsetOrderBy() + { + $query = new GenericSQL(); + $query->selectAllFrom('test1') + ->where(SelectionCriteria::select([ + 'id' => [5, 6, 7], + ])->OrWhere('price', FieldRelation::GREATER_THAN, 1.25)) + ->limitOffsetOrderBy(ResultModifier::initialize([ + 'limit' => 1024, + 'skip' => 100, + 'name' => FieldOrdering::ASC, + ])); + + $this->assertEquals(GenericSQL::beautify('SELECT * FROM test1 WHERE id IN (?,?,?) OR price > ? LIMIT 1024 OFFSET 100 ORDER BY name ASC'), GenericSQL::beautify($query->exportQuery())); + $this->assertEquals([5, 6, 7, 1.25], $query->exportParams()); + } + + public function testSelectFromWhereLimitOffsetOrderBy() + { + $query = new GenericSQL(); + $query->selectFrom('test1', ['name', 'surname']) + ->where(SelectionCriteria::select([ + 'id' => [5, 6, 7], + ])->OrWhere('price', FieldRelation::GREATER_THAN, 1.25)) + ->limitOffsetOrderBy(ResultModifier::initialize([ + 'limit' => 1024, + 'skip' => 100, + 'name' => FieldOrdering::ASC, + 'surname' => FieldOrdering::DESC, + ])); + + $this->assertEquals(GenericSQL::beautify('SELECT name, surname FROM test1 WHERE id IN (?,?,?) OR price > ? LIMIT 1024 OFFSET 100 ORDER BY name ASC, surname DESC'), GenericSQL::beautify($query->exportQuery())); + $this->assertEquals([5, 6, 7, 1.25], $query->exportParams()); + } + + public function testInsertIntoValues() + { + $query = new GenericSQL(); + $query->insertInto('users')->values([ + 'name' => 'Mario', + 'surname' => 'Rossi', + 'age' => 25, + 'time' => 56.04, + ]); + + $this->assertEquals(GenericSQL::beautify('INSERT INTO users (name, surname, age, time) VALUES (?,?,?,?)'), GenericSQL::beautify($query->exportQuery())); + $this->assertEquals(['Mario', 'Rossi', 25, 56.04], $query->exportParams()); + } + + public function testDeleteFrom() + { + $query = new GenericSQL(); + $query->deleteFrom('users'); + + $this->assertEquals(GenericSQL::beautify('DELETE FROM users'), GenericSQL::beautify($query->exportQuery())); + $this->assertEquals([], $query->exportParams()); + } + + public function testUpdateSetWhere() + { + $query = new GenericSQL(); + $query->update('users')->set(['name' => 'Gianni', 'surname' => 'Pinotto'])->where(SelectionCriteria::select(['id' => 200])); + + $this->assertEquals(GenericSQL::beautify('UPDATE users SET name = ?, surname = ? WHERE id = ?'), GenericSQL::beautify($query->exportQuery())); + $this->assertEquals(['Gianni', 'Pinotto', 200], $query->exportParams()); + } +} diff --git a/tests/Database/Adapters/Utils/SQLiteQueryBuilderTest.php b/tests/Database/Adapters/Utils/SQLiteQueryBuilderTest.php index 7ec37fa6..0850968d 100644 --- a/tests/Database/Adapters/Utils/SQLiteQueryBuilderTest.php +++ b/tests/Database/Adapters/Utils/SQLiteQueryBuilderTest.php @@ -1,132 +1,132 @@ - - */ -class SQLiteQueryBuilderTest extends TestCase -{ - public function testCreateTableWithNoForeignKey() - { - $table = new Table(__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('name', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $creditColumn = new Column('credit', ColumnType::NUMERIC); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('registered', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new SQLiteWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(SQLiteWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' - .'id INTEGER PRIMARY KEY NOT NULL, ' - .'name TEXT NOT NULL, ' - .'credit REAL NOT NULL, ' - .'registered INTEGER' - .')'), SQLiteWrapper::beautify($query->exportQuery())); - } - - public function testCreateTableWithForeignKey() - { - $tableExtern = new Table('users'); - $userIdColumn = new Column('id', ColumnType::INTEGER); - $userIdColumn->setNotNull(true); - $userIdColumn->setPrimaryKey(true); - $tableExtern->addColumn($userIdColumn); - - $table = new Table('orders'); - - $relation = new ColumnRelation($tableExtern, $userIdColumn); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('customer_id', ColumnType::INTEGER); - $nameColumn->setNotNull(true); - $nameColumn->setRelation($relation); - $table->addColumn($nameColumn); - $creditColumn = new Column('spent', ColumnType::FLOAT); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('ship_date', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new SQLiteWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(SQLiteWrapper::beautify('CREATE TABLE IF NOT EXISTS orders (' - .'id INTEGER PRIMARY KEY NOT NULL, ' - .'customer_id INTEGER NOT NULL, ' - .'FOREIGN KEY (customer_id) REFERENCES users(id), ' - .'spent REAL NOT NULL, ' - .'ship_date INTEGER' - .')'), SQLiteWrapper::beautify($query->exportQuery())); - } - - public function testCreateTableWithAutoIncrementAndNoForeignKey() - { - $table = new Table(__FUNCTION__); - - $idColumn = new Column('id', ColumnType::INTEGER); - $idColumn->setAutoIncrement(true); - $idColumn->setNotNull(true); - $idColumn->setPrimaryKey(true); - $table->addColumn($idColumn); - $nameColumn = new Column('name', ColumnType::TEXT); - $nameColumn->setNotNull(true); - $table->addColumn($nameColumn); - $creditColumn = new Column('credit', ColumnType::NUMERIC); - $creditColumn->setNotNull(true); - $table->addColumn($creditColumn); - $registeredColumn = new Column('registered', ColumnType::DATETIME); - $registeredColumn->setNotNull(false); - $table->addColumn($registeredColumn); - - $query = new SQLiteWrapper(); - $query->createTable($table->getName())->definedAs($table->getColumns()); - - $this->assertEquals(SQLiteWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' - .'id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' - .'name TEXT NOT NULL, ' - .'credit REAL NOT NULL, ' - .'registered INTEGER' - .')'), SQLiteWrapper::beautify($query->exportQuery())); - } -} + + */ +class SQLiteQueryBuilderTest extends TestCase +{ + public function testCreateTableWithNoForeignKey() + { + $table = new Table(__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('name', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $creditColumn = new Column('credit', ColumnType::NUMERIC); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('registered', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new SQLiteWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(SQLiteWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' + .'id INTEGER PRIMARY KEY NOT NULL, ' + .'name TEXT NOT NULL, ' + .'credit REAL NOT NULL, ' + .'registered INTEGER' + .')'), SQLiteWrapper::beautify($query->exportQuery())); + } + + public function testCreateTableWithForeignKey() + { + $tableExtern = new Table('users'); + $userIdColumn = new Column('id', ColumnType::INTEGER); + $userIdColumn->setNotNull(true); + $userIdColumn->setPrimaryKey(true); + $tableExtern->addColumn($userIdColumn); + + $table = new Table('orders'); + + $relation = new ColumnRelation($tableExtern, $userIdColumn); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('customer_id', ColumnType::INTEGER); + $nameColumn->setNotNull(true); + $nameColumn->setRelation($relation); + $table->addColumn($nameColumn); + $creditColumn = new Column('spent', ColumnType::FLOAT); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('ship_date', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new SQLiteWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(SQLiteWrapper::beautify('CREATE TABLE IF NOT EXISTS orders (' + .'id INTEGER PRIMARY KEY NOT NULL, ' + .'customer_id INTEGER NOT NULL, ' + .'FOREIGN KEY (customer_id) REFERENCES users(id), ' + .'spent REAL NOT NULL, ' + .'ship_date INTEGER' + .')'), SQLiteWrapper::beautify($query->exportQuery())); + } + + public function testCreateTableWithAutoIncrementAndNoForeignKey() + { + $table = new Table(__FUNCTION__); + + $idColumn = new Column('id', ColumnType::INTEGER); + $idColumn->setAutoIncrement(true); + $idColumn->setNotNull(true); + $idColumn->setPrimaryKey(true); + $table->addColumn($idColumn); + $nameColumn = new Column('name', ColumnType::TEXT); + $nameColumn->setNotNull(true); + $table->addColumn($nameColumn); + $creditColumn = new Column('credit', ColumnType::NUMERIC); + $creditColumn->setNotNull(true); + $table->addColumn($creditColumn); + $registeredColumn = new Column('registered', ColumnType::DATETIME); + $registeredColumn->setNotNull(false); + $table->addColumn($registeredColumn); + + $query = new SQLiteWrapper(); + $query->createTable($table->getName())->definedAs($table->getColumns()); + + $this->assertEquals(SQLiteWrapper::beautify('CREATE TABLE IF NOT EXISTS '.__FUNCTION__.' (' + .'id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' + .'name TEXT NOT NULL, ' + .'credit REAL NOT NULL, ' + .'registered INTEGER' + .')'), SQLiteWrapper::beautify($query->exportQuery())); + } +} diff --git a/tests/Database/DatabaseManagerTest.php b/tests/Database/DatabaseManagerTest.php index f9a1c510..2e9ba1b8 100644 --- a/tests/Database/DatabaseManagerTest.php +++ b/tests/Database/DatabaseManagerTest.php @@ -1,67 +1,67 @@ - - */ -class DatabaseManagerTest extends TestCase -{ - public function testBadConnectionQuery() - { - $this->expectException(\InvalidArgumentException::class); - DatabaseManager::connect(3, 'unknown_db_adapter://user:pass@host:port/db'); - } - - public function testConnectionQuery() - { - $this->expectException(DatabaseException::class); - DatabaseManager::connect('default', 'unknown_db_adapter://user:pass@host:port/db'); - } - - public function testVoidConnection() - { - $this->expectException(DatabaseException::class); - DatabaseManager::retrieve('testing_bad_db (unconnected)'); - } - - public function testInvalidNameConnection() - { - $this->expectException(\InvalidArgumentException::class); - DatabaseManager::retrieve(3); - } - - public function testValidConnection() - { - //connect an empty-memory bounded database - DatabaseManager::connect('temp_db', 'sqlite://:memory:'); - - //retrieve the connected database - $connection = DatabaseManager::retrieve('temp_db'); - - //test for a successful retrieve operation - $this->assertEquals(true, $connection->connected()); - } -} + + */ +class DatabaseManagerTest extends TestCase +{ + public function testBadConnectionQuery() + { + $this->expectException(\InvalidArgumentException::class); + DatabaseManager::connect(3, 'unknown_db_adapter://user:pass@host:port/db'); + } + + public function testConnectionQuery() + { + $this->expectException(DatabaseException::class); + DatabaseManager::connect('default', 'unknown_db_adapter://user:pass@host:port/db'); + } + + public function testVoidConnection() + { + $this->expectException(DatabaseException::class); + DatabaseManager::retrieve('testing_bad_db (unconnected)'); + } + + public function testInvalidNameConnection() + { + $this->expectException(\InvalidArgumentException::class); + DatabaseManager::retrieve(3); + } + + public function testValidConnection() + { + //connect an empty-memory bounded database + DatabaseManager::connect('temp_db', 'sqlite://:memory:'); + + //retrieve the connected database + $connection = DatabaseManager::retrieve('temp_db'); + + //test for a successful retrieve operation + $this->assertEquals(true, $connection->connected()); + } +} diff --git a/tests/Database/ORM/DatabaseStructureTest.php b/tests/Database/ORM/DatabaseStructureTest.php index 0cd2b6e3..bbae94d8 100644 --- a/tests/Database/ORM/DatabaseStructureTest.php +++ b/tests/Database/ORM/DatabaseStructureTest.php @@ -1,39 +1,39 @@ - [ ] - ]); - - $this->expectException(StructureException::class); - - new DatabaseStructure($desc); - } - - + [ ] + ]); + + $this->expectException(StructureException::class); + + new DatabaseStructure($desc); + } + + } \ No newline at end of file diff --git a/tests/Database/Runtime/ResultModifierTest.php b/tests/Database/Runtime/ResultModifierTest.php index b9853f5d..15f18992 100644 --- a/tests/Database/Runtime/ResultModifierTest.php +++ b/tests/Database/Runtime/ResultModifierTest.php @@ -1,139 +1,139 @@ - - */ -class ResultModifierTest extends TestCase -{ - public function testBadInitializer() - { - $this->expectException(\InvalidArgumentException::class); - ResultModifier::initialize('only array and null are allowed here!'); - } - - public function testBadLimit() - { - $this->expectException(\InvalidArgumentException::class); - ResultModifier::initialize([ - 'limit' => 5, - 'skip' => 8, - ])->limit('bad')->skip(5); - } - - public function testBadOffset() - { - $this->expectException(\InvalidArgumentException::class); - ResultModifier::initialize([ - 'limit' => 5, - 'skip' => 8, - ])->limit(10)->skip('bad'); - } - - public function testBadNameOrdering() - { - $this->expectException(\InvalidArgumentException::class); - ResultModifier::initialize([ - 'limit' => 5, - 'skip' => 8, - ])->order(null, FieldOrdering::ASC); - } - - public function testBadOrderOrdering() - { - $this->expectException(\InvalidArgumentException::class); - ResultModifier::initialize([ - 'limit' => 5, - 'skip' => 8, - ])->order('name', null); - } - - public function testLimitAndOffset() - { - $exportResult = [ - 'limit' => 8, - 'skip' => 5, - 'order' => [], - ]; - - $resMod = ResultModifier::initialize([ - 'limit' => 5, - 'skip' => 8, - ])->limit(8)->skip(5); - - $exportMethod = new \ReflectionMethod($resMod, 'export'); - $exportMethod->setAccessible(true); - - $this->assertEquals($exportResult, $exportMethod->invoke($resMod)); - } - - public function testOrdering() - { - $exportResult = [ - 'limit' => 0, - 'skip' => 0, - 'order' => [ - 'name' => FieldOrdering::ASC, - 'surname' => FieldOrdering::ASC, - 'year' => FieldOrdering::DESC, - ], - ]; - - $resMod = ResultModifier::initialize([]) - ->order('name', FieldOrdering::ASC) - ->order('surname', FieldOrdering::ASC) - ->order('year', FieldOrdering::DESC); - - $exportMethod = new \ReflectionMethod($resMod, 'export'); - $exportMethod->setAccessible(true); - - $this->assertEquals($exportResult, $exportMethod->invoke($resMod)); - } - - public function testOrderingOnInitializer() - { - $exportResult = [ - 'limit' => 0, - 'skip' => 0, - 'order' => [ - 'name' => FieldOrdering::ASC, - 'surname' => FieldOrdering::ASC, - 'year' => FieldOrdering::DESC, - ], - ]; - - $resMod = ResultModifier::initialize([ - 'name' => FieldOrdering::ASC, - 'surname' => FieldOrdering::ASC, - 'year' => FieldOrdering::DESC, - ]); - - $exportMethod = new \ReflectionMethod($resMod, 'export'); - $exportMethod->setAccessible(true); - - $this->assertEquals($exportResult, $exportMethod->invoke($resMod)); - } -} + + */ +class ResultModifierTest extends TestCase +{ + public function testBadInitializer() + { + $this->expectException(\InvalidArgumentException::class); + ResultModifier::initialize('only array and null are allowed here!'); + } + + public function testBadLimit() + { + $this->expectException(\InvalidArgumentException::class); + ResultModifier::initialize([ + 'limit' => 5, + 'skip' => 8, + ])->limit('bad')->skip(5); + } + + public function testBadOffset() + { + $this->expectException(\InvalidArgumentException::class); + ResultModifier::initialize([ + 'limit' => 5, + 'skip' => 8, + ])->limit(10)->skip('bad'); + } + + public function testBadNameOrdering() + { + $this->expectException(\InvalidArgumentException::class); + ResultModifier::initialize([ + 'limit' => 5, + 'skip' => 8, + ])->order(null, FieldOrdering::ASC); + } + + public function testBadOrderOrdering() + { + $this->expectException(\InvalidArgumentException::class); + ResultModifier::initialize([ + 'limit' => 5, + 'skip' => 8, + ])->order('name', null); + } + + public function testLimitAndOffset() + { + $exportResult = [ + 'limit' => 8, + 'skip' => 5, + 'order' => [], + ]; + + $resMod = ResultModifier::initialize([ + 'limit' => 5, + 'skip' => 8, + ])->limit(8)->skip(5); + + $exportMethod = new \ReflectionMethod($resMod, 'export'); + $exportMethod->setAccessible(true); + + $this->assertEquals($exportResult, $exportMethod->invoke($resMod)); + } + + public function testOrdering() + { + $exportResult = [ + 'limit' => 0, + 'skip' => 0, + 'order' => [ + 'name' => FieldOrdering::ASC, + 'surname' => FieldOrdering::ASC, + 'year' => FieldOrdering::DESC, + ], + ]; + + $resMod = ResultModifier::initialize([]) + ->order('name', FieldOrdering::ASC) + ->order('surname', FieldOrdering::ASC) + ->order('year', FieldOrdering::DESC); + + $exportMethod = new \ReflectionMethod($resMod, 'export'); + $exportMethod->setAccessible(true); + + $this->assertEquals($exportResult, $exportMethod->invoke($resMod)); + } + + public function testOrderingOnInitializer() + { + $exportResult = [ + 'limit' => 0, + 'skip' => 0, + 'order' => [ + 'name' => FieldOrdering::ASC, + 'surname' => FieldOrdering::ASC, + 'year' => FieldOrdering::DESC, + ], + ]; + + $resMod = ResultModifier::initialize([ + 'name' => FieldOrdering::ASC, + 'surname' => FieldOrdering::ASC, + 'year' => FieldOrdering::DESC, + ]); + + $exportMethod = new \ReflectionMethod($resMod, 'export'); + $exportMethod->setAccessible(true); + + $this->assertEquals($exportResult, $exportMethod->invoke($resMod)); + } +} diff --git a/tests/Database/Runtime/SelectionCriteriaTest.php b/tests/Database/Runtime/SelectionCriteriaTest.php index 0d762973..b671d070 100644 --- a/tests/Database/Runtime/SelectionCriteriaTest.php +++ b/tests/Database/Runtime/SelectionCriteriaTest.php @@ -1,130 +1,130 @@ - - */ -class SelectionCriteriaTest extends TestCase -{ - - public function testBadNameAnd() - { - $this->expectException(\InvalidArgumentException::class); - SelectionCriteria::select(['a' => [3, 5, 6]])->AndWhere(3, FieldRelation::EQUAL, ''); - } - - public function testBadRelationAnd() - { - $this->expectException(\InvalidArgumentException::class); - SelectionCriteria::select(['a' => [3, 5, 6]])->AndWhere('a', 'IDK', ''); - } - - public function testBadValueAnd() - { - $this->expectException(\InvalidArgumentException::class); - SelectionCriteria::select(['a' => [3, 5, 6]])->AndWhere('a', FieldRelation::EQUAL, new SelectionCriteria()); - } - - public function testBadValueOr() - { - $this->expectException(\InvalidArgumentException::class); - SelectionCriteria::select(['a' => [3, 5, 6]])->OrWhere('a', FieldRelation::EQUAL, new SelectionCriteria()); - } - - public function testBadNameOr() - { - $this->expectException(\InvalidArgumentException::class); - SelectionCriteria::select(['a' => [3, 5, 6]])->OrWhere(3, FieldRelation::EQUAL, ''); - } - - public function testBadRelationOr() - { - $this->expectException(\InvalidArgumentException::class); - SelectionCriteria::select(['a' => [3, 5, 6]])->OrWhere('a', 'IDK', ''); - } - - public function testInitializerOnly() - { - $sc = SelectionCriteria::select(['a' => [3, 5, 6], 'b' => 96]); - - $exportMethod = new \ReflectionMethod($sc, 'export'); - $exportMethod->setAccessible(true); - $resultModifierExported = $exportMethod->invoke($sc); - - $this->assertEquals([ - 'historic' => [128, 129], - 'criteria' => [ - 'and' => [ - [ - 0 => 'a', - 1 => FieldRelation::IN_RANGE, - 2 => [3, 5, 6], - ], - [ - 0 => 'b', - 1 => FieldRelation::EQUAL, - 2 => 96, - ], - ], - 'or' => [], - ], - ], $resultModifierExported); - } - - public function testOrAfterInitializer() - { - $sc = SelectionCriteria::select(['a' => [3, 5, 6], 'b' => 96])->OrWhere('c', FieldRelation::LIKE, '%test%'); - - $exportMethod = new \ReflectionMethod($sc, 'export'); - $exportMethod->setAccessible(true); - $resultModifierExported = $exportMethod->invoke($sc); - - $this->assertEquals([ - 'historic' => [128, 129, 0], - 'criteria' => [ - 'and' => [ - [ - 0 => 'a', - 1 => FieldRelation::IN_RANGE, - 2 => [3, 5, 6], - ], - [ - 0 => 'b', - 1 => FieldRelation::EQUAL, - 2 => 96, - ], - ], - 'or' => [ - [ - 0 => 'c', - 1 => FieldRelation::LIKE, - 2 => '%test%', - ], - ], - ], - ], $resultModifierExported); - } -} + + */ +class SelectionCriteriaTest extends TestCase +{ + + public function testBadNameAnd() + { + $this->expectException(\InvalidArgumentException::class); + SelectionCriteria::select(['a' => [3, 5, 6]])->AndWhere(3, FieldRelation::EQUAL, ''); + } + + public function testBadRelationAnd() + { + $this->expectException(\InvalidArgumentException::class); + SelectionCriteria::select(['a' => [3, 5, 6]])->AndWhere('a', 'IDK', ''); + } + + public function testBadValueAnd() + { + $this->expectException(\InvalidArgumentException::class); + SelectionCriteria::select(['a' => [3, 5, 6]])->AndWhere('a', FieldRelation::EQUAL, new SelectionCriteria()); + } + + public function testBadValueOr() + { + $this->expectException(\InvalidArgumentException::class); + SelectionCriteria::select(['a' => [3, 5, 6]])->OrWhere('a', FieldRelation::EQUAL, new SelectionCriteria()); + } + + public function testBadNameOr() + { + $this->expectException(\InvalidArgumentException::class); + SelectionCriteria::select(['a' => [3, 5, 6]])->OrWhere(3, FieldRelation::EQUAL, ''); + } + + public function testBadRelationOr() + { + $this->expectException(\InvalidArgumentException::class); + SelectionCriteria::select(['a' => [3, 5, 6]])->OrWhere('a', 'IDK', ''); + } + + public function testInitializerOnly() + { + $sc = SelectionCriteria::select(['a' => [3, 5, 6], 'b' => 96]); + + $exportMethod = new \ReflectionMethod($sc, 'export'); + $exportMethod->setAccessible(true); + $resultModifierExported = $exportMethod->invoke($sc); + + $this->assertEquals([ + 'historic' => [128, 129], + 'criteria' => [ + 'and' => [ + [ + 0 => 'a', + 1 => FieldRelation::IN_RANGE, + 2 => [3, 5, 6], + ], + [ + 0 => 'b', + 1 => FieldRelation::EQUAL, + 2 => 96, + ], + ], + 'or' => [], + ], + ], $resultModifierExported); + } + + public function testOrAfterInitializer() + { + $sc = SelectionCriteria::select(['a' => [3, 5, 6], 'b' => 96])->OrWhere('c', FieldRelation::LIKE, '%test%'); + + $exportMethod = new \ReflectionMethod($sc, 'export'); + $exportMethod->setAccessible(true); + $resultModifierExported = $exportMethod->invoke($sc); + + $this->assertEquals([ + 'historic' => [128, 129, 0], + 'criteria' => [ + 'and' => [ + [ + 0 => 'a', + 1 => FieldRelation::IN_RANGE, + 2 => [3, 5, 6], + ], + [ + 0 => 'b', + 1 => FieldRelation::EQUAL, + 2 => 96, + ], + ], + 'or' => [ + [ + 0 => 'c', + 1 => FieldRelation::LIKE, + 2 => '%test%', + ], + ], + ], + ], $resultModifierExported); + } +} diff --git a/tests/Database/Schema/ColumnRelationTest.php b/tests/Database/Schema/ColumnRelationTest.php index ef3d2bea..060cc50e 100644 --- a/tests/Database/Schema/ColumnRelationTest.php +++ b/tests/Database/Schema/ColumnRelationTest.php @@ -1,78 +1,78 @@ - - */ -class ColumnRelationTest extends TestCase -{ - public function testColumnRelationNotPrimaryKey() - { - $this->expectException(DatabaseException::class); - - $externTable = new Table(__FUNCTION__); - - $externColumn = new Column('id', ColumnType::INTEGER); - // $externColumn->setPrimaryKey(true); this is not a primary key, so... an error should be triggered - $externColumn->setNotNull(true); - - $externTable->addColumn($externColumn); - - new ColumnRelation($externTable, $externColumn); - } - - public function testColumnRelationUnbindedColumn() - { - $this->expectException(DatabaseException::class); - - $externTable = new Table(__FUNCTION__); - - $externColumn = new Column('id', ColumnType::INTEGER); - $externColumn->setPrimaryKey(true); - $externColumn->setNotNull(true); - - $relation = new ColumnRelation($externTable, $externColumn); - } - - public function testColumnRelation() - { - $externTable = new Table(__FUNCTION__); - - $externColumn = new Column('id', ColumnType::INTEGER); - $externColumn->setPrimaryKey(true); - $externColumn->setNotNull(true); - - $externTable->addColumn($externColumn); - - $relation = new ColumnRelation($externTable, $externColumn); - - $this->assertEquals($externTable, $relation->getForeignTable()); - $this->assertEquals($externColumn, $relation->getForeignKey()); - } -} + + */ +class ColumnRelationTest extends TestCase +{ + public function testColumnRelationNotPrimaryKey() + { + $this->expectException(DatabaseException::class); + + $externTable = new Table(__FUNCTION__); + + $externColumn = new Column('id', ColumnType::INTEGER); + // $externColumn->setPrimaryKey(true); this is not a primary key, so... an error should be triggered + $externColumn->setNotNull(true); + + $externTable->addColumn($externColumn); + + new ColumnRelation($externTable, $externColumn); + } + + public function testColumnRelationUnbindedColumn() + { + $this->expectException(DatabaseException::class); + + $externTable = new Table(__FUNCTION__); + + $externColumn = new Column('id', ColumnType::INTEGER); + $externColumn->setPrimaryKey(true); + $externColumn->setNotNull(true); + + $relation = new ColumnRelation($externTable, $externColumn); + } + + public function testColumnRelation() + { + $externTable = new Table(__FUNCTION__); + + $externColumn = new Column('id', ColumnType::INTEGER); + $externColumn->setPrimaryKey(true); + $externColumn->setNotNull(true); + + $externTable->addColumn($externColumn); + + $relation = new ColumnRelation($externTable, $externColumn); + + $this->assertEquals($externTable, $relation->getForeignTable()); + $this->assertEquals($externColumn, $relation->getForeignKey()); + } +} diff --git a/tests/Database/Schema/ColumnTest.php b/tests/Database/Schema/ColumnTest.php index ad61360f..b53ef478 100644 --- a/tests/Database/Schema/ColumnTest.php +++ b/tests/Database/Schema/ColumnTest.php @@ -1,140 +1,140 @@ - - */ -class ColumnTest extends TestCase -{ - public function testColumnBadNaming() - { - $this->expectException(\InvalidArgumentException::class); - - //create a testing column - new Column('', ColumnType::INTEGER); - } - - public function testColumnNaming() - { - //create a testing column - $col = new Column('test', ColumnType::INTEGER); - - $this->assertEquals('test', $col->getName()); - } - - public function testColumnPrimaryKey() - { - //create a testing column (by default a new column is NOT a primary key) - $col = new Column('id', ColumnType::INTEGER); - - $this->assertEquals(false, $col->getPrimaryKey()); - - $col->setPrimaryKey(true); - - $this->assertEquals(true, $col->getPrimaryKey()); - } - - public function testColumnBadPrimaryKey() - { - $this->expectException(\InvalidArgumentException::class); - - //create a testing column - $col = new Column('id', ColumnType::INTEGER); - $col->setPrimaryKey(null); - } - - public function testColumnAutoIncrement() - { - //create a testing column (by default a new column is NOT a primary key) - $col = new Column('id', ColumnType::INTEGER); - - $this->assertEquals(false, $col->getAutoIncrement()); - - $col->setAutoIncrement(true); - - $this->assertEquals(true, $col->getAutoIncrement()); - } - - public function testColumnBadAutoIncrement() - { - $this->expectException(\InvalidArgumentException::class); - - //create a testing column - $col = new Column('id', ColumnType::INTEGER); - $col->setAutoIncrement(null); - } - - public function testColumnBadNotNull() - { - $this->expectException(\InvalidArgumentException::class); - - //create a testing column - $col = new Column('id', ColumnType::INTEGER); - $col->setNotNull(null); - } - - public function testColumnNotNull() - { - //create a testing column (by default a new column is NOT a primary key) - $col = new Column('id', ColumnType::INTEGER); - - $this->assertEquals(false, $col->getNotNull()); - - $col->setNotNull(true); - - $this->assertEquals(true, $col->getNotNull()); - } - - public function testColumnBadType() - { - $this->expectException(\InvalidArgumentException::class); - - //create a testing column - $col = new Column('id', null); - $col->setNotNull(null); - } - - public function testColumnType() - { - //create a testing column (by default a new column is NOT a primary key) - $col = new Column('id', ColumnType::INTEGER); - - $this->assertEquals(ColumnType::INTEGER, $col->getType()); - - $col->setType(ColumnType::TEXT); - $this->assertEquals(ColumnType::TEXT, $col->getType()); - - $col->setType(ColumnType::DOUBLE); - $this->assertEquals(ColumnType::DOUBLE, $col->getType()); - - $col->setType(ColumnType::DATETIME); - $this->assertEquals(ColumnType::DATETIME, $col->getType()); - - $col->setType(ColumnType::INTEGER); - $this->assertEquals(ColumnType::INTEGER, $col->getType()); - } -} + + */ +class ColumnTest extends TestCase +{ + public function testColumnBadNaming() + { + $this->expectException(\InvalidArgumentException::class); + + //create a testing column + new Column('', ColumnType::INTEGER); + } + + public function testColumnNaming() + { + //create a testing column + $col = new Column('test', ColumnType::INTEGER); + + $this->assertEquals('test', $col->getName()); + } + + public function testColumnPrimaryKey() + { + //create a testing column (by default a new column is NOT a primary key) + $col = new Column('id', ColumnType::INTEGER); + + $this->assertEquals(false, $col->getPrimaryKey()); + + $col->setPrimaryKey(true); + + $this->assertEquals(true, $col->getPrimaryKey()); + } + + public function testColumnBadPrimaryKey() + { + $this->expectException(\InvalidArgumentException::class); + + //create a testing column + $col = new Column('id', ColumnType::INTEGER); + $col->setPrimaryKey(null); + } + + public function testColumnAutoIncrement() + { + //create a testing column (by default a new column is NOT a primary key) + $col = new Column('id', ColumnType::INTEGER); + + $this->assertEquals(false, $col->getAutoIncrement()); + + $col->setAutoIncrement(true); + + $this->assertEquals(true, $col->getAutoIncrement()); + } + + public function testColumnBadAutoIncrement() + { + $this->expectException(\InvalidArgumentException::class); + + //create a testing column + $col = new Column('id', ColumnType::INTEGER); + $col->setAutoIncrement(null); + } + + public function testColumnBadNotNull() + { + $this->expectException(\InvalidArgumentException::class); + + //create a testing column + $col = new Column('id', ColumnType::INTEGER); + $col->setNotNull(null); + } + + public function testColumnNotNull() + { + //create a testing column (by default a new column is NOT a primary key) + $col = new Column('id', ColumnType::INTEGER); + + $this->assertEquals(false, $col->getNotNull()); + + $col->setNotNull(true); + + $this->assertEquals(true, $col->getNotNull()); + } + + public function testColumnBadType() + { + $this->expectException(\InvalidArgumentException::class); + + //create a testing column + $col = new Column('id', null); + $col->setNotNull(null); + } + + public function testColumnType() + { + //create a testing column (by default a new column is NOT a primary key) + $col = new Column('id', ColumnType::INTEGER); + + $this->assertEquals(ColumnType::INTEGER, $col->getType()); + + $col->setType(ColumnType::TEXT); + $this->assertEquals(ColumnType::TEXT, $col->getType()); + + $col->setType(ColumnType::DOUBLE); + $this->assertEquals(ColumnType::DOUBLE, $col->getType()); + + $col->setType(ColumnType::DATETIME); + $this->assertEquals(ColumnType::DATETIME, $col->getType()); + + $col->setType(ColumnType::INTEGER); + $this->assertEquals(ColumnType::INTEGER, $col->getType()); + } +} diff --git a/tests/Database/Schema/TableTest.php b/tests/Database/Schema/TableTest.php index 9d584196..50c72c79 100644 --- a/tests/Database/Schema/TableTest.php +++ b/tests/Database/Schema/TableTest.php @@ -1,103 +1,103 @@ - - */ -class TableTest extends TestCase -{ - public function testTableBadName() - { - $this->expectException(\InvalidArgumentException::class); - - new Table(''); - } - - public function testTableName() - { - $table = new Table(__FUNCTION__); - - $this->assertEquals(__FUNCTION__, $table->getName()); - } - - public function testTableDuplicateColumns() - { - $this->expectException(DatabaseException::class); - - $table = new Table(__FUNCTION__); - - $columnOne = new Column('id', ColumnType::INTEGER); - $columnTwo = new Column('id', ColumnType::TEXT); - - $table->addColumn($columnOne)->addColumn($columnTwo); - } - - public function testTableColumns() - { - $table = new Table(__FUNCTION__); - - $columnOne = new Column('id', ColumnType::INTEGER); - $columnTwo = new Column('test', ColumnType::TEXT); - $columnThree = new Column('created_at', ColumnType::DATETIME); - - $table->addColumn($columnOne)->addColumn($columnTwo)->addColumn($columnThree); - - $this->assertEquals([ - $columnOne, - $columnTwo, - $columnThree, - ], $table->getColumns()); - } - - public function testTableColumnRelation() - { - $externTable = new Table(__FUNCTION__.'_extern'); - - $externColumn = new Column('id', ColumnType::INTEGER); - $externColumn->setPrimaryKey(true); - $externColumn->setNotNull(true); - - $externTable->addColumn($externColumn); - - $localColumn = new Column(($externTable->getName()).'_id', ColumnType::INTEGER); - - $relation = new ColumnRelation($externTable, $externColumn); - - //at the beginning no relation - $this->assertEquals(null, $localColumn->getRelation()); - - $localColumn->setRelation($relation); - - $this->assertEquals($externTable, $relation->getForeignTable()); - $this->assertEquals($externColumn, $relation->getForeignKey()); - - //after adding one there should be one :) - $this->assertEquals($relation, $localColumn->getRelation()); - } -} + + */ +class TableTest extends TestCase +{ + public function testTableBadName() + { + $this->expectException(\InvalidArgumentException::class); + + new Table(''); + } + + public function testTableName() + { + $table = new Table(__FUNCTION__); + + $this->assertEquals(__FUNCTION__, $table->getName()); + } + + public function testTableDuplicateColumns() + { + $this->expectException(DatabaseException::class); + + $table = new Table(__FUNCTION__); + + $columnOne = new Column('id', ColumnType::INTEGER); + $columnTwo = new Column('id', ColumnType::TEXT); + + $table->addColumn($columnOne)->addColumn($columnTwo); + } + + public function testTableColumns() + { + $table = new Table(__FUNCTION__); + + $columnOne = new Column('id', ColumnType::INTEGER); + $columnTwo = new Column('test', ColumnType::TEXT); + $columnThree = new Column('created_at', ColumnType::DATETIME); + + $table->addColumn($columnOne)->addColumn($columnTwo)->addColumn($columnThree); + + $this->assertEquals([ + $columnOne, + $columnTwo, + $columnThree, + ], $table->getColumns()); + } + + public function testTableColumnRelation() + { + $externTable = new Table(__FUNCTION__.'_extern'); + + $externColumn = new Column('id', ColumnType::INTEGER); + $externColumn->setPrimaryKey(true); + $externColumn->setNotNull(true); + + $externTable->addColumn($externColumn); + + $localColumn = new Column(($externTable->getName()).'_id', ColumnType::INTEGER); + + $relation = new ColumnRelation($externTable, $externColumn); + + //at the beginning no relation + $this->assertEquals(null, $localColumn->getRelation()); + + $localColumn->setRelation($relation); + + $this->assertEquals($externTable, $relation->getForeignTable()); + $this->assertEquals($externColumn, $relation->getForeignKey()); + + //after adding one there should be one :) + $this->assertEquals($relation, $localColumn->getRelation()); + } +} diff --git a/tests/Application/FakeController.php b/tests/FakeController.php similarity index 61% rename from tests/Application/FakeController.php rename to tests/FakeController.php index 5a382a52..ad3a8cbd 100644 --- a/tests/Application/FakeController.php +++ b/tests/FakeController.php @@ -1,42 +1,53 @@ - - */ -class FakeController extends \Gishiki\Core\MVC\Controller -{ - public function myAction() - { - $this->response->write('My email is: '.$this->arguments->mail); - } - - public static function quickAction(Request &$request, Response &$response, GenericCollection &$collection) - { - $response->write('should I send an email to '.$collection->mail.'?'); - } -} + + */ +class FakeController extends Controller +{ + public function none() + { + + } + + public function do() + { + $this->response->getBody()->write('Th1s 1s 4 t3st'); + } + + public function myAction() + { + $this->response->getBody()->write('My email is: '.$this->arguments->mail); + } + + public function quickAction() + { + if (!($this->arguments instanceof GenericCollection)) { + throw new \RuntimeException("something went wrong"); + } + + $this->response->getBody()->write('should I send an email to '.$this->arguments->mail.'?'); + } +} diff --git a/tests/Http/BodyTest.php b/tests/Http/BodyTest.php deleted file mode 100644 index 22eebab7..00000000 --- a/tests/Http/BodyTest.php +++ /dev/null @@ -1,404 +0,0 @@ -stream) === true) { - fclose($this->stream); - } - } - - /** - * This method creates a new resource, and it seeds - * the resource with lorem ipsum text. The returned - * resource is readable, writable, and seekable. - * - * @param string $mode - * - * @return resource - */ - public function resourceFactory($mode = 'r+') - { - $stream = fopen('php://temp', $mode); - fwrite($stream, $this->text); - rewind($stream); - - return $stream; - } - - public function testConstructorAttachesStream() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - - $this->assertSame($this->stream, $bodyStream->getValue($body)); - } - - public function testConstructorInvalidStream() - { - $this->expectException(\InvalidArgumentException::class); - - $this->stream = 'foo'; - $body = new Body($this->stream); - } - - public function testGetMetadata() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertTrue(is_array($body->getMetadata())); - } - - public function testGetMetadataKey() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals('php://temp', $body->getMetadata('uri')); - } - - public function testGetMetadataKeyNotFound() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertNull($body->getMetadata('foo')); - } - - public function testDetach() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - - $bodyMetadata = new ReflectionProperty($body, 'meta'); - $bodyMetadata->setAccessible(true); - - $bodyReadable = new ReflectionProperty($body, 'readable'); - $bodyReadable->setAccessible(true); - - $bodyWritable = new ReflectionProperty($body, 'writable'); - $bodyWritable->setAccessible(true); - - $bodySeekable = new ReflectionProperty($body, 'seekable'); - $bodySeekable->setAccessible(true); - - $result = $body->detach(); - - $this->assertSame($this->stream, $result); - $this->assertNull($bodyStream->getValue($body)); - $this->assertNull($bodyMetadata->getValue($body)); - $this->assertNull($bodyReadable->getValue($body)); - $this->assertNull($bodyWritable->getValue($body)); - $this->assertNull($bodySeekable->getValue($body)); - } - - public function testToStringAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals($this->text, (string) $body); - } - - public function testToStringAttachedRewindsFirst() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals($this->text, (string) $body); - $this->assertEquals($this->text, (string) $body); - $this->assertEquals($this->text, (string) $body); - } - - public function testToStringDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($body, null); - - $this->assertEquals('', (string) $body); - } - - public function testClose() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->close(); - - $this->assertAttributeEquals(null, 'stream', $body); - //$this->assertFalse($body->isAttached()); #1269 - } - - public function testGetSizeAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals(mb_strlen($this->text), $body->getSize()); - } - - public function testGetSizeDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($body, null); - - $this->assertNull($body->getSize()); - } - - public function testTellAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - fseek($this->stream, 10); - - $this->assertEquals(10, $body->tell()); - } - - public function testTellDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($body, null); - - $this->expectException('\RuntimeException'); - $body->tell(); - } - - public function testEofAttachedFalse() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - fseek($this->stream, 10); - - $this->assertFalse($body->eof()); - } - - public function testEofAttachedTrue() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - while (feof($this->stream) === false) { - fread($this->stream, 1024); - } - - $this->assertTrue($body->eof()); - } - - public function testEofDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $bodyStream = new ReflectionProperty($body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($body, null); - - $this->assertTrue($body->eof()); - } - - public function isReadableAttachedTrue() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertTrue($body->isReadable()); - } - - public function isReadableAttachedFalse() - { - $stream = fopen('php://temp', 'w'); - $body = new Body($this->stream); - - $this->assertFalse($body->isReadable()); - fclose($stream); - } - - public function testIsReadableDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->assertFalse($body->isReadable()); - } - - public function isWritableAttachedTrue() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertTrue($body->isWritable()); - } - - public function isWritableAttachedFalse() - { - $stream = fopen('php://temp', 'r'); - $body = new Body($this->stream); - - $this->assertFalse($body->isWritable()); - fclose($stream); - } - - public function testIsWritableDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->assertFalse($body->isWritable()); - } - - public function isSeekableAttachedTrue() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertTrue($body->isSeekable()); - } - - // TODO: Is seekable is false when attached... how? - - public function testIsSeekableDetached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->assertFalse($body->isSeekable()); - } - - public function testSeekAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->seek(10); - - $this->assertEquals(10, ftell($this->stream)); - } - - public function testSeekDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->expectException('\RuntimeException'); - $body->seek(10); - } - - public function testRewindAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - fseek($this->stream, 10); - $body->rewind(); - - $this->assertEquals(0, ftell($this->stream)); - } - - public function testRewindDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->expectException('\RuntimeException'); - $body->rewind(); - } - - public function testReadAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - - $this->assertEquals(substr($this->text, 0, 10), $body->read(10)); - } - - public function testReadDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->expectException('\RuntimeException'); - $body->read(10); - } - - public function testWriteAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - while (feof($this->stream) === false) { - fread($this->stream, 1024); - } - $body->write('foo'); - - $this->assertEquals($this->text.'foo', (string) $body); - } - - public function testWriteDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->expectException('\RuntimeException'); - $body->write('foo'); - } - - public function testGetContentsAttached() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - fseek($this->stream, 10); - - $this->assertEquals(substr($this->text, 10), $body->getContents()); - } - - public function testGetContentsDetachedThrowsRuntimeException() - { - $this->stream = $this->resourceFactory(); - $body = new Body($this->stream); - $body->detach(); - - $this->expectException('\RuntimeException'); - $body->getContents(); - } -} diff --git a/tests/Http/CookiesTest.php b/tests/Http/CookiesTest.php deleted file mode 100644 index b22a9662..00000000 --- a/tests/Http/CookiesTest.php +++ /dev/null @@ -1,43 +0,0 @@ - 'toast', - 'domain' => null, - 'path' => null, - 'expires' => null, - 'secure' => true, - 'httponly' => true, - ]; - - $cookies = new Cookies(); - - $prop = new ReflectionProperty($cookies, 'defaults'); - $prop->setAccessible(true); - - $origDefaults = $prop->getValue($cookies); - - $cookies->setDefaults($defaults); - - $this->assertEquals($defaults, $prop->getValue($cookies)); - $this->assertNotEquals($origDefaults, $prop->getValue($cookies)); - } -} diff --git a/tests/Http/EnvironmentTest.php b/tests/Http/EnvironmentTest.php deleted file mode 100644 index 04898cdf..00000000 --- a/tests/Http/EnvironmentTest.php +++ /dev/null @@ -1,62 +0,0 @@ -assertEquals($_SERVER, $env->all()); - } - - /** - * Test environment from mock data. - */ - public function testMock() - { - $env = Environment::mock([ - 'SCRIPT_NAME' => '/foo/bar/index.php', - 'REQUEST_URI' => '/foo/bar?abc=123', - ]); - - $this->assertInstanceOf('\Gishiki\Core\Environment', $env); - $this->assertEquals('/foo/bar/index.php', $env->get('SCRIPT_NAME')); - $this->assertEquals('/foo/bar?abc=123', $env->get('REQUEST_URI')); - $this->assertEquals('localhost', $env->get('HTTP_HOST')); - } -} diff --git a/tests/Http/HeadersTest.php b/tests/Http/HeadersTest.php deleted file mode 100644 index e6560ca1..00000000 --- a/tests/Http/HeadersTest.php +++ /dev/null @@ -1,208 +0,0 @@ - 'application/json', - ]); - $h = Headers::createFromEnvironment($e); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['accept'])); - $this->assertEquals('application/json', $prop->getValue($h)['accept']['value'][0]); - } - - public function testCreateFromEnvironmentWithSpecialHeaders() - { - $e = Environment::mock([ - 'CONTENT_TYPE' => 'application/json', - ]); - $h = Headers::createFromEnvironment($e); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['content-type'])); - $this->assertEquals('application/json', $prop->getValue($h)['content-type']['value'][0]); - } - - public function testCreateFromEnvironmentIgnoresHeaders() - { - $e = Environment::mock([ - 'CONTENT_TYPE' => 'text/csv', - 'HTTP_CONTENT_LENGTH' => 1230, // <-- Ignored - ]); - $h = Headers::createFromEnvironment($e); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertNotContains('content-length', $prop->getValue($h)); - } - - public function testConstructor() - { - $h = new Headers([ - 'Content-Length' => 100, - ]); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['content-length'])); - $this->assertEquals(100, $prop->getValue($h)['content-length']['value'][0]); - } - - public function testSetSingleValue() - { - $h = new Headers(); - $h->set('Content-Length', 100); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['content-length'])); - $this->assertEquals(100, $prop->getValue($h)['content-length']['value'][0]); - } - - public function testSetArrayValue() - { - $h = new Headers(); - $h->set('Allow', ['GET', 'POST']); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['allow'])); - $this->assertEquals(['GET', 'POST'], $prop->getValue($h)['allow']['value']); - } - - public function testGet() - { - $h = new Headers(); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - $prop->setValue($h, [ - 'allow' => [ - 'value' => ['GET', 'POST'], - 'originalKey' => 'Allow', - ], - ]); - - $this->assertEquals(['GET', 'POST'], $h->get('Allow')); - } - - public function testGetNotExists() - { - $h = new Headers(); - - $this->assertNull($h->get('Foo')); - } - - public function testAddNewValue() - { - $h = new Headers(); - $h->add('Foo', 'Bar'); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['foo'])); - $this->assertEquals(['Bar'], $prop->getValue($h)['foo']['value']); - } - - public function testAddAnotherValue() - { - $h = new Headers(); - $h->add('Foo', 'Bar'); - $h->add('Foo', 'Xyz'); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['foo'])); - $this->assertEquals(['Bar', 'Xyz'], $prop->getValue($h)['foo']['value']); - } - - public function testAddArrayValue() - { - $h = new Headers(); - $h->add('Foo', 'Bar'); - $h->add('Foo', ['Xyz', '123']); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - - $this->assertTrue(is_array($prop->getValue($h)['foo'])); - $this->assertEquals(['Bar', 'Xyz', '123'], $prop->getValue($h)['foo']['value']); - } - - public function testHas() - { - $h = new Headers(); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - $prop->setValue($h, [ - 'allow' => [ - 'value' => ['GET', 'POST'], - 'originalKey' => 'Allow', - ], - ]); - $this->assertTrue($h->has('allow')); - $this->assertFalse($h->has('foo')); - } - - public function testRemove() - { - $h = new Headers(); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - $prop->setValue($h, [ - 'Allow' => [ - 'value' => ['GET', 'POST'], - 'originalKey' => 'Allow', - ], - ]); - $h->remove('Allow'); - - $this->assertNotContains('Allow', $prop->getValue($h)); - } - - public function testOriginalKeys() - { - $h = new Headers(); - $prop = new ReflectionProperty($h, 'data'); - $prop->setAccessible(true); - $prop->setValue($h, [ - 'Allow' => [ - 'value' => ['GET', 'POST'], - 'originalKey' => 'ALLOW', - ], - ]); - $all = $h->all(); - - $this->assertArrayHasKey('ALLOW', $all); - } - - public function testNormalizeKey() - { - $h = new Headers(); - $this->assertEquals('foo-bar', $h->normalizeKey('HTTP_FOO_BAR')); - $this->assertEquals('foo-bar', $h->normalizeKey('HTTP-FOO-BAR')); - $this->assertEquals('foo-bar', $h->normalizeKey('Http-Foo-Bar')); - $this->assertEquals('foo-bar', $h->normalizeKey('Http_Foo_Bar')); - $this->assertEquals('foo-bar', $h->normalizeKey('http_foo_bar')); - $this->assertEquals('foo-bar', $h->normalizeKey('http-foo-bar')); - } -} diff --git a/tests/Http/MessageTest.php b/tests/Http/MessageTest.php deleted file mode 100644 index 53b818a9..00000000 --- a/tests/Http/MessageTest.php +++ /dev/null @@ -1,218 +0,0 @@ -protocolVersion = '1.0'; - - $this->assertEquals('1.0', $message->getProtocolVersion()); - } - - /** - * @covers \Gishiki\HttpKernel\Message::withProtocolVersion - */ - public function testWithProtocolVersion() - { - $message = new MessageStub(); - $clone = $message->withProtocolVersion('1.0'); - - $this->assertEquals('1.0', $clone->protocolVersion); - } - - /** - * @covers \Gishiki\HttpKernel\Message::withProtocolVersion - */ - public function testWithProtocolVersionInvalidThrowsException() - { - $this->expectException(\InvalidArgumentException::class); - - $message = new MessageStub(); - $message->withProtocolVersion('3.0'); - } - - /******************************************************************************* - * Headers - ******************************************************************************/ - - /** - * @covers \Gishiki\HttpKernel\Message::getHeaders - */ - public function testGetHeaders() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $headers->add('X-Foo', 'two'); - $headers->add('X-Foo', 'three'); - - $message = new MessageStub(); - $message->headers = $headers; - - $shouldBe = [ - 'X-Foo' => [ - 'one', - 'two', - 'three', - ], - ]; - - $this->assertEquals($shouldBe, $message->getHeaders()); - } - - /** - * @covers \Gishiki\HttpKernel\Message::hasHeader - */ - public function testHasHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - - $message = new MessageStub(); - $message->headers = $headers; - - $this->assertTrue($message->hasHeader('X-Foo')); - $this->assertFalse($message->hasHeader('X-Bar')); - } - - /** - * @covers \Gishiki\HttpKernel\Message::getHeaderLine - */ - public function testGetHeaderLine() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $headers->add('X-Foo', 'two'); - $headers->add('X-Foo', 'three'); - - $message = new MessageStub(); - $message->headers = $headers; - - $this->assertEquals('one,two,three', $message->getHeaderLine('X-Foo')); - $this->assertEquals('', $message->getHeaderLine('X-Bar')); - } - - /** - * @covers \Gishiki\HttpKernel\Message::getHeader - */ - public function testGetHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $headers->add('X-Foo', 'two'); - $headers->add('X-Foo', 'three'); - - $message = new MessageStub(); - $message->headers = $headers; - - $this->assertEquals(['one', 'two', 'three'], $message->getHeader('X-Foo')); - $this->assertEquals([], $message->getHeader('X-Bar')); - } - - /** - * @covers \Gishiki\HttpKernel\Message::withHeader - */ - public function testWithHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $message = new MessageStub(); - $message->headers = $headers; - $clone = $message->withHeader('X-Foo', 'bar'); - - $this->assertEquals('bar', $clone->getHeaderLine('X-Foo')); - } - - /** - * @covers \Gishiki\HttpKernel\Message::withAddedHeader - */ - public function testWithAddedHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $message = new MessageStub(); - $message->headers = $headers; - $clone = $message->withAddedHeader('X-Foo', 'two'); - - $this->assertEquals('one,two', $clone->getHeaderLine('X-Foo')); - } - - /** - * @covers \Gishiki\HttpKernel\Message::withoutHeader - */ - public function testWithoutHeader() - { - $headers = new Headers(); - $headers->add('X-Foo', 'one'); - $headers->add('X-Bar', 'two'); - $response = new MessageStub(); - $response->headers = $headers; - $clone = $response->withoutHeader('X-Foo'); - $shouldBe = [ - 'X-Bar' => ['two'], - ]; - - $this->assertEquals($shouldBe, $clone->getHeaders()); - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - /** - * @covers \Gishiki\HttpKernel\Message::getBody - */ - public function testGetBody() - { - $body = $this->getBody(); - $message = new MessageStub(); - $message->body = $body; - - $this->assertSame($body, $message->getBody()); - } - - /** - * @covers \Gishiki\HttpKernel\Message::withBody - */ - public function testWithBody() - { - $body = $this->getBody(); - $body2 = $this->getBody(); - $message = new MessageStub(); - $message->body = $body; - $clone = $message->withBody($body2); - - $this->assertSame($body, $message->body); - $this->assertSame($body2, $clone->body); - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject|\Gishiki\HttpKernel\Body - */ - protected function getBody() - { - return $this->getMockBuilder('Gishiki\HttpKernel\Body')->disableOriginalConstructor()->getMock(); - } -} diff --git a/tests/Http/Mocks/MessageStub.php b/tests/Http/Mocks/MessageStub.php deleted file mode 100644 index f616b455..00000000 --- a/tests/Http/Mocks/MessageStub.php +++ /dev/null @@ -1,40 +0,0 @@ -body = new RequestBody(); - $this->body->write($this->text); - $this->body->rewind(); - } - - protected function tearDown() - { - if (is_resource($this->stream) === true) { - fclose($this->stream); - } - $this->body = null; - } - - /** - * This method creates a new resource, and it seeds - * the resource with lorem ipsum text. The returned - * resource is readable, writable, and seekable. - * - * @param string $mode - * - * @return resource - */ - public function resourceFactory($mode = 'r+') - { - $stream = fopen('php://temp', $mode); - fwrite($stream, $this->text); - rewind($stream); - - return $stream; - } - - public function testConstructorAttachesStream() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - - $this->assertInternalType('resource', $bodyStream->getValue($this->body)); - } - - public function testConstructorSetsMetadata() - { - $bodyMetadata = new ReflectionProperty($this->body, 'meta'); - $bodyMetadata->setAccessible(true); - - $this->assertTrue(is_array($bodyMetadata->getValue($this->body))); - } - - public function testGetMetadata() - { - $this->assertTrue(is_array($this->body->getMetadata())); - } - - public function testGetMetadataKey() - { - $this->assertEquals('php://temp', $this->body->getMetadata('uri')); - } - - public function testGetMetadataKeyNotFound() - { - $this->assertNull($this->body->getMetadata('foo')); - } - - public function testDetach() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - - $bodyMetadata = new ReflectionProperty($this->body, 'meta'); - $bodyMetadata->setAccessible(true); - - $bodyReadable = new ReflectionProperty($this->body, 'readable'); - $bodyReadable->setAccessible(true); - - $bodyWritable = new ReflectionProperty($this->body, 'writable'); - $bodyWritable->setAccessible(true); - - $bodySeekable = new ReflectionProperty($this->body, 'seekable'); - $bodySeekable->setAccessible(true); - - $result = $this->body->detach(); - - $this->assertInternalType('resource', $result); - $this->assertNull($bodyStream->getValue($this->body)); - $this->assertNull($bodyMetadata->getValue($this->body)); - $this->assertNull($bodyReadable->getValue($this->body)); - $this->assertNull($bodyWritable->getValue($this->body)); - $this->assertNull($bodySeekable->getValue($this->body)); - } - - public function testToStringAttached() - { - $this->assertEquals($this->text, (string) $this->body); - } - - public function testToStringAttachedRewindsFirst() - { - $this->assertEquals($this->text, (string) $this->body); - $this->assertEquals($this->text, (string) $this->body); - $this->assertEquals($this->text, (string) $this->body); - } - - public function testToStringDetached() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($this->body, null); - - $this->assertEquals('', (string) $this->body); - } - - public function testClose() - { - $this->body->close(); - - $this->assertAttributeEquals(null, 'stream', $this->body); - $this->assertFalse($this->body->isReadable()); - $this->assertFalse($this->body->isWritable()); - $this->assertEquals('', (string) $this->body); - - $this->expectException('RuntimeException'); - $this->body->tell(); - } - - public function testGetSizeAttached() - { - $this->assertEquals(mb_strlen($this->text), $this->body->getSize()); - } - - public function testGetSizeDetached() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($this->body, null); - - $this->assertNull($this->body->getSize()); - } - - public function testTellAttached() - { - $this->body->seek(10); - - $this->assertEquals(10, $this->body->tell()); - } - - public function testTellDetachedThrowsRuntimeException() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($this->body, null); - - $this->expectException('\RuntimeException'); - $this->body->tell(); - } - - public function testEofAttachedFalse() - { - $this->body->seek(10); - - $this->assertFalse($this->body->eof()); - } - - public function testEofAttachedTrue() - { - while ($this->body->eof() === false) { - $this->body->read(1024); - } - - $this->assertTrue($this->body->eof()); - } - - public function testEofDetached() - { - $bodyStream = new ReflectionProperty($this->body, 'stream'); - $bodyStream->setAccessible(true); - $bodyStream->setValue($this->body, null); - - $this->assertTrue($this->body->eof()); - } - - public function testIsReadableAttachedTrue() - { - $this->assertTrue($this->body->isReadable()); - } - - public function testIsReadableDetached() - { - $this->body->detach(); - - $this->assertFalse($this->body->isReadable()); - } - - public function testIsWritableAttachedTrue() - { - $this->assertTrue($this->body->isWritable()); - } - - public function testIsWritableDetached() - { - $this->body->detach(); - - $this->assertFalse($this->body->isWritable()); - } - - public function isSeekableAttachedTrue() - { - $this->assertTrue($this->body->isSeekable()); - } - - // TODO: Is seekable is false when attached... how? - - public function testIsSeekableDetached() - { - $this->body->detach(); - - $this->assertFalse($this->body->isSeekable()); - } - - public function testSeekAttached() - { - $this->body->seek(10); - - $this->assertEquals(10, $this->body->tell()); - } - - public function testSeekDetachedThrowsRuntimeException() - { - $this->body->detach(); - - $this->expectException('\RuntimeException'); - $this->body->seek(10); - } - - public function testRewindAttached() - { - $this->body->seek(10); - $this->body->rewind(); - - $this->assertEquals(0, $this->body->tell()); - } - - public function testRewindDetachedThrowsRuntimeException() - { - $this->body->detach(); - - $this->expectException('\RuntimeException'); - $this->body->rewind(); - } - - public function testReadAttached() - { - $this->assertEquals(substr($this->text, 0, 10), $this->body->read(10)); - } - - public function testReadDetachedThrowsRuntimeException() - { - $this->body->detach(); - - $this->expectException('\RuntimeException'); - $this->body->read(10); - } - - public function testWriteAttached() - { - while ($this->body->eof() === false) { - $this->body->read(1024); - } - $this->body->write('foo'); - - $this->assertEquals($this->text.'foo', (string) $this->body); - } - - public function testWriteDetachedThrowsRuntimeException() - { - $this->body->detach(); - - $this->expectException('\RuntimeException'); - $this->body->write('foo'); - } - - public function testGetContentsAttached() - { - $this->body->seek(10); - - $this->assertEquals(substr($this->text, 10), $this->body->getContents()); - } - - public function testGetContentsDetachedThrowsRuntimeException() - { - $this->body->detach(); - - $this->expectException('\RuntimeException'); - $this->body->getContents(); - } -} diff --git a/tests/Http/RequestTest.php b/tests/Http/RequestTest.php deleted file mode 100644 index 84152763..00000000 --- a/tests/Http/RequestTest.php +++ /dev/null @@ -1,989 +0,0 @@ - 'john', - 'id' => '123', - ]; - $serverParams = $env->all(); - $body = new RequestBody(); - $uploadedFiles = UploadedFile::createFromEnvironment($env); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); - - return $request; - } - - public function testDisableSetter() - { - $request = $this->requestFactory(); - $request->foo = 'bar'; - - $this->assertFalse(property_exists($request, 'foo')); - } - - public function testAddsHostHeaderFromUri() - { - $request = $this->requestFactory(); - $this->assertEquals('example.com', $request->getHeaderLine('Host')); - } - - /******************************************************************************* - * Method - ******************************************************************************/ - - public function testGetMethod() - { - $this->assertEquals('GET', $this->requestFactory()->getMethod()); - } - - public function testGetOriginalMethod() - { - $this->assertEquals('GET', $this->requestFactory()->getOriginalMethod()); - } - - public function testWithMethod() - { - $request = $this->requestFactory()->withMethod('PUT'); - - //$this->assertAttributeEquals(null, 'method', $request); - $this->assertAttributeEquals('PUT', 'originalMethod', $request); - } - - public function testWithMethodInvalid() - { - $this->expectException(\InvalidArgumentException::class); - - $this->requestFactory()->withMethod('FOO'); - } - - public function testWithMethodNull() - { - $request = $this->requestFactory()->withMethod(null); - - $this->assertAttributeEquals(null, 'originalMethod', $request); - } - - /** - * @covers \Gishiki\HttpKernel\Request::createFromEnvironment - */ - public function testCreateFromEnvironment() - { - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'POST', - ]); - - $request = Request::createFromEnvironment($env); - $this->assertEquals('POST', $request->getMethod()); - $this->assertEquals($env->all(), $request->getServerParams()); - } - - /** - * @covers \Gishiki\HttpKernel\Request::createFromEnvironment - */ - public function testCreateFromEnvironmentWithMultipart() - { - $_POST['foo'] = 'bar'; - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'POST', - 'HTTP_CONTENT_TYPE' => 'multipart/form-data; boundary=---foo', - ]); - - $request = Request::createFromEnvironment($env); - unset($_POST); - - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - /** - * @covers \Gishiki\HttpKernel\Request::createFromEnvironment - */ - public function testCreateFromEnvironmentWithMultipartMethodOverride() - { - $_POST['_METHOD'] = 'PUT'; - - $env = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo', - 'REQUEST_METHOD' => 'POST', - 'HTTP_CONTENT_TYPE' => 'multipart/form-data; boundary=---foo', - ]); - - $request = Request::createFromEnvironment($env); - unset($_POST); - - $this->assertEquals('POST', $request->getOriginalMethod()); - $this->assertEquals('PUT', $request->getMethod()); - } - - public function testGetMethodWithOverrideHeader() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers([ - 'HTTP_X_HTTP_METHOD_OVERRIDE' => 'PUT', - ]); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals('PUT', $request->getMethod()); - $this->assertEquals('POST', $request->getOriginalMethod()); - } - - public function testGetMethodWithOverrideParameterFromBodyObject() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers([ - 'Content-Type' => 'application/x-www-form-urlencoded', - ]); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('_METHOD=PUT'); - $body->rewind(); - $request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals('PUT', $request->getMethod()); - $this->assertEquals('POST', $request->getOriginalMethod()); - } - - public function testGetMethodOverrideParameterFromBodyArray() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers([ - 'Content-Type' => 'application/x-www-form-urlencoded', - ]); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('_METHOD=PUT'); - $body->rewind(); - $request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); - $request->registerMediaTypeParser('application/x-www-form-urlencoded', function ($input) { - parse_str($input, $body); - - return $body; // <-- Array - }); - - $this->assertEquals('PUT', $request->getMethod()); - } - - public function testCreateRequestWithInvalidMethodString() - { - $this->expectException(\InvalidArgumentException::class); - - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('FOO', $uri, $headers, $cookies, $serverParams, $body); - } - - public function testCreateRequestWithInvalidMethodOther() - { - $this->expectException(\InvalidArgumentException::class); - - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request(10, $uri, $headers, $cookies, $serverParams, $body); - } - - public function testIsGet() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'originalMethod'); - $prop->setAccessible(true); - $prop->setValue($request, 'GET'); - - $this->assertTrue($request->isGet()); - } - - public function testIsPost() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'originalMethod'); - $prop->setAccessible(true); - $prop->setValue($request, 'POST'); - - $this->assertTrue($request->isPost()); - } - - public function testIsPut() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'originalMethod'); - $prop->setAccessible(true); - $prop->setValue($request, 'PUT'); - - $this->assertTrue($request->isPut()); - } - - public function testIsPatch() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'originalMethod'); - $prop->setAccessible(true); - $prop->setValue($request, 'PATCH'); - - $this->assertTrue($request->isPatch()); - } - - public function testIsDelete() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'originalMethod'); - $prop->setAccessible(true); - $prop->setValue($request, 'DELETE'); - - $this->assertTrue($request->isDelete()); - } - - public function testIsHead() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'originalMethod'); - $prop->setAccessible(true); - $prop->setValue($request, 'HEAD'); - - $this->assertTrue($request->isHead()); - } - - public function testIsOptions() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'originalMethod'); - $prop->setAccessible(true); - $prop->setValue($request, 'OPTIONS'); - - $this->assertTrue($request->isOptions()); - } - - public function testIsXhr() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers([ - 'Content-Type' => 'application/x-www-form-urlencoded', - 'X-Requested-With' => 'XMLHttpRequest', - ]); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - - $this->assertTrue($request->isXhr()); - } - - /******************************************************************************* - * URI - ******************************************************************************/ - - public function testGetRequestTarget() - { - $this->assertEquals('/foo/bar?abc=123', $this->requestFactory()->getRequestTarget()); - } - - public function testGetRequestTargetAlreadySet() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'requestTarget'); - $prop->setAccessible(true); - $prop->setValue($request, '/foo/bar?abc=123'); - - $this->assertEquals('/foo/bar?abc=123', $request->getRequestTarget()); - } - - public function testGetRequestTargetIfNoUri() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'uri'); - $prop->setAccessible(true); - $prop->setValue($request, null); - - $this->assertEquals('/', $request->getRequestTarget()); - } - - public function testWithRequestTarget() - { - $clone = $this->requestFactory()->withRequestTarget('/test?user=1'); - - $this->assertAttributeEquals('/test?user=1', 'requestTarget', $clone); - } - - public function testWithRequestTargetThatHasSpaces() - { - $this->expectException(\InvalidArgumentException::class); - - $this->requestFactory()->withRequestTarget('/test/m ore/stuff?user=1'); - } - - public function testGetUri() - { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body); - - $this->assertSame($uri, $request->getUri()); - } - - public function testWithUri() - { - // Uris - $uri1 = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $uri2 = Uri::createFromString('https://example2.com:443/test?xyz=123'); - - // Request - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $request = new Request('GET', $uri1, $headers, $cookies, $serverParams, $body); - $clone = $request->withUri($uri2); - - $this->assertAttributeSame($uri2, 'uri', $clone); - } - - public function testGetContentType() - { - $headers = new Headers([ - 'Content-Type' => ['application/json;charset=utf8'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals('application/json;charset=utf8', $request->getContentType()); - } - - public function testGetContentTypeEmpty() - { - $request = $this->requestFactory(); - - $this->assertNull($request->getContentType()); - } - - public function testGetMediaType() - { - $headers = new Headers([ - 'Content-Type' => ['application/json;charset=utf8'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals('application/json', $request->getMediaType()); - } - - public function testGetMediaTypeEmpty() - { - $request = $this->requestFactory(); - - $this->assertNull($request->getMediaType()); - } - - public function testGetMediaTypeParams() - { - $headers = new Headers([ - 'Content-Type' => ['application/json;charset=utf8;foo=bar'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals(['charset' => 'utf8', 'foo' => 'bar'], $request->getMediaTypeParams()); - } - - public function testGetMediaTypeParamsEmpty() - { - $headers = new Headers([ - 'Content-Type' => ['application/json'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals([], $request->getMediaTypeParams()); - } - - public function testGetMediaTypeParamsWithoutHeader() - { - $request = $this->requestFactory(); - - $this->assertEquals([], $request->getMediaTypeParams()); - } - - public function testGetContentCharset() - { - $headers = new Headers([ - 'Content-Type' => ['application/json;charset=utf8'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals('utf8', $request->getContentCharset()); - } - - public function testGetContentCharsetEmpty() - { - $headers = new Headers([ - 'Content-Type' => ['application/json'], - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertNull($request->getContentCharset()); - } - - public function testGetContentCharsetWithoutHeader() - { - $request = $this->requestFactory(); - - $this->assertNull($request->getContentCharset()); - } - - public function testGetContentLength() - { - $headers = new Headers([ - 'Content-Length' => '150', // <-- Note we define as a string - ]); - $request = $this->requestFactory(); - $headersProp = new ReflectionProperty($request, 'headers'); - $headersProp->setAccessible(true); - $headersProp->setValue($request, $headers); - - $this->assertEquals(150, $request->getContentLength()); - } - - public function testGetContentLengthWithoutHeader() - { - $request = $this->requestFactory(); - - $this->assertNull($request->getContentLength()); - } - - /******************************************************************************* - * Cookies - ******************************************************************************/ - - public function testGetCookieParams() - { - $shouldBe = [ - 'user' => 'john', - 'id' => '123', - ]; - - $this->assertEquals($shouldBe, $this->requestFactory()->getCookieParams()); - } - - public function testWithCookieParams() - { - $request = $this->requestFactory(); - $clone = $request->withCookieParams(['type' => 'framework']); - - $this->assertEquals(['type' => 'framework'], $clone->getCookieParams()); - } - - /******************************************************************************* - * Query Params - ******************************************************************************/ - - public function testGetQueryParams() - { - $this->assertEquals(['abc' => '123'], $this->requestFactory()->getQueryParams()); - } - - public function testGetQueryParamsAlreadySet() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'queryParams'); - $prop->setAccessible(true); - $prop->setValue($request, ['foo' => 'bar']); - - $this->assertEquals(['foo' => 'bar'], $request->getQueryParams()); - } - - public function testWithQueryParams() - { - $request = $this->requestFactory(); - $clone = $request->withQueryParams(['foo' => 'bar']); - $cloneUri = $clone->getUri(); - - $this->assertEquals('abc=123', $cloneUri->getQuery()); // <-- Unchanged - $this->assertAttributeEquals(['foo' => 'bar'], 'queryParams', $clone); // <-- Changed - } - - public function testGetQueryParamsWithoutUri() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'uri'); - $prop->setAccessible(true); - $prop->setValue($request, null); - - $this->assertEquals([], $request->getQueryParams()); - } - - /******************************************************************************* - * Uploaded files - ******************************************************************************/ - - /** - * @covers \Gishiki\HttpKernel\Request::withUploadedFiles - * @covers \Gishiki\HttpKernel\Request::getUploadedFiles - */ - public function testWithUploadedFiles() - { - $files = [new UploadedFile('foo.txt'), new UploadedFile('bar.txt')]; - - $request = $this->requestFactory(); - $clone = $request->withUploadedFiles($files); - - $this->assertEquals([], $request->getUploadedFiles()); - $this->assertEquals($files, $clone->getUploadedFiles()); - } - - /******************************************************************************* - * Server Params - ******************************************************************************/ - - public function testGetServerParams() - { - $mockEnv = Environment::mock(); - $request = $this->requestFactory(); - - $serverParams = $request->getServerParams(); - foreach ($serverParams as $key => $value) { - if ($key == 'REQUEST_TIME' || $key == 'REQUEST_TIME_FLOAT') { - $this->assertGreaterThanOrEqual( - $mockEnv[$key], - $value, - sprintf('%s value of %s was less than expected value of %s', $key, $value, $mockEnv[$key]) - ); - } else { - $this->assertEquals( - $mockEnv[$key], - $value, - sprintf('%s value of %s did not equal expected value of %s', $key, $value, $mockEnv[$key]) - ); - } - } - } - - /******************************************************************************* - * File Params - ******************************************************************************/ - - /******************************************************************************* - * Attributes - ******************************************************************************/ - - public function testGetAttributes() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new GenericCollection(['foo' => 'bar'])); - - $this->assertEquals(['foo' => 'bar'], $request->getAttributes()); - } - - public function testGetAttribute() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new GenericCollection(['foo' => 'bar'])); - - $this->assertEquals('bar', $request->getAttribute('foo')); - $this->assertNull($request->getAttribute('bar')); - $this->assertEquals(2, $request->getAttribute('bar', 2)); - } - - public function testWithAttribute() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new GenericCollection(['foo' => 'bar'])); - $clone = $request->withAttribute('test', '123'); - - $this->assertEquals('123', $clone->getAttribute('test')); - } - - public function testWithAttributes() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new GenericCollection(['foo' => 'bar'])); - $clone = $request->withAttributes(['test' => '123']); - - $this->assertNull($clone->getAttribute('foo')); - $this->assertEquals('123', $clone->getAttribute('test')); - } - - public function testWithoutAttribute() - { - $request = $this->requestFactory(); - $attrProp = new ReflectionProperty($request, 'attributes'); - $attrProp->setAccessible(true); - $attrProp->setValue($request, new GenericCollection(['foo' => 'bar'])); - $clone = $request->withoutAttribute('foo'); - - $this->assertNull($clone->getAttribute('foo')); - } - - /******************************************************************************* - * Body - ******************************************************************************/ - - public function testGetParsedBodyForm() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/x-www-form-urlencoded;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('foo=bar'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - public function testGetParsedBodyJson() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/json;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('{"foo":"bar"}'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - public function testGetParsedBodyXml() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/xml;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('Josh'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals('Josh', $request->getParsedBody()->name); - } - - public function testGetParsedBodyXmlWithTextXMLMediaType() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'text/xml'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('Josh'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals('Josh', $request->getParsedBody()->name); - } - - public function testGetParsedBodyWhenAlreadyParsed() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'bodyParsed'); - $prop->setAccessible(true); - $prop->setValue($request, ['foo' => 'bar']); - - $this->assertEquals(['foo' => 'bar'], $request->getParsedBody()); - } - - public function testGetParsedBodyWhenBodyDoesNotExist() - { - $request = $this->requestFactory(); - $prop = new ReflectionProperty($request, 'body'); - $prop->setAccessible(true); - $prop->setValue($request, null); - - $this->assertNull($request->getParsedBody()); - } - - public function testGetParsedBodyAsArray() - { - $this->expectException(\RuntimeException::class); - - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers([ - 'Content-Type' => 'application/json;charset=utf8', - ]); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('{"foo": "bar"}'); - $body->rewind(); - $request = new Request('POST', $uri, $headers, $cookies, $serverParams, $body); - $request->registerMediaTypeParser('application/json', function ($input) { - return 10; // <-- Return invalid body value - }); - $request->getParsedBody(); // <-- Triggers exception - } - - public function testWithParsedBody() - { - $clone = $this->requestFactory()->withParsedBody(['xyz' => '123']); - - $this->assertAttributeEquals(['xyz' => '123'], 'bodyParsed', $clone); - } - - public function testWithParsedBodyInvalid() - { - $this->expectException(\InvalidArgumentException::class); - - $this->requestFactory()->withParsedBody(2); - } - - /******************************************************************************* - * Parameters - ******************************************************************************/ - - public function testGetParameterFromBody() - { - $body = new RequestBody(); - $body->write('foo=bar'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals('bar', $request->getParam('foo')); - } - - public function testGetParameterFromQuery() - { - $request = $this->requestFactory()->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals('123', $request->getParam('abc')); - } - - public function testGetParameterFromBodyOverQuery() - { - $body = new RequestBody(); - $body->write('abc=xyz'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - $this->assertEquals('xyz', $request->getParam('abc')); - } - - public function testGetParameterWithDefaultFromBodyOverQuery() - { - $body = new RequestBody(); - $body->write('abc=xyz'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - $this->assertEquals('xyz', $request->getParam('abc')); - $this->assertEquals('bar', $request->getParam('foo', 'bar')); - } - - public function testGetParameters() - { - $body = new RequestBody(); - $body->write('foo=bar'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals(['abc' => '123', 'foo' => 'bar'], $request->getParams()); - } - - public function testGetParametersWithBodyPriority() - { - $body = new RequestBody(); - $body->write('foo=bar&abc=xyz'); - $body->rewind(); - $request = $this->requestFactory() - ->withBody($body) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded'); - - $this->assertEquals(['abc' => 'xyz', 'foo' => 'bar'], $request->getParams()); - } - - public function testGetDeserializedBody() - { - $method = 'POST'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/x-www-form-urlencoded;charset=utf8'); - $_POST['foo'] = 'bar'; - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('foo=bar'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - $this->assertEquals(new \Gishiki\Algorithms\Collections\SerializableCollection(['foo' => 'bar']), $request->getDeserializedBody()); - } - - public function testGetDeserializedBodyJson() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/json;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $body->write('{"foo":"bar"}'); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals(new \Gishiki\Algorithms\Collections\SerializableCollection(['foo' => 'bar']), $request->getDeserializedBody()); - $this->assertEquals(new \Gishiki\Algorithms\Collections\SerializableCollection(['foo' => 'bar']), $request->getDeserializedBody()); - } - - public function testGetDeserializedBodyXml() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/xml;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $xml = <<<'XML' - - - bk101 - Gambardella, Matthew - XML Developer's Guide - 40.5 - 2000-10-01 - An in-depth look at creating applications with XML. - -XML; - $body->write($xml); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - //throw new \Exception(print_r($request->getDeserializedBody()->all(), true)); - - $this->assertEquals([ - 'id' => 'bk101', - 'author' => 'Gambardella, Matthew', - 'title' => "XML Developer's Guide", - 'price' => 40.5, - 'publish_date' => '2000-10-01', - 'description' => 'An in-depth look at creating applications with XML.', - ], $request->getDeserializedBody()->all()); - } - - public function testGetDeserializedBodyComplexXml() - { - $method = 'GET'; - $uri = new Uri('https', 'example.com', 443, '/foo/bar', 'abc=123', '', ''); - $headers = new Headers(); - $headers->set('Content-Type', 'application/xml;charset=utf8'); - $cookies = []; - $serverParams = []; - $body = new RequestBody(); - $xml = <<<'XML' - - - - Empire Burlesque - Bob Dylan - USA - Columbia - 10.90 - 1985 - - - Hide your heart - Bonnie Tyler - UK - CBS Records - 9.90 - 1988 - - -XML; - $body->write($xml); - $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); - - $this->assertEquals([ - 'CD' => [ - 0 => [ - 'TITLE' => 'Empire Burlesque', - 'ARTIST' => 'Bob Dylan', - 'COUNTRY' => 'USA', - 'COMPANY' => 'Columbia', - 'PRICE' => 10.90, - 'YEAR' => 1985, - ], - 1 => [ - 'TITLE' => 'Hide your heart', - 'ARTIST' => 'Bonnie Tyler', - 'COUNTRY' => 'UK', - 'COMPANY' => 'CBS Records', - 'PRICE' => 9.90, - 'YEAR' => 1988, - ], - ], - ], $request->getDeserializedBody()->all()); - } -} diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php deleted file mode 100644 index 5bfe8858..00000000 --- a/tests/Http/ResponseTest.php +++ /dev/null @@ -1,442 +0,0 @@ -assertAttributeEquals(200, 'status', $response); - $this->assertAttributeInstanceOf('\Gishiki\HttpKernel\Headers', 'headers', $response); - $this->assertAttributeInstanceOf('\Psr\Http\Message\StreamInterface', 'body', $response); - } - - public function testConstructorWithCustomArgs() - { - $headers = new Headers(); - $body = new Body(fopen('php://temp', 'r+')); - $response = new Response(404, $headers, $body); - - $this->assertAttributeEquals(404, 'status', $response); - $this->assertAttributeSame($headers, 'headers', $response); - $this->assertAttributeSame($body, 'body', $response); - } - - public function testDeepCopyClone() - { - $headers = new Headers(); - $body = new Body(fopen('php://temp', 'r+')); - $response = new Response(404, $headers, $body); - $clone = clone $response; - - $this->assertAttributeEquals('1.1', 'protocolVersion', $clone); - $this->assertAttributeEquals(404, 'status', $clone); - $this->assertAttributeNotSame($headers, 'headers', $clone); - $this->assertAttributeNotSame($body, 'body', $clone); - } - - public function testDisableSetter() - { - $response = new Response(); - $response->foo = 'bar'; - - $this->assertFalse(property_exists($response, 'foo')); - } - - /******************************************************************************* - * Status - ******************************************************************************/ - - public function testGetStatusCode() - { - $response = new Response(); - $responseStatus = new ReflectionProperty($response, 'status'); - $responseStatus->setAccessible(true); - $responseStatus->setValue($response, '404'); - - $this->assertEquals(404, $response->getStatusCode()); - } - - public function testWithStatus() - { - $response = new Response(); - $clone = $response->withStatus(302); - - $this->assertAttributeEquals(302, 'status', $clone); - } - - public function testWithStatusInvalidStatusCodeThrowsException() - { - $this->expectException(\InvalidArgumentException::class); - - $response = new Response(); - $response->withStatus(800); - } - - public function testWithStatusInvalidReasonPhraseThrowsException() - { - $this->expectExceptionMessage("ReasonPhrase must be a string"); - - $response = new Response(); - $response->withStatus(200, null); - } - - public function testWithStatusEmptyReasonPhrase() - { - $response = new Response(); - $clone = $response->withStatus(207); - $responsePhrase = new ReflectionProperty($response, 'reasonPhrase'); - $responsePhrase->setAccessible(true); - - $this->assertEquals('Multi-Status', $responsePhrase->getValue($clone)); - } - - public function testGetReasonPhrase() - { - $response = new Response(); - $responseStatus = new ReflectionProperty($response, 'status'); - $responseStatus->setAccessible(true); - $responseStatus->setValue($response, '404'); - - $this->assertEquals('Not Found', $response->getReasonPhrase()); - } - - public function testMustSetReasonPhraseForUnrecognisedCode() - { - $this->expectExceptionMessage("ReasonPhrase must be supplied for this code"); - - $response = new Response(); - $response = $response->withStatus(499); - } - - public function testSetReasonPhraseForUnrecognisedCode() - { - $response = new Response(); - $response = $response->withStatus(499, 'Authentication timeout'); - - $this->assertEquals('Authentication timeout', $response->getReasonPhrase()); - } - - public function testGetCustomReasonPhrase() - { - $response = new Response(); - $clone = $response->withStatus(200, 'Custom Phrase'); - - $this->assertEquals('Custom Phrase', $clone->getReasonPhrase()); - } - - /** - * @covers \Gishiki\HttpKernel\Response::withRedirect - */ - public function testWithRedirect() - { - $response = new Response(200); - $clone = $response->withRedirect('/foo', 301); - - $this->assertSame(200, $response->getStatusCode()); - $this->assertFalse($response->hasHeader('Location')); - - $this->assertSame(301, $clone->getStatusCode()); - $this->assertTrue($clone->hasHeader('Location')); - $this->assertEquals('/foo', $clone->getHeaderLine('Location')); - } - - /******************************************************************************* - * Behaviors - ******************************************************************************/ - - public function testIsEmpty() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 204); - - $this->assertTrue($response->isEmpty()); - } - - public function testIsInformational() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 100); - - $this->assertTrue($response->isInformational()); - } - - public function testIsOk() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 200); - - $this->assertTrue($response->isOk()); - } - - public function testIsSuccessful() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 201); - - $this->assertTrue($response->isSuccessful()); - } - - public function testIsRedirect() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 302); - - $this->assertTrue($response->isRedirect()); - } - - public function testIsRedirection() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 308); - - $this->assertTrue($response->isRedirection()); - } - - public function testIsForbidden() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 403); - - $this->assertTrue($response->isForbidden()); - } - - public function testIsNotFound() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 404); - - $this->assertTrue($response->isNotFound()); - } - - public function testIsClientError() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 400); - - $this->assertTrue($response->isClientError()); - } - - public function testIsServerError() - { - $response = new Response(); - $prop = new ReflectionProperty($response, 'status'); - $prop->setAccessible(true); - $prop->setValue($response, 503); - - $this->assertTrue($response->isServerError()); - } - - public function testToString() - { - $output = 'HTTP/1.1 404 Not Found'.PHP_EOL. - 'X-Foo: Bar'.PHP_EOL.PHP_EOL. - 'Where am I?'; - $this->expectOutputString($output); - $response = new Response(); - $response = $response->withStatus(404)->withHeader('X-Foo', 'Bar')->write('Where am I?'); - - echo $response; - } - - public function testWithJson() - { - $data = ['foo' => 'bar1&bar2']; - - $response = new Response(); - $response = $response->withJson($data, 201); - - $this->assertEquals(201, $response->getStatusCode()); - $this->assertEquals('application/json;charset=utf-8', $response->getHeaderLine('Content-Type')); - - $body = $response->getBody(); - $body->rewind(); - $dataJson = $body->getContents(); //json_decode($body->getContents(), true); - - $this->assertEquals('{"foo":"bar1&bar2"}', $dataJson); - $this->assertEquals($data['foo'], json_decode($dataJson, true)['foo']); - - // Test encoding option - $response = $response->withJson($data, 200, JSON_HEX_AMP); - - $body = $response->getBody(); - $body->rewind(); - $dataJson = $body->getContents(); - - $this->assertEquals('{"foo":"bar1\u0026bar2"}', $dataJson); - $this->assertEquals($data['foo'], json_decode($dataJson, true)['foo']); - } - - public function testSendResponse() - { - //setup a new response - $response = new Response(); - - //generate some binary-safe data - $message = base64_encode(openssl_random_pseudo_bytes(1024)); - - //write data to the stream - $response->write($message); - - //remove the content length - $response = $response->withoutHeader('Content-Length'); - - //test the output - $this->assertEquals(strlen($message), $response->send(24, true)); - } - - public function testSendFixedLengthResponse() - { - //setup a new response - $response = new Response(); - - //generate some binary-safe data - $message = base64_encode(openssl_random_pseudo_bytes(1024)); - - //write data to the stream - $response->write($message); - - //re-test data stream-write - $response = $response->withHeader('Content-Length', ''.strlen($message)); - - //test the output (fixed length) - $this->assertEquals(strlen($message), $response->send(31, true)); - } - - public function testSerializationResponse() - { - //setup a new response - $response = new Response(); - - //write data to the stream - $response = $response->withHeader('Content-Type', 'application/xml'); - $response->setSerializedBody(new SerializableCollection([ - 'CD' => [ - 0 => [ - 'TITLE' => 'Empire Burlesque', - 'ARTIST' => 'Bob Dylan', - 'COUNTRY' => 'USA', - 'COMPANY' => 'Columbia', - 'PRICE' => 10.90, - 'YEAR' => 1985, - ], - 1 => [ - 'TITLE' => 'Hide your heart', - 'ARTIST' => 'Bonnie Tyler', - 'COUNTRY' => 'UK', - 'COMPANY' => 'CBS Records', - 'PRICE' => 9.90, - 'YEAR' => 1988, - ], - ], - ])); - - //test the output deserialization result - $this->assertEquals([ - 'CD' => [ - 0 => [ - 'TITLE' => 'Empire Burlesque', - 'ARTIST' => 'Bob Dylan', - 'COUNTRY' => 'USA', - 'COMPANY' => 'Columbia', - 'PRICE' => 10.90, - 'YEAR' => 1985, - ], - 1 => [ - 'TITLE' => 'Hide your heart', - 'ARTIST' => 'Bonnie Tyler', - 'COUNTRY' => 'UK', - 'COMPANY' => 'CBS Records', - 'PRICE' => 9.90, - 'YEAR' => 1988, - ], - ], - ], SerializableCollection::deserialize((string) $response->getBody(), SerializableCollection::XML)->all()); - } - - public function requestFactory() - { - $env = \Gishiki\Core\Environment::mock(); - - $uri = \Gishiki\HttpKernel\Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = Headers::createFromEnvironment($env); - $cookies = [ - 'user' => 'john', - 'id' => '123', - ]; - $serverParams = $env->all(); - $body = new \Gishiki\HttpKernel\RequestBody(); - $uploadedFiles = \Gishiki\HttpKernel\UploadedFile::createFromEnvironment($env); - $request = new \Gishiki\HttpKernel\Request('GET', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); - - return $request; - } - - public function testCompleteYamlSerialization() - { - //generate a stupid request for testing purpouses - $request = $this->requestFactory(); - - //expecting a yaml output.... - $response = Response::deriveFromRequest($request->withAddedHeader('Accept', 'application/x-yaml')); - $testArray = [ - 'a' => [0, 1, 4, 6], - 'b' => 'this is a test', - 'c' => 1, - 'd' => 20.5, - 'e' => [ - 'f' => 'nestedtest', - 'g' => 9, - 'h' => true, - 'i' => null, - ], - ]; - $response->setSerializedBody(new SerializableCollection($testArray)); - - //check for the content type - $this->assertEquals('application/x-yaml', explode(';', $response->getHeader('Content-Type')[0])[0]); - - //check the serialization result - $this->assertEquals($testArray, \Symfony\Component\Yaml\Yaml::parse((string) $response->getBody())); - } -} diff --git a/tests/Http/UploadedFilesTest.php b/tests/Http/UploadedFilesTest.php deleted file mode 100644 index 031533ed..00000000 --- a/tests/Http/UploadedFilesTest.php +++ /dev/null @@ -1,200 +0,0 @@ -assertEquals($expected, $uploadedFile); - } - - /** - * @return UploadedFile - */ - public function testConstructor() - { - $attr = [ - 'tmp_name' => self::$filename, - 'name' => 'my-avatar.txt', - 'size' => 8, - 'type' => 'text/plain', - 'error' => 0, - ]; - - $uploadedFile = new UploadedFile($attr['tmp_name'], $attr['name'], $attr['type'], $attr['size'], $attr['error'], false); - - $this->assertEquals($attr['name'], $uploadedFile->getClientFilename()); - $this->assertEquals($attr['type'], $uploadedFile->getClientMediaType()); - $this->assertEquals($attr['size'], $uploadedFile->getSize()); - $this->assertEquals($attr['error'], $uploadedFile->getError()); - - return $uploadedFile; - } - - /** - * @depends testConstructor - * - * @param UploadedFile $uploadedFile - * - * @return UploadedFile - */ - public function testGetStream(UploadedFile $uploadedFile) - { - $stream = $uploadedFile->getStream(); - $this->assertEquals(true, $uploadedFile->getStream() instanceof Stream); - $stream->close(); - - return $uploadedFile; - } - - /** - * @depends testConstructor - * - * @param UploadedFile $uploadedFile - * - * @return UploadedFile - */ - public function testMoveTo(UploadedFile $uploadedFile) - { - $tempName = uniqid('file-'); - $path = sys_get_temp_dir().DIRECTORY_SEPARATOR.$tempName; - $uploadedFile->moveTo($path); - - $this->assertFileExists($path); - - unlink($path); - - return $uploadedFile; - } - - public function providerCreateFromEnvironment() - { - return [ - [ - [ - 'files' => [ - 'tmp_name' => [ - 0 => __DIR__.DIRECTORY_SEPARATOR.'file0.txt', - 1 => __DIR__.DIRECTORY_SEPARATOR.'file1.html', - ], - 'name' => [ - 0 => 'file0.txt', - 1 => 'file1.html', - ], - 'type' => [ - 0 => 'text/plain', - 1 => 'text/html', - ], - 'error' => [ - 0 => 0, - 1 => 0, - ], - ], - ], - [ - 'files' => [ - 0 => new UploadedFile( - __DIR__.DIRECTORY_SEPARATOR.'file0.txt', - 'file0.txt', - 'text/plain', - null, - UPLOAD_ERR_OK, - true - ), - 1 => new UploadedFile( - __DIR__.DIRECTORY_SEPARATOR.'file1.html', - 'file1.html', - 'text/html', - null, - UPLOAD_ERR_OK, - true - ), - ], - ], - ], - [ - [ - 'avatar' => [ - 'tmp_name' => 'phpUxcOty', - 'name' => 'my-avatar.png', - 'size' => 90996, - 'type' => 'image/png', - 'error' => 0, - ], - ], - [ - 'avatar' => new UploadedFile('phpUxcOty', 'my-avatar.png', 'image/png', 90996, UPLOAD_ERR_OK, true), - ], - ], - ]; - } - - /** - * @param array $mockEnv An array representing a mock environment - * - * @return Request - */ - public function requestFactory(array $mockEnv) - { - $env = Environment::mock(); - - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = Headers::createFromEnvironment($env); - $cookies = []; - $serverParams = $env->all(); - $body = new RequestBody(); - $uploadedFiles = UploadedFile::createFromEnvironment($env); - $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles); - - return $request; - } -} diff --git a/tests/Http/UriTest.php b/tests/Http/UriTest.php deleted file mode 100644 index c1dc12ca..00000000 --- a/tests/Http/UriTest.php +++ /dev/null @@ -1,611 +0,0 @@ -assertEquals('https', $this->uriFactory()->getScheme()); - } - - public function testWithScheme() - { - $uri = $this->uriFactory()->withScheme('http'); - - $this->assertAttributeEquals('http', 'scheme', $uri); - } - - public function testWithSchemeRemovesSuffix() - { - $uri = $this->uriFactory()->withScheme('http://'); - - $this->assertAttributeEquals('http', 'scheme', $uri); - } - - public function testWithSchemeEmpty() - { - $uri = $this->uriFactory()->withScheme(''); - - $this->assertAttributeEquals('', 'scheme', $uri); - } - - public function testWithSchemeInvalid() - { - $this->expectExceptionMessage('Uri scheme must be one of: "", "https", "http"'); - - $this->uriFactory()->withScheme('ftp'); - } - - public function testWithSchemeInvalidType() - { - $this->expectExceptionMessage('Uri scheme must be a string'); - - $this->uriFactory()->withScheme([]); - } - - /******************************************************************************** - * Authority - *******************************************************************************/ - - public function testGetAuthorityWithUsernameAndPassword() - { - $this->assertEquals('josh:sekrit@example.com', $this->uriFactory()->getAuthority()); - } - - public function testGetAuthorityWithUsername() - { - $scheme = 'https'; - $user = 'josh'; - $password = ''; - $host = 'example.com'; - $path = '/foo/bar'; - $port = 443; - $query = 'abc=123'; - $fragment = 'section3'; - $uri = new Uri($scheme, $host, $port, $path, $query, $fragment, $user, $password); - - $this->assertEquals('josh@example.com', $uri->getAuthority()); - } - - public function testGetAuthority() - { - $scheme = 'https'; - $user = ''; - $password = ''; - $host = 'example.com'; - $path = '/foo/bar'; - $port = 443; - $query = 'abc=123'; - $fragment = 'section3'; - $uri = new Uri($scheme, $host, $port, $path, $query, $fragment, $user, $password); - - $this->assertEquals('example.com', $uri->getAuthority()); - } - - public function testGetAuthorityWithNonStandardPort() - { - $scheme = 'https'; - $user = ''; - $password = ''; - $host = 'example.com'; - $path = '/foo/bar'; - $port = 400; - $query = 'abc=123'; - $fragment = 'section3'; - $uri = new Uri($scheme, $host, $port, $path, $query, $fragment, $user, $password); - - $this->assertEquals('example.com:400', $uri->getAuthority()); - } - - public function testGetUserInfoWithUsernameAndPassword() - { - $scheme = 'https'; - $user = 'josh'; - $password = 'sekrit'; - $host = 'example.com'; - $path = '/foo/bar'; - $port = 443; - $query = 'abc=123'; - $fragment = 'section3'; - $uri = new Uri($scheme, $host, $port, $path, $query, $fragment, $user, $password); - - $this->assertEquals('josh:sekrit', $uri->getUserInfo()); - } - - public function testGetUserInfoWithUsername() - { - $scheme = 'https'; - $user = 'josh'; - $password = ''; - $host = 'example.com'; - $path = '/foo/bar'; - $port = 443; - $query = 'abc=123'; - $fragment = 'section3'; - $uri = new Uri($scheme, $host, $port, $path, $query, $fragment, $user, $password); - - $this->assertEquals('josh', $uri->getUserInfo()); - } - - public function testGetUserInfoNone() - { - $scheme = 'https'; - $user = ''; - $password = ''; - $host = 'example.com'; - $path = '/foo/bar'; - $port = 443; - $query = 'abc=123'; - $fragment = 'section3'; - $uri = new Uri($scheme, $host, $port, $path, $query, $fragment, $user, $password); - - $this->assertEquals('', $uri->getUserInfo()); - } - - public function testWithUserInfo() - { - $uri = $this->uriFactory()->withUserInfo('bob', 'pass'); - - $this->assertAttributeEquals('bob', 'user', $uri); - $this->assertAttributeEquals('pass', 'password', $uri); - } - - public function testWithUserInfoRemovesPassword() - { - $uri = $this->uriFactory()->withUserInfo('bob'); - - $this->assertAttributeEquals('bob', 'user', $uri); - $this->assertAttributeEquals('', 'password', $uri); - } - - public function testGetHost() - { - $this->assertEquals('example.com', $this->uriFactory()->getHost()); - } - - public function testWithHost() - { - $uri = $this->uriFactory()->withHost('gishiki.herokuapp.com'); - - $this->assertAttributeEquals('gishiki.herokuapp.com', 'host', $uri); - } - - public function testGetPortWithSchemeAndNonDefaultPort() - { - $uri = new Uri('https', 'www.example.com', 4000); - - $this->assertEquals(4000, $uri->getPort()); - } - - public function testGetPortWithSchemeAndDefaultPort() - { - $uriHppt = new Uri('http', 'www.example.com', 80); - $uriHppts = new Uri('https', 'www.example.com', 443); - - $this->assertNull($uriHppt->getPort()); - $this->assertNull($uriHppts->getPort()); - } - - public function testGetPortWithoutSchemeAndPort() - { - $uri = new Uri('', 'www.example.com'); - - $this->assertNull($uri->getPort()); - } - - public function testGetPortWithSchemeWithoutPort() - { - $uri = new Uri('http', 'www.example.com'); - - $this->assertNull($uri->getPort()); - } - - public function testWithPort() - { - $uri = $this->uriFactory()->withPort(8000); - - $this->assertAttributeEquals(8000, 'port', $uri); - } - - public function testWithPortNull() - { - $uri = $this->uriFactory()->withPort(null); - - $this->assertAttributeEquals(null, 'port', $uri); - } - - public function testWithPortInvalidInt() - { - $this->expectException(\InvalidArgumentException::class); - $this->uriFactory()->withPort(70000); - } - - public function testWithPortInvalidString() - { - $this->expectException(\InvalidArgumentException::class); - $this->uriFactory()->withPort('Foo'); - } - - /******************************************************************************** - * Path - *******************************************************************************/ - - public function testGetBasePathNone() - { - $this->assertEquals('', $this->uriFactory()->getBasePath()); - } - - public function testWithBasePath() - { - $uri = $this->uriFactory()->withBasePath('/base'); - - $this->assertAttributeEquals('/base', 'basePath', $uri); - } - - /** - * @covers \Gishiki\HttpKernel\Uri::withBasePath - */ - public function testWithBasePathInvalidType() - { - $this->expectExceptionMessage('Uri path must be a string'); - - $this->uriFactory()->withBasePath(['foo']); - } - - public function testWithBasePathAddsPrefix() - { - $uri = $this->uriFactory()->withBasePath('base'); - - $this->assertAttributeEquals('/base', 'basePath', $uri); - } - - public function testWithBasePathIgnoresSlash() - { - $uri = $this->uriFactory()->withBasePath('/'); - - $this->assertAttributeEquals('', 'basePath', $uri); - } - - public function testGetPath() - { - $this->assertEquals('/foo/bar', $this->uriFactory()->getPath()); - } - - public function testWithPath() - { - $uri = $this->uriFactory()->withPath('/new'); - - $this->assertAttributeEquals('/new', 'path', $uri); - } - - public function testWithPathWithoutPrefix() - { - $uri = $this->uriFactory()->withPath('new'); - - $this->assertAttributeEquals('new', 'path', $uri); - } - - public function testWithPathEmptyValue() - { - $uri = $this->uriFactory()->withPath(''); - - $this->assertAttributeEquals('', 'path', $uri); - } - - public function testWithPathUrlEncodesInput() - { - $uri = $this->uriFactory()->withPath('/includes?/new'); - - $this->assertAttributeEquals('/includes%3F/new', 'path', $uri); - } - - public function testWithPathDoesNotDoubleEncodeInput() - { - $uri = $this->uriFactory()->withPath('/include%25s/new'); - - $this->assertAttributeEquals('/include%25s/new', 'path', $uri); - } - - /** - * @covers \Gishiki\HttpKernel\Uri::withPath - */ - public function testWithPathInvalidType() - { - $this->expectExceptionMessage('Uri path must be a string'); - - $this->uriFactory()->withPath(['foo']); - } - - /******************************************************************************** - * Query - *******************************************************************************/ - - public function testGetQuery() - { - $this->assertEquals('abc=123', $this->uriFactory()->getQuery()); - } - - public function testWithQuery() - { - $uri = $this->uriFactory()->withQuery('xyz=123'); - - $this->assertAttributeEquals('xyz=123', 'query', $uri); - } - - public function testWithQueryRemovesPrefix() - { - $uri = $this->uriFactory()->withQuery('?xyz=123'); - - $this->assertAttributeEquals('xyz=123', 'query', $uri); - } - - public function testWithQueryEmpty() - { - $uri = $this->uriFactory()->withQuery(''); - - $this->assertAttributeEquals('', 'query', $uri); - } - - /** - * @covers \Gishiki\HttpKernel\Uri::withQuery - */ - public function testWithQueryInvalidType() - { - $this->expectExceptionMessage('Uri query must be a string'); - - $this->uriFactory()->withQuery(['foo']); - } - - /******************************************************************************** - * Fragment - *******************************************************************************/ - - public function testGetFragment() - { - $this->assertEquals('section3', $this->uriFactory()->getFragment()); - } - - public function testWithFragment() - { - $uri = $this->uriFactory()->withFragment('other-fragment'); - - $this->assertAttributeEquals('other-fragment', 'fragment', $uri); - } - - public function testWithFragmentRemovesPrefix() - { - $uri = $this->uriFactory()->withFragment('#other-fragment'); - - $this->assertAttributeEquals('other-fragment', 'fragment', $uri); - } - - public function testWithFragmentEmpty() - { - $uri = $this->uriFactory()->withFragment(''); - - $this->assertAttributeEquals('', 'fragment', $uri); - } - - /** - * @covers \Gishiki\HttpKernel\Uri::withFragment - */ - public function testWithFragmentInvalidType() - { - $this->expectExceptionMessage('Uri fragment must be a string'); - - $this->uriFactory()->withFragment(['foo']); - } - - /******************************************************************************** - * Helpers - *******************************************************************************/ - - public function testToString() - { - $uri = $this->uriFactory(); - - $this->assertEquals('https://josh:sekrit@example.com/foo/bar?abc=123#section3', (string) $uri); - - $uri = $uri->withPath('bar'); - $this->assertEquals('https://josh:sekrit@example.com/bar?abc=123#section3', (string) $uri); - - $uri = $uri->withBasePath('foo/'); - $this->assertEquals('https://josh:sekrit@example.com/foo/bar?abc=123#section3', (string) $uri); - - $uri = $uri->withPath('/bar'); - $this->assertEquals('https://josh:sekrit@example.com/bar?abc=123#section3', (string) $uri); - - // ensure that a Uri with just a base path correctly converts to a string - // (This occurs via createFromEnvironment when index.php is in a subdirectory) - $environment = Environment::mock([ - 'SCRIPT_NAME' => '/foo/index.php', - 'REQUEST_URI' => '/foo/', - 'HTTP_HOST' => 'example.com', - ]); - $uri = Uri::createFromEnvironment($environment); - $this->assertEquals('http://example.com/foo/', (string) $uri); - } - - /** - * @covers \Gishiki\HttpKernel\Uri::createFromString - */ - public function testCreateFromString() - { - $uri = Uri::createFromString('https://example.com:8080/foo/bar?abc=123'); - - $this->assertEquals('https', $uri->getScheme()); - $this->assertEquals('example.com', $uri->getHost()); - $this->assertEquals('8080', $uri->getPort()); - $this->assertEquals('/foo/bar', $uri->getPath()); - $this->assertEquals('abc=123', $uri->getQuery()); - } - - /** - * @covers \Gishiki\HttpKernel\Uri::createFromString - */ - public function testCreateFromStringWithInvalidType() - { - $this->expectExceptionMessage('Uri must be a string'); - - Uri::createFromString(['https://example.com:8080/foo/bar?abc=123']); - } - - public function testCreateEnvironment() - { - $environment = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo/bar', - 'PHP_AUTH_USER' => 'josh', - 'PHP_AUTH_PW' => 'sekrit', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => 'example.com:8080', - 'SERVER_PORT' => 8080, - ]); - - $uri = Uri::createFromEnvironment($environment); - - $this->assertEquals('josh:sekrit', $uri->getUserInfo()); - $this->assertEquals('example.com', $uri->getHost()); - $this->assertEquals('8080', $uri->getPort()); - $this->assertEquals('/foo/bar', $uri->getPath()); - $this->assertEquals('abc=123', $uri->getQuery()); - $this->assertEquals('', $uri->getFragment()); - } - - public function testCreateEnvironmentWithIPv6Host() - { - $environment = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo/bar', - 'PHP_AUTH_USER' => 'josh', - 'PHP_AUTH_PW' => 'sekrit', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => '[2001:db8::1]:8080', - 'REMOTE_ADDR' => '2001:db8::1', - 'SERVER_PORT' => 8080, - ]); - - $uri = Uri::createFromEnvironment($environment); - - $this->assertEquals('josh:sekrit', $uri->getUserInfo()); - $this->assertEquals('[2001:db8::1]', $uri->getHost()); - $this->assertEquals('8080', $uri->getPort()); - $this->assertEquals('/foo/bar', $uri->getPath()); - $this->assertEquals('abc=123', $uri->getQuery()); - $this->assertEquals('', $uri->getFragment()); - } - - /** - * @covers \Gishiki\HttpKernel\Uri::createFromEnvironment - * @ticket 1375 - */ - public function testCreateEnvironmentWithBasePath() - { - $environment = Environment::mock([ - 'SCRIPT_NAME' => '/foo/index.php', - 'REQUEST_URI' => '/foo/bar', - ]); - $uri = Uri::createFromEnvironment($environment); - - $this->assertEquals('/foo', $uri->getBasePath()); - $this->assertEquals('bar', $uri->getPath()); - - $this->assertEquals('http://localhost/foo/bar', (string) $uri); - } - - public function testGetBaseUrl() - { - $environment = Environment::mock([ - 'SCRIPT_NAME' => '/foo/index.php', - 'REQUEST_URI' => '/foo/bar', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => 'example.com:80', - 'SERVER_PORT' => 80, - ]); - $uri = Uri::createFromEnvironment($environment); - - $this->assertEquals('http://example.com/foo', $uri->getBaseUrl()); - } - - public function testGetBaseUrlWithNoBasePath() - { - $environment = Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/foo/bar', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => 'example.com:80', - 'SERVER_PORT' => 80, - ]); - $uri = Uri::createFromEnvironment($environment); - - $this->assertEquals('http://example.com', $uri->getBaseUrl()); - } - - public function testGetBaseUrlWithAuthority() - { - $environment = Environment::mock([ - 'SCRIPT_NAME' => '/foo/index.php', - 'REQUEST_URI' => '/foo/bar', - 'PHP_AUTH_USER' => 'josh', - 'PHP_AUTH_PW' => 'sekrit', - 'QUERY_STRING' => 'abc=123', - 'HTTP_HOST' => 'example.com:8080', - 'SERVER_PORT' => 8080, - ]); - $uri = Uri::createFromEnvironment($environment); - - $this->assertEquals('http://josh:sekrit@example.com:8080/foo', $uri->getBaseUrl()); - } - - /** - * @covers \Gishiki\HttpKernel\Uri::createFromEnvironment - * @ticket 1380 - */ - public function testWithPathWhenBaseRootIsEmpty() - { - $environment = \Gishiki\Core\Environment::mock([ - 'SCRIPT_NAME' => '/index.php', - 'REQUEST_URI' => '/bar', - ]); - $uri = \Gishiki\HttpKernel\Uri::createFromEnvironment($environment); - - $this->assertEquals('http://localhost/test', (string) $uri->withPath('test')); - } -} diff --git a/tests/Logging/LoggerManagerTest.php b/tests/Logging/LoggerManagerTest.php index 7d3a8aad..052b3897 100644 --- a/tests/Logging/LoggerManagerTest.php +++ b/tests/Logging/LoggerManagerTest.php @@ -1,139 +1,139 @@ - - */ -class LoggerManagerTest extends TestCase -{ - public function testConnectBadName() - { - $this->expectException(\InvalidArgumentException::class); - - LoggerManager::connect(6, []); - } - - public function testConnectBadValue() - { - $this->expectException(\InvalidArgumentException::class); - - LoggerManager::connect(__FUNCTION__, [ - [ - 'connection' => ['testLog.log', 0] - ] - ]); - } - - public function testConnectBadClassValue() - { - $this->expectException(\InvalidArgumentException::class); - - LoggerManager::connect(__FUNCTION__, [ - [ - 'class' => 'lol', - 'connection' => ['testLog.log', 0] - ] - ]); - } - - public function testSetDefaultBadConnectionName() - { - $this->expectException(\InvalidArgumentException::class); - - LoggerManager::setDefault(null); - } - - public function testSetDefaultInexistentConnectionName() - { - $this->expectException(\InvalidArgumentException::class); - - LoggerManager::setDefault("what a lol"); - } - - public function testRetrieveBadConnectionName() - { - $this->expectException(\InvalidArgumentException::class); - - LoggerManager::retrieve(100.05); - } - - public function testRetrieveInexistentConnectionName() - { - $this->expectException(\InvalidArgumentException::class); - - LoggerManager::retrieve("what a lol"); - } - - public function testRetrieveUnsetDefaultConnection() - { - $this->expectException(\InvalidArgumentException::class); - - //set to null the default connection - $reflectionClass = new \ReflectionClass(LoggerManager::class); - $reflectionProperty = $reflectionClass->getProperty('hashOfDefault'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue(null); - - LoggerManager::retrieve(null); - } - - public function testSetDefaultLogger() - { - //empty the error testing file - file_put_contents('testLog.log', ''); - - LoggerManager::connect(__FUNCTION__, [ - [ - 'class' => 'StreamHandler', - 'connection' => ['testLog.log', \Monolog\Logger::ERROR ] - ] - ]); - - LoggerManager::setDefault(__FUNCTION__); - - $logger = LoggerManager::retrieve(null); - $logger->error("testing error"); - - $this->assertGreaterThanOrEqual(strlen('testing error'), strlen(file_get_contents('testLog.log'))); - } - - public function testRetrieveLogger() - { - //empty the error testing file - file_put_contents('testLog.log', ''); - - LoggerManager::connect(__FUNCTION__, [ - [ - 'class' => StreamHandler::class, - 'connection' => ['testLog.log', \Monolog\Logger::NOTICE ] - ] - ]); - - $logger = LoggerManager::retrieve(__FUNCTION__); - $logger->notice("testing notice"); - - $this->assertGreaterThanOrEqual(strlen('testing notice'), strlen(file_get_contents('testLog.log'))); - } -} + + */ +class LoggerManagerTest extends TestCase +{ + public function testConnectBadName() + { + $this->expectException(\InvalidArgumentException::class); + + LoggerManager::connect(6, []); + } + + public function testConnectBadValue() + { + $this->expectException(\InvalidArgumentException::class); + + LoggerManager::connect(__FUNCTION__, [ + [ + 'connection' => ['testLog.log', 0] + ] + ]); + } + + public function testConnectBadClassValue() + { + $this->expectException(\InvalidArgumentException::class); + + LoggerManager::connect(__FUNCTION__, [ + [ + 'class' => 'lol', + 'connection' => ['testLog.log', 0] + ] + ]); + } + + public function testSetDefaultBadConnectionName() + { + $this->expectException(\InvalidArgumentException::class); + + LoggerManager::setDefault(null); + } + + public function testSetDefaultInexistentConnectionName() + { + $this->expectException(\InvalidArgumentException::class); + + LoggerManager::setDefault("what a lol"); + } + + public function testRetrieveBadConnectionName() + { + $this->expectException(\InvalidArgumentException::class); + + LoggerManager::retrieve(100.05); + } + + public function testRetrieveInexistentConnectionName() + { + $this->expectException(\InvalidArgumentException::class); + + LoggerManager::retrieve("what a lol"); + } + + public function testRetrieveUnsetDefaultConnection() + { + $this->expectException(\InvalidArgumentException::class); + + //set to null the default connection + $reflectionClass = new \ReflectionClass(LoggerManager::class); + $reflectionProperty = $reflectionClass->getProperty('hashOfDefault'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue(null); + + LoggerManager::retrieve(null); + } + + public function testSetDefaultLogger() + { + //empty the error testing file + file_put_contents('testLog.log', ''); + + LoggerManager::connect(__FUNCTION__, [ + [ + 'class' => 'StreamHandler', + 'connection' => ['testLog.log', \Monolog\Logger::ERROR ] + ] + ]); + + LoggerManager::setDefault(__FUNCTION__); + + $logger = LoggerManager::retrieve(null); + $logger->error("testing error"); + + $this->assertGreaterThanOrEqual(strlen('testing error'), strlen(file_get_contents('testLog.log'))); + } + + public function testRetrieveLogger() + { + //empty the error testing file + file_put_contents('testLog.log', ''); + + LoggerManager::connect(__FUNCTION__, [ + [ + 'class' => StreamHandler::class, + 'connection' => ['testLog.log', \Monolog\Logger::NOTICE ] + ] + ]); + + $logger = LoggerManager::retrieve(__FUNCTION__); + $logger->notice("testing notice"); + + $this->assertGreaterThanOrEqual(strlen('testing notice'), strlen(file_get_contents('testLog.log'))); + } +} diff --git a/tests/Security/Encryption/Asymmetric/CryptographyTest.php b/tests/Security/Encryption/Asymmetric/CryptographyTest.php index 703002ef..8a6d32df 100644 --- a/tests/Security/Encryption/Asymmetric/CryptographyTest.php +++ b/tests/Security/Encryption/Asymmetric/CryptographyTest.php @@ -1,243 +1,243 @@ - - */ -class CryptographyTest extends TestCase -{ - public function testInvalidMessageEncryption() - { - $this->expectException(\InvalidArgumentException::class); - - $privateKey = new PrivateKey(PrivateKey::generate()); - - //check if the private key has been loaded correctly - $this->assertEquals(true, $privateKey->isLoaded()); - - //attempt to perform the bad encryption - Cryptography::encrypt($privateKey, 73); - } - - public function testInvalidMessageReverseEncryption() - { - $this->expectException(\InvalidArgumentException::class); - - //generate a new private key and the associated public key - $privKey = new PrivateKey(PrivateKey::generate()); - $pubKey = new PublicKey($privKey->exportPublicKey()); - - //attempt to reverse encrypt a bad message - Cryptography::encryptReverse($pubKey, ''); - } - - public function testInvalidMessageDecryption() - { - $this->expectException(\InvalidArgumentException::class); - - //this is the test example message - $message = 'mL84hPpR+nmb2UuWDnhiXnpMDxzQT0NMPXT.dY.*?ImTrO86Dt'; - - //generate two keys - $privateKey = new PrivateKey(PrivateKey::generate()); - $publicKey = new PublicKey($privateKey->exportPublicKey()); - - //check if the private key has been loaded correctly - $this->assertEquals(true, $privateKey->isLoaded()); - - //attempt to perform the bad decryption - $decryption_result = Cryptography::decrypt($publicKey, ''); - } - - public function testInvalidMessageReverseDecryption() - { - $this->expectException(\InvalidArgumentException::class); - - //generate a new private key and the associated public key - $privKey = new PrivateKey(PrivateKey::generate()); - $pubKey = new PublicKey($privKey->exportPublicKey()); - - //attempt to reverse decrypt a bad message - Cryptography::decryptReverse($privKey, ''); - } - - public function testInvalidMessageGenerateDigitalSignature() - { - $this->expectException(\InvalidArgumentException::class); - - //generate a new private key and the associated public key - $privKey = new PrivateKey(PrivateKey::generate()); - $pubKey = new PublicKey($privKey->exportPublicKey()); - - $message = 'who knows if this message will be modified.....'; - - //generate the signature - Cryptography::generateDigitalSignature($privKey, ''); - } - - public function testInvalidMessageVerifyDigitalSignature() - { - $this->expectException(\InvalidArgumentException::class); - - //generate a new private key and the associated public key - $privKey = new PrivateKey(PrivateKey::generate()); - $pubKey = new PublicKey($privKey->exportPublicKey()); - - $message = 'verify me if U can.....'; - - //generate the signature - $signature = Cryptography::generateDigitalSignature($privKey, $message); - Cryptography::verifyDigitalSignature($pubKey, '', $signature); - } - - public function testInvalidSignatureVerifyDigitalSignature() - { - $this->expectException(\InvalidArgumentException::class); - - //generate a new private key and the associated public key - $privKey = new PrivateKey(PrivateKey::generate()); - $pubKey = new PublicKey($privKey->exportPublicKey()); - - $message = 'verify me if U can.....'; - - //generate the signature - $signature = Cryptography::generateDigitalSignature($privKey, $message); - Cryptography::verifyDigitalSignature($pubKey, $message, ''); - } - - public function testEncryption() - { - $privateKey = new PrivateKey(PrivateKey::generate()); - - //check if the private key has been loaded correctly - $this->assertEquals(true, $privateKey->isLoaded()); - - //perform the encryption - $encrytpion_result = Cryptography::encrypt($privateKey, 'ciao bello!'); - - //check if the encryption result has the correct type - $this->assertEquals(true, is_string($encrytpion_result)); - } - - public function testMessage() - { - //this is the test example message - $message = 'mL84hPpR+nmb2UuWDnhiXnpMDxzQT0NMPXT.dY.*?ImTrO86Dt'; - - //generate two keys - $privateKey = new PrivateKey(PrivateKey::generate()); - $publicKey = new PublicKey($privateKey->exportPublicKey()); - - //check if the private key has been loaded correctly - $this->assertEquals(true, $privateKey->isLoaded()); - - //perform the encryption and decryption - $encrytpion_result = Cryptography::encrypt($privateKey, $message); - $decryption_result = Cryptography::decrypt($publicKey, $encrytpion_result); - - //test the return value - $this->assertEquals($message, $decryption_result); - } - - public function testLongMessage() - { - //generate two keys - $privateKey = new PrivateKey(KeyTest::getTestRSAPrivateKey()); - $publicKey = new PublicKey($privateKey->exportPublicKey()); - - //generate a very long example message - $message = openssl_random_pseudo_bytes(25 * $privateKey()['byteLength']); - - //check if the private key has been loaded correctly - $this->assertEquals(true, $privateKey->isLoaded()); - - //perform the encryption and decryption - $encrytpion_result = Cryptography::encrypt($privateKey, $message); - $decryption_result = Cryptography::decrypt($publicKey, $encrytpion_result); - - //test the return value - $this->assertEquals($message, $decryption_result); - } - - public function testBadDecryption() - { - $this->expectException(AsymmetricException::class); - - //generate two keys - $privateKey = new PrivateKey(KeyTest::getTestRSAPrivateKey()); - $publicKey = new PublicKey($privateKey->exportPublicKey()); - - //generate a very long example message - $message = openssl_random_pseudo_bytes(5 * $privateKey()['byteLength']); - - //check if the private key has been loaded correctly - $this->assertEquals(true, $privateKey->isLoaded()); - - //perform the encryption and decryption - $encrytpion_result = Cryptography::encrypt($privateKey, $message); - $malformed_encrytpion_result = str_shuffle(substr($encrytpion_result, 1)); - - //an exception should be thrown.... - //$this->expectException('Gishiki\Security\Encryption\Asymmetric\AsymmetricException'); - - //come on, decrypt a malformed message, if you can! - $decryption_result = Cryptography::decrypt($publicKey, $malformed_encrytpion_result); - - //test the return value (should be null) - $this->assertEquals(null, $decryption_result); - } - - public function testDigitalSignature() - { - //generate a new private key and the associated public key - $privKey = new PrivateKey(PrivateKey::generate()); - $pubKey = new PublicKey($privKey->exportPublicKey()); - - $message = 'who knows if this message will be modified.....'; - - //generate the signature - $signature = Cryptography::generateDigitalSignature($privKey, $message); - - //check the result - $this->assertEquals(true, Cryptography::verifyDigitalSignature($pubKey, $message, $signature)); - } - - public function testReverse() - { - //generate a new private key and the associated public key - $privKey = new PrivateKey(PrivateKey::generate()); - $pubKey = new PublicKey($privKey->exportPublicKey()); - - //generate a very long example message - $message = openssl_random_pseudo_bytes(5 * $privKey()['byteLength']); - - //encrypt and decrypt - $enc_message = Cryptography::encryptReverse($pubKey, $message); - $this->assertEquals($message, Cryptography::decryptReverse($privKey, $enc_message)); - } -} + + */ +class CryptographyTest extends TestCase +{ + public function testInvalidMessageEncryption() + { + $this->expectException(\InvalidArgumentException::class); + + $privateKey = new PrivateKey(PrivateKey::generate()); + + //check if the private key has been loaded correctly + $this->assertEquals(true, $privateKey->isLoaded()); + + //attempt to perform the bad encryption + Cryptography::encrypt($privateKey, 73); + } + + public function testInvalidMessageReverseEncryption() + { + $this->expectException(\InvalidArgumentException::class); + + //generate a new private key and the associated public key + $privKey = new PrivateKey(PrivateKey::generate()); + $pubKey = new PublicKey($privKey->exportPublicKey()); + + //attempt to reverse encrypt a bad message + Cryptography::encryptReverse($pubKey, ''); + } + + public function testInvalidMessageDecryption() + { + $this->expectException(\InvalidArgumentException::class); + + //this is the test example message + $message = 'mL84hPpR+nmb2UuWDnhiXnpMDxzQT0NMPXT.dY.*?ImTrO86Dt'; + + //generate two keys + $privateKey = new PrivateKey(PrivateKey::generate()); + $publicKey = new PublicKey($privateKey->exportPublicKey()); + + //check if the private key has been loaded correctly + $this->assertEquals(true, $privateKey->isLoaded()); + + //attempt to perform the bad decryption + $decryption_result = Cryptography::decrypt($publicKey, ''); + } + + public function testInvalidMessageReverseDecryption() + { + $this->expectException(\InvalidArgumentException::class); + + //generate a new private key and the associated public key + $privKey = new PrivateKey(PrivateKey::generate()); + $pubKey = new PublicKey($privKey->exportPublicKey()); + + //attempt to reverse decrypt a bad message + Cryptography::decryptReverse($privKey, ''); + } + + public function testInvalidMessageGenerateDigitalSignature() + { + $this->expectException(\InvalidArgumentException::class); + + //generate a new private key and the associated public key + $privKey = new PrivateKey(PrivateKey::generate()); + $pubKey = new PublicKey($privKey->exportPublicKey()); + + $message = 'who knows if this message will be modified.....'; + + //generate the signature + Cryptography::generateDigitalSignature($privKey, ''); + } + + public function testInvalidMessageVerifyDigitalSignature() + { + $this->expectException(\InvalidArgumentException::class); + + //generate a new private key and the associated public key + $privKey = new PrivateKey(PrivateKey::generate()); + $pubKey = new PublicKey($privKey->exportPublicKey()); + + $message = 'verify me if U can.....'; + + //generate the signature + $signature = Cryptography::generateDigitalSignature($privKey, $message); + Cryptography::verifyDigitalSignature($pubKey, '', $signature); + } + + public function testInvalidSignatureVerifyDigitalSignature() + { + $this->expectException(\InvalidArgumentException::class); + + //generate a new private key and the associated public key + $privKey = new PrivateKey(PrivateKey::generate()); + $pubKey = new PublicKey($privKey->exportPublicKey()); + + $message = 'verify me if U can.....'; + + //generate the signature + $signature = Cryptography::generateDigitalSignature($privKey, $message); + Cryptography::verifyDigitalSignature($pubKey, $message, ''); + } + + public function testEncryption() + { + $privateKey = new PrivateKey(PrivateKey::generate()); + + //check if the private key has been loaded correctly + $this->assertEquals(true, $privateKey->isLoaded()); + + //perform the encryption + $encrytpion_result = Cryptography::encrypt($privateKey, 'ciao bello!'); + + //check if the encryption result has the correct type + $this->assertEquals(true, is_string($encrytpion_result)); + } + + public function testMessage() + { + //this is the test example message + $message = 'mL84hPpR+nmb2UuWDnhiXnpMDxzQT0NMPXT.dY.*?ImTrO86Dt'; + + //generate two keys + $privateKey = new PrivateKey(PrivateKey::generate()); + $publicKey = new PublicKey($privateKey->exportPublicKey()); + + //check if the private key has been loaded correctly + $this->assertEquals(true, $privateKey->isLoaded()); + + //perform the encryption and decryption + $encrytpion_result = Cryptography::encrypt($privateKey, $message); + $decryption_result = Cryptography::decrypt($publicKey, $encrytpion_result); + + //test the return value + $this->assertEquals($message, $decryption_result); + } + + public function testLongMessage() + { + //generate two keys + $privateKey = new PrivateKey(KeyTest::getTestRSAPrivateKey()); + $publicKey = new PublicKey($privateKey->exportPublicKey()); + + //generate a very long example message + $message = openssl_random_pseudo_bytes(25 * $privateKey()['byteLength']); + + //check if the private key has been loaded correctly + $this->assertEquals(true, $privateKey->isLoaded()); + + //perform the encryption and decryption + $encrytpion_result = Cryptography::encrypt($privateKey, $message); + $decryption_result = Cryptography::decrypt($publicKey, $encrytpion_result); + + //test the return value + $this->assertEquals($message, $decryption_result); + } + + public function testBadDecryption() + { + $this->expectException(AsymmetricException::class); + + //generate two keys + $privateKey = new PrivateKey(KeyTest::getTestRSAPrivateKey()); + $publicKey = new PublicKey($privateKey->exportPublicKey()); + + //generate a very long example message + $message = openssl_random_pseudo_bytes(5 * $privateKey()['byteLength']); + + //check if the private key has been loaded correctly + $this->assertEquals(true, $privateKey->isLoaded()); + + //perform the encryption and decryption + $encrytpion_result = Cryptography::encrypt($privateKey, $message); + $malformed_encrytpion_result = str_shuffle(substr($encrytpion_result, 1)); + + //an exception should be thrown.... + //$this->expectException('Gishiki\Security\Encryption\Asymmetric\AsymmetricException'); + + //come on, decrypt a malformed message, if you can! + $decryption_result = Cryptography::decrypt($publicKey, $malformed_encrytpion_result); + + //test the return value (should be null) + $this->assertEquals(null, $decryption_result); + } + + public function testDigitalSignature() + { + //generate a new private key and the associated public key + $privKey = new PrivateKey(PrivateKey::generate()); + $pubKey = new PublicKey($privKey->exportPublicKey()); + + $message = 'who knows if this message will be modified.....'; + + //generate the signature + $signature = Cryptography::generateDigitalSignature($privKey, $message); + + //check the result + $this->assertEquals(true, Cryptography::verifyDigitalSignature($pubKey, $message, $signature)); + } + + public function testReverse() + { + //generate a new private key and the associated public key + $privKey = new PrivateKey(PrivateKey::generate()); + $pubKey = new PublicKey($privKey->exportPublicKey()); + + //generate a very long example message + $message = openssl_random_pseudo_bytes(5 * $privKey()['byteLength']); + + //encrypt and decrypt + $enc_message = Cryptography::encryptReverse($pubKey, $message); + $this->assertEquals($message, Cryptography::decryptReverse($privKey, $enc_message)); + } +} diff --git a/tests/Security/Encryption/Asymmetric/KeyTest.php b/tests/Security/Encryption/Asymmetric/KeyTest.php index 6933420b..6a55d23a 100644 --- a/tests/Security/Encryption/Asymmetric/KeyTest.php +++ b/tests/Security/Encryption/Asymmetric/KeyTest.php @@ -1,146 +1,146 @@ - - */ -class KeyTest extends TestCase -{ - public static function getTestRSAPublicKey() - { - return '-----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUHsaL7kLg0CWpUlnQkT -YW3HFSNRV8fAKKWWeZswc4JXbPTqeiorGZT3mPlKl/e+FrwKPK7ZUYHlLxdEDOB5 -0Sqe/qOalA/zr6OGWkxC+rq5BibPz29dcTFr9GSsF01PorR57sKqqeZPfTboAKA1 -jTNL8dwFqFuT/ZI+NC6rki2lTt3JZb/LUOqvqY0vW8wlyXBxTScJpPEhlXcRuN5Y -QLJCYhz9f8AlMhzUYj3AN6hhYFCwa5+PgAGzjBpWVzDKGNFWCrxVejGkONrypbSr -nF6yHj6jVpVSfZvQFK9cmujGeU6fackVLcARUiUDY3rS0lFNC41VADHP5hpgPLTp -YwONOkuWoPFvyVIJDMCB0IIkbRSojrtuNlkGx7ZVksXA7pRP48LhFRjyHEHGnZ87 -7pMoDd8e33/1Lut2NUMpCOR/8owgek985rv049tsa9Uw6xL92anOt6CcUS4SxOdY -lh3mnDuAB4egzLS2UUVmmaOgGVkfnzoPLxg+m1tF7nRqSNtCQOBAQVcDP3189//w -DPj/pt5btnKsH9bC720+AmuM7XKuX3uiIjDo8UTENdQpoEUG0A/gKXmdCagOeLZ+ -tdaf03LPdaT7yoYCcmdLezqtMRywLkyxqDNhLfqJvQD+tct02fASZKjxLP2+F48L -LDdopnAc51efTUT3s+DN+J0CAwEAAQ== ------END PUBLIC KEY-----'; - } - - public static function getTestRSAPrivateKey() - { - return '-----BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEAma9gYrocyBwqcKrhjsg29ySYSoKC+ovzGSg83P7aelrKrRKP -6bjlgi/Su1iNAXDJZXg2Dpcrf1uxOAGlbnuVkfEbQLeW+5gZn4jazKYdaSEMbYdf -9pWlCDU5Ao81oyWFukmkNYaD+bjmAuoRR5EG3rLoXDVfFtpgWtwyWpJpBvPABFcC -8xspdsmcZp9mmWvUi9HEZHrUOu7AgW9kot7DZ4AonMhtbGNj2t9CafceG00g6Y30 -fYIiMTZDrc5gNOKFC20ikwFIC9NDh6gCrD0mAQOvSipR0N9nfIk2aZbNPvhFvzQV -tMP6DWS3Oo5LzVDdtSN/tE1lN24vCvXPz0tJlPtx5dNW3fSi+qQLoYS1d2dyN3wN -dlStf/AEhdoH7ubKKbalRhCpEK0UA969jb2aesy8SxPZLCb6BgmbtGWHQ3QIvt/5 -aaZiyuvVNjJng3bzANgu2I7dbQ9yOELswcbo/l+YW+hIS5Dj0v9lYGPGn13C0U2i -EudM1fLyM8uvmX3zBZSGEyMh+lSY68uym1mzP26MvYNdIU5wnttydE2XV8wNz7ld -y3z+MyaDGQdZsdEZyv8Ebi4qZMkrKvXv0MNOxLAf+ZfFA+g4wsepxgPaCsvDZKKH -vldvyIZOXh3Lh8vIfXGVX+CunUk8FXGvifrNDWEpwZfLHitTnsQujGlKx8kCAwEA -AQKCAgBXSm2MpflDD/xrEiQbXU0bAwYdDBQpCuSBHYG0ZGzjoj4MH8buEb8KOu+O -ybUNZGp/38+Uafii1gnKreSw5DEIO9Im6CAxtyqWmrzsEE4UMFlGvOWcwVKDXveK -pJzqlZ1nOfyzCjxb0tGSRjCaXZ1xUFz6QrZH3LFt6jQPjalp8XjW/jUGsB7VAZ58 -C33TFpQa9oJ+L+Xrs0BURFj8yVpjpz9qDc1ZCvrkjnrChUHsb9qJzb9YqlmRaij2 -x4mNgDvhSZOhu9CYJt7sZlleSz0Sxm0Byxe9c6br7WOihaz+XzX+bC8IBWg5w0Lv -V3Nmos/K2ubmGi+rVEIUD5qBO1oHC4Dnu0bMiGPx18Gpl9kNa7DsK0khmhLaDeu/ -uYjHmwuBtS5XP1lVbVYvmjalKSATnmrQx3vzoqOBdI/CTFhN/s9RWUe+sAjWsVSc -PTLePQ+eAvZMVD9no8//0QjBfE/1R5jhQGoSq/raC9Rclxt/7mRyim882n1+o03l -PPAzuD91HbcKNAkIcOy2BWU1XDcyBvHTuabVHin6n38+bztQF1e1ryPqAz5GEXSL -Yh7sSEqZ3FwtWcX+1zqqmmuVruqPS8AjqKtsaQhrjY+pwhjwG9qKy+7naZvl75ZW -fCvM+7SRXsgy7DC2NymEDPraqMtbitoOQxmdH1241gztxqccIQKCAQEAy/qYQNog -0sL6YEiJHL1M8Mb6muMJABjld/FXpY+XM4Bfs3C35Xtcg/N+Uh5Fvq05es8hjNT5 -inft/dXD/OHdDZH5hzmDxFiYNhHTRtMU2LB9X5sQc5BYU0CJaGQXV3yagvcg5/58 -PsuKO+f+asCDi0fwjn6S0/zgFrMAEnmrmrXhi8/tNnnUekejYIr+bZuD+H4Acf+i -EB0ygLqqKgUSXpBkVszKvNH4jFyqdA0/9fIU21FPHSAbSHVNvfcwApRaXj8Z0IgF -op/OvXHj4FPQLegngNuRc52cWtVr3OjchC9XngMwFXglMioOlIoprSkpdkp1YBZA -wAr/LpSmT9koRwKCAQEAwOEw4QIozwOjNA7Ki341i2wT6sHp7Lr6Tjib7hknM/Re -Ohz6IWGbr8ly5TayIKwGoDpp0/RUu31HeKVgSack/VevOaghW/as5ALIvyGV2MJM -w94dwKkLeE3T0Z3wxM1ELKOXJdfKb/yfpYQJesGeF6mxKdykQ/zD196H/fIyfgnT -q/omuoNPUreRsDxH6MKhj3MS0UBYiABlMRx8vR2G76odYIUOWJJHXkW0PNlbhija -IOhB2in5MfgJ28Xmf7HmV7bYIAtG3YZTWG6hlxljPf8Qodp6TA7OSOC5WOmmAFbC -xUFofMloBpEYz2YimktWCaTcVHteiktfdk+nIkSnbwKCAQEAkW8n9T1RH9Si/dlZ -4WLbI+VLMvnjJe2aVq195258yNyj32XjyDvvl6kZjOVGpxANJpHegvIqxd6CknRC -m+BSYuWMeyy31VuxkwOclyfS+jjD+1GtJihpwVoHXqXWuqr945jeHmslHQS0l8fu -byC56amuS3rVp03qXGTeDU4w20sI+E2U/T1aEKFZTHFtvKqgKqF0IdO5MjIPGxd8 -Uh9xnHjpAbZcaspuo21CnyH/U5V553GOrd6BdWUlu+ctlPk/gWkON89z7SJyHkLA -zeYUTVb0K3zhtQRQQbdfg4+IArtahjARrY0PQDgaUzA7TNpHVK78Bzl2izaMASM9 -fTsA6wKCAQBnjT8BvngMVEadn0dMxtCWbsruoXcmemgB8NB+bxCmCw8/oekEXPQJ -11yRBOFzOwg/o7zHZ4jKNANYGWltgYgRX68ahFKMng3KSFhgjPZ3LjGqgqh0lA0t -ZJNRGbt23UE5ugZe8dCkePt5ED9KoYJv79HGyMeEHMNENRvL0ekb08jJrv516iN/ -JEDaXjK5Gy1D56L1ptchBR1O1Z1+psiYCTvGYwkFslsQmNmgRY2mpG4fdrJMH3bD -Rgh87m3Gpssk0myMH6HHMuOyOYsVpTKryTGzw6kfBl/nroaz3pUZ33qoDmq7fCIW -THYGey4eqk2h1dnYnXdvRfIVgcQYWMWPAoIBAEhFOr/Rgv9soX9EvTfGLXx6KZ+1 -ryJdnFKI1pex2RrVmxMqK/o6xZgFJEvpQv5JmQEHWDJqc8gUmbmC5Gy6lZuuleR3 -Maq675p9R2U4mi2AvNQYJvbCrqQCUZA60F3UpB68xNh/2srbLIL3Eq1b7BT0kUAS -T8CkMEtV5GSB1rH8a39LzRjWCyHJ7k/sWaq2dVF76b9/MorCTvq9rBInGeRD8GaP -GoNddW/jHLbdWsOGtnzSIXYqxYyXWRGXkiD5aOzQX2rE9ml4qVpT5ytX9uUSQJjq -cf1zSJX0I5GEo9EIBb2r7cFNdOLa02qTL/IO4a3c5NbHqmDBqyfh9lpU6Do= ------END RSA PRIVATE KEY-----'; - } - - public function testPrivateKeyload() - { - $loadedKey = new PrivateKey(self::getTestRSAPrivateKey()); - - //check if the private key has been loaded correctly - $this->assertEquals(true, $loadedKey->isLoaded()); - } - - public function testPublicKeyload() - { - $loadedKey = new PublicKey(self::getTestRSAPublicKey()); - - //check if the private key has been loaded correctly - $this->assertEquals(true, $loadedKey->isLoaded()); - } - - public function testFakePrivateKeyload() - { - //the load of a bad key results in an exception - $this->expectException(AsymmetricException::class); - - //try load an invalid key - $loadedKey = new PrivateKey('th1s is s0m3 Sh1t th4t, obviously, is NOT an RSA pr1v4t3 k3y!'); - - //the exception was thrown, the loaded key is null! - $this->assertEquals(null, $loadedKey); - } - - public function testKeyGeneration() - { - //generate a new serialized key - $serialized_private_key = PrivateKey::generate(); - - $this->assertEquals(true, strlen($serialized_private_key) > 1); - - //load the newly generated key - $private_key = new PrivateKey($serialized_private_key); - - $this->assertEquals(true, $private_key->isLoaded()); - } -} + + */ +class KeyTest extends TestCase +{ + public static function getTestRSAPublicKey() + { + return '-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwUHsaL7kLg0CWpUlnQkT +YW3HFSNRV8fAKKWWeZswc4JXbPTqeiorGZT3mPlKl/e+FrwKPK7ZUYHlLxdEDOB5 +0Sqe/qOalA/zr6OGWkxC+rq5BibPz29dcTFr9GSsF01PorR57sKqqeZPfTboAKA1 +jTNL8dwFqFuT/ZI+NC6rki2lTt3JZb/LUOqvqY0vW8wlyXBxTScJpPEhlXcRuN5Y +QLJCYhz9f8AlMhzUYj3AN6hhYFCwa5+PgAGzjBpWVzDKGNFWCrxVejGkONrypbSr +nF6yHj6jVpVSfZvQFK9cmujGeU6fackVLcARUiUDY3rS0lFNC41VADHP5hpgPLTp +YwONOkuWoPFvyVIJDMCB0IIkbRSojrtuNlkGx7ZVksXA7pRP48LhFRjyHEHGnZ87 +7pMoDd8e33/1Lut2NUMpCOR/8owgek985rv049tsa9Uw6xL92anOt6CcUS4SxOdY +lh3mnDuAB4egzLS2UUVmmaOgGVkfnzoPLxg+m1tF7nRqSNtCQOBAQVcDP3189//w +DPj/pt5btnKsH9bC720+AmuM7XKuX3uiIjDo8UTENdQpoEUG0A/gKXmdCagOeLZ+ +tdaf03LPdaT7yoYCcmdLezqtMRywLkyxqDNhLfqJvQD+tct02fASZKjxLP2+F48L +LDdopnAc51efTUT3s+DN+J0CAwEAAQ== +-----END PUBLIC KEY-----'; + } + + public static function getTestRSAPrivateKey() + { + return '-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAma9gYrocyBwqcKrhjsg29ySYSoKC+ovzGSg83P7aelrKrRKP +6bjlgi/Su1iNAXDJZXg2Dpcrf1uxOAGlbnuVkfEbQLeW+5gZn4jazKYdaSEMbYdf +9pWlCDU5Ao81oyWFukmkNYaD+bjmAuoRR5EG3rLoXDVfFtpgWtwyWpJpBvPABFcC +8xspdsmcZp9mmWvUi9HEZHrUOu7AgW9kot7DZ4AonMhtbGNj2t9CafceG00g6Y30 +fYIiMTZDrc5gNOKFC20ikwFIC9NDh6gCrD0mAQOvSipR0N9nfIk2aZbNPvhFvzQV +tMP6DWS3Oo5LzVDdtSN/tE1lN24vCvXPz0tJlPtx5dNW3fSi+qQLoYS1d2dyN3wN +dlStf/AEhdoH7ubKKbalRhCpEK0UA969jb2aesy8SxPZLCb6BgmbtGWHQ3QIvt/5 +aaZiyuvVNjJng3bzANgu2I7dbQ9yOELswcbo/l+YW+hIS5Dj0v9lYGPGn13C0U2i +EudM1fLyM8uvmX3zBZSGEyMh+lSY68uym1mzP26MvYNdIU5wnttydE2XV8wNz7ld +y3z+MyaDGQdZsdEZyv8Ebi4qZMkrKvXv0MNOxLAf+ZfFA+g4wsepxgPaCsvDZKKH +vldvyIZOXh3Lh8vIfXGVX+CunUk8FXGvifrNDWEpwZfLHitTnsQujGlKx8kCAwEA +AQKCAgBXSm2MpflDD/xrEiQbXU0bAwYdDBQpCuSBHYG0ZGzjoj4MH8buEb8KOu+O +ybUNZGp/38+Uafii1gnKreSw5DEIO9Im6CAxtyqWmrzsEE4UMFlGvOWcwVKDXveK +pJzqlZ1nOfyzCjxb0tGSRjCaXZ1xUFz6QrZH3LFt6jQPjalp8XjW/jUGsB7VAZ58 +C33TFpQa9oJ+L+Xrs0BURFj8yVpjpz9qDc1ZCvrkjnrChUHsb9qJzb9YqlmRaij2 +x4mNgDvhSZOhu9CYJt7sZlleSz0Sxm0Byxe9c6br7WOihaz+XzX+bC8IBWg5w0Lv +V3Nmos/K2ubmGi+rVEIUD5qBO1oHC4Dnu0bMiGPx18Gpl9kNa7DsK0khmhLaDeu/ +uYjHmwuBtS5XP1lVbVYvmjalKSATnmrQx3vzoqOBdI/CTFhN/s9RWUe+sAjWsVSc +PTLePQ+eAvZMVD9no8//0QjBfE/1R5jhQGoSq/raC9Rclxt/7mRyim882n1+o03l +PPAzuD91HbcKNAkIcOy2BWU1XDcyBvHTuabVHin6n38+bztQF1e1ryPqAz5GEXSL +Yh7sSEqZ3FwtWcX+1zqqmmuVruqPS8AjqKtsaQhrjY+pwhjwG9qKy+7naZvl75ZW +fCvM+7SRXsgy7DC2NymEDPraqMtbitoOQxmdH1241gztxqccIQKCAQEAy/qYQNog +0sL6YEiJHL1M8Mb6muMJABjld/FXpY+XM4Bfs3C35Xtcg/N+Uh5Fvq05es8hjNT5 +inft/dXD/OHdDZH5hzmDxFiYNhHTRtMU2LB9X5sQc5BYU0CJaGQXV3yagvcg5/58 +PsuKO+f+asCDi0fwjn6S0/zgFrMAEnmrmrXhi8/tNnnUekejYIr+bZuD+H4Acf+i +EB0ygLqqKgUSXpBkVszKvNH4jFyqdA0/9fIU21FPHSAbSHVNvfcwApRaXj8Z0IgF +op/OvXHj4FPQLegngNuRc52cWtVr3OjchC9XngMwFXglMioOlIoprSkpdkp1YBZA +wAr/LpSmT9koRwKCAQEAwOEw4QIozwOjNA7Ki341i2wT6sHp7Lr6Tjib7hknM/Re +Ohz6IWGbr8ly5TayIKwGoDpp0/RUu31HeKVgSack/VevOaghW/as5ALIvyGV2MJM +w94dwKkLeE3T0Z3wxM1ELKOXJdfKb/yfpYQJesGeF6mxKdykQ/zD196H/fIyfgnT +q/omuoNPUreRsDxH6MKhj3MS0UBYiABlMRx8vR2G76odYIUOWJJHXkW0PNlbhija +IOhB2in5MfgJ28Xmf7HmV7bYIAtG3YZTWG6hlxljPf8Qodp6TA7OSOC5WOmmAFbC +xUFofMloBpEYz2YimktWCaTcVHteiktfdk+nIkSnbwKCAQEAkW8n9T1RH9Si/dlZ +4WLbI+VLMvnjJe2aVq195258yNyj32XjyDvvl6kZjOVGpxANJpHegvIqxd6CknRC +m+BSYuWMeyy31VuxkwOclyfS+jjD+1GtJihpwVoHXqXWuqr945jeHmslHQS0l8fu +byC56amuS3rVp03qXGTeDU4w20sI+E2U/T1aEKFZTHFtvKqgKqF0IdO5MjIPGxd8 +Uh9xnHjpAbZcaspuo21CnyH/U5V553GOrd6BdWUlu+ctlPk/gWkON89z7SJyHkLA +zeYUTVb0K3zhtQRQQbdfg4+IArtahjARrY0PQDgaUzA7TNpHVK78Bzl2izaMASM9 +fTsA6wKCAQBnjT8BvngMVEadn0dMxtCWbsruoXcmemgB8NB+bxCmCw8/oekEXPQJ +11yRBOFzOwg/o7zHZ4jKNANYGWltgYgRX68ahFKMng3KSFhgjPZ3LjGqgqh0lA0t +ZJNRGbt23UE5ugZe8dCkePt5ED9KoYJv79HGyMeEHMNENRvL0ekb08jJrv516iN/ +JEDaXjK5Gy1D56L1ptchBR1O1Z1+psiYCTvGYwkFslsQmNmgRY2mpG4fdrJMH3bD +Rgh87m3Gpssk0myMH6HHMuOyOYsVpTKryTGzw6kfBl/nroaz3pUZ33qoDmq7fCIW +THYGey4eqk2h1dnYnXdvRfIVgcQYWMWPAoIBAEhFOr/Rgv9soX9EvTfGLXx6KZ+1 +ryJdnFKI1pex2RrVmxMqK/o6xZgFJEvpQv5JmQEHWDJqc8gUmbmC5Gy6lZuuleR3 +Maq675p9R2U4mi2AvNQYJvbCrqQCUZA60F3UpB68xNh/2srbLIL3Eq1b7BT0kUAS +T8CkMEtV5GSB1rH8a39LzRjWCyHJ7k/sWaq2dVF76b9/MorCTvq9rBInGeRD8GaP +GoNddW/jHLbdWsOGtnzSIXYqxYyXWRGXkiD5aOzQX2rE9ml4qVpT5ytX9uUSQJjq +cf1zSJX0I5GEo9EIBb2r7cFNdOLa02qTL/IO4a3c5NbHqmDBqyfh9lpU6Do= +-----END RSA PRIVATE KEY-----'; + } + + public function testPrivateKeyload() + { + $loadedKey = new PrivateKey(self::getTestRSAPrivateKey()); + + //check if the private key has been loaded correctly + $this->assertEquals(true, $loadedKey->isLoaded()); + } + + public function testPublicKeyload() + { + $loadedKey = new PublicKey(self::getTestRSAPublicKey()); + + //check if the private key has been loaded correctly + $this->assertEquals(true, $loadedKey->isLoaded()); + } + + public function testFakePrivateKeyload() + { + //the load of a bad key results in an exception + $this->expectException(AsymmetricException::class); + + //try load an invalid key + $loadedKey = new PrivateKey('th1s is s0m3 Sh1t th4t, obviously, is NOT an RSA pr1v4t3 k3y!'); + + //the exception was thrown, the loaded key is null! + $this->assertEquals(null, $loadedKey); + } + + public function testKeyGeneration() + { + //generate a new serialized key + $serialized_private_key = PrivateKey::generate(); + + $this->assertEquals(true, strlen($serialized_private_key) > 1); + + //load the newly generated key + $private_key = new PrivateKey($serialized_private_key); + + $this->assertEquals(true, $private_key->isLoaded()); + } +} diff --git a/tests/Security/Encryption/Symmetric/CriptographyTest.php b/tests/Security/Encryption/Symmetric/CriptographyTest.php index 8a169307..ad02f585 100644 --- a/tests/Security/Encryption/Symmetric/CriptographyTest.php +++ b/tests/Security/Encryption/Symmetric/CriptographyTest.php @@ -1,152 +1,152 @@ - - */ -class CriptographyTest extends TestCase -{ - public function testAES128Encryption() - { - //generate the key - $key = new SecretKey(SecretKey::generate('testing/key')); - - $message = 'you should hide this, lol!'; - - //encrypt the message - $enc_message = Cryptography::encrypt($key, $message); - - //decrypt the message - $result = Cryptography::decrypt($key, $enc_message['Encryption'], $enc_message['IV_base64']); - - //test the result - $this->assertEquals($message, $result); - } - - public function testAES128LongEncryption() - { - //generate the key - $key = new SecretKey(SecretKey::generate('testing/key')); - - $message = base64_encode(openssl_random_pseudo_bytes(515)); - - //encrypt the message - $enc_message = Cryptography::encrypt($key, $message); - - //decrypt the message - $result = Cryptography::decrypt($key, $enc_message['Encryption'], $enc_message['IV_base64']); - - //test the result - $this->assertEquals($message, $result); - } - - public function testAES192Encryption() - { - //generate the key - $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 24)); - - $message = base64_encode(openssl_random_pseudo_bytes(512)); - - //encrypt the message - $enc_message = Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_192); - - //decrypt the message - $result = Cryptography::decrypt($key, $enc_message['Encryption'], $enc_message['IV_base64'], Cryptography::AES_CBC_192); - - //test the result - $this->assertEquals($message, $result); - } - - public function testAES256Encryption() - { - //generate the key - $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 32)); - - $message = base64_encode(openssl_random_pseudo_bytes(512)); - - //encrypt the message - $enc_message = Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_256); - - //decrypt the message - $result = Cryptography::decrypt($key, $enc_message['Encryption'], $enc_message['IV_base64'], Cryptography::AES_CBC_256); - - //test the result - $this->assertEquals($message, $result); - } - - public function testInvalidKey() - { - $this->expectException(\InvalidArgumentException::class); - - //generate the key - $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 1)); - - $message = base64_encode(openssl_random_pseudo_bytes(512)); - - //encrypt the message (trigger the exception) - Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_128); - } - - public function testAES128BadKey() - { - $this->expectException(SymmetricException::class); - - //generate the key - $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 2)); - - $message = base64_encode(openssl_random_pseudo_bytes(512)); - - //encrypt the message (trigger the exception) - Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_128); - } - - public function testAES192BadKey() - { - $this->expectException(SymmetricException::class); - - //generate the key - $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 40)); - - $message = base64_encode(openssl_random_pseudo_bytes(512)); - - //encrypt the message (trigger the exception) - Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_192); - } - - public function testAES256BadKey() - { - $this->expectException(SymmetricException::class); - - //generate the key - $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 12)); - - $message = base64_encode(openssl_random_pseudo_bytes(512)); - - //encrypt the message (trigger the exception) - Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_256); - } -} + + */ +class CriptographyTest extends TestCase +{ + public function testAES128Encryption() + { + //generate the key + $key = new SecretKey(SecretKey::generate('testing/key')); + + $message = 'you should hide this, lol!'; + + //encrypt the message + $enc_message = Cryptography::encrypt($key, $message); + + //decrypt the message + $result = Cryptography::decrypt($key, $enc_message['Encryption'], $enc_message['IV_base64']); + + //test the result + $this->assertEquals($message, $result); + } + + public function testAES128LongEncryption() + { + //generate the key + $key = new SecretKey(SecretKey::generate('testing/key')); + + $message = base64_encode(openssl_random_pseudo_bytes(515)); + + //encrypt the message + $enc_message = Cryptography::encrypt($key, $message); + + //decrypt the message + $result = Cryptography::decrypt($key, $enc_message['Encryption'], $enc_message['IV_base64']); + + //test the result + $this->assertEquals($message, $result); + } + + public function testAES192Encryption() + { + //generate the key + $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 24)); + + $message = base64_encode(openssl_random_pseudo_bytes(512)); + + //encrypt the message + $enc_message = Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_192); + + //decrypt the message + $result = Cryptography::decrypt($key, $enc_message['Encryption'], $enc_message['IV_base64'], Cryptography::AES_CBC_192); + + //test the result + $this->assertEquals($message, $result); + } + + public function testAES256Encryption() + { + //generate the key + $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 32)); + + $message = base64_encode(openssl_random_pseudo_bytes(512)); + + //encrypt the message + $enc_message = Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_256); + + //decrypt the message + $result = Cryptography::decrypt($key, $enc_message['Encryption'], $enc_message['IV_base64'], Cryptography::AES_CBC_256); + + //test the result + $this->assertEquals($message, $result); + } + + public function testInvalidKey() + { + $this->expectException(\InvalidArgumentException::class); + + //generate the key + $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 1)); + + $message = base64_encode(openssl_random_pseudo_bytes(512)); + + //encrypt the message (trigger the exception) + Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_128); + } + + public function testAES128BadKey() + { + $this->expectException(SymmetricException::class); + + //generate the key + $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 2)); + + $message = base64_encode(openssl_random_pseudo_bytes(512)); + + //encrypt the message (trigger the exception) + Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_128); + } + + public function testAES192BadKey() + { + $this->expectException(SymmetricException::class); + + //generate the key + $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 40)); + + $message = base64_encode(openssl_random_pseudo_bytes(512)); + + //encrypt the message (trigger the exception) + Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_192); + } + + public function testAES256BadKey() + { + $this->expectException(SymmetricException::class); + + //generate the key + $key = new SecretKey(SecretKey::generate('T3st1n9/k3y <3', 12)); + + $message = base64_encode(openssl_random_pseudo_bytes(512)); + + //encrypt the message (trigger the exception) + Cryptography::encrypt($key, $message, null, Cryptography::AES_CBC_256); + } +} diff --git a/tests/Security/Encryption/Symmetric/KeyTest.php b/tests/Security/Encryption/Symmetric/KeyTest.php index c1622e84..2f8139a1 100644 --- a/tests/Security/Encryption/Symmetric/KeyTest.php +++ b/tests/Security/Encryption/Symmetric/KeyTest.php @@ -1,37 +1,37 @@ - - */ -class KeyTest extends TestCase -{ - public function testKeyExport() - { - $key = new Secretkey('6578616d706c65206865782064617461'); - - $this->assertEquals('6578616d706c65206865782064617461', ''.$key); - } -} + + */ +class KeyTest extends TestCase +{ + public function testKeyExport() + { + $key = new Secretkey('6578616d706c65206865782064617461'); + + $this->assertEquals('6578616d706c65206865782064617461', ''.$key); + } +} diff --git a/tests/Security/Hashing/AlgorithmTest.php b/tests/Security/Hashing/AlgorithmTest.php index 23c1025e..11a3952d 100644 --- a/tests/Security/Hashing/AlgorithmTest.php +++ b/tests/Security/Hashing/AlgorithmTest.php @@ -1,237 +1,237 @@ - - */ -class AlgorithmTest extends TestCase -{ - public function testInvalidMessageForOpensslHash() - { - $this->expectException(\InvalidArgumentException::class); - - //test hash compatibility - Algorithm::opensslHash('', Algorithm::SHA512); - } - - public function testInvalidMessageForRot13Hash() - { - $this->expectException(\InvalidArgumentException::class); - - //test hash compatibility - Algorithm::rot13Hash(''); - } - - public function testInvalidMessageForBcryptHash() - { - $this->expectException(\InvalidArgumentException::class); - - Algorithm::bcryptHash(''); - } - - public function testInvalidMessageForOpensslVerify() - { - $this->expectException(\InvalidArgumentException::class); - - //test hash compatibility - Algorithm::opensslVerify('', ':)', Algorithm::SHA512); - } - - public function testInvalidMessageForRot13Verify() - { - $this->expectException(\InvalidArgumentException::class); - - //test hash compatibility - Algorithm::rot13Verify('', ':)'); - } - - public function testInvalidMessageForBcryptVerify() - { - $this->expectException(\InvalidArgumentException::class); - - //test hash compatibility - Algorithm::bcryptVerify('', ':)'); - } - - public function testInvalidMessageDigestForOpensslVerify() - { - $this->expectException(\InvalidArgumentException::class); - - //test hash compatibility - Algorithm::opensslVerify('My message', '', Algorithm::SHA512); - } - - public function testInvalidMessageDigestForRot13Verify() - { - $this->expectException(\InvalidArgumentException::class); - - //test hash compatibility - Algorithm::rot13Verify('My message', ''); - } - - public function testInvalidMessageDigestForBcryptVerify() - { - $this->expectException(\InvalidArgumentException::class); - - //test hash compatibility - Algorithm::bcryptVerify('My message', ''); - } - - public function testOpensslVerify() - { - $random = bin2hex(openssl_random_pseudo_bytes(25)); - - //test hash compatibility - $hash = Algorithm::opensslHash($random, Algorithm::SHA512); - $this->assertEquals(true, Algorithm::opensslVerify($random, $hash, Algorithm::SHA512)); - - $this->assertEquals(false, Algorithm::opensslVerify($random, 'any other thing', Algorithm::SHA512)); - } - - public function testRot13Verify() - { - $random = bin2hex(openssl_random_pseudo_bytes(25)); - - //test hash compatibility - $hash = Algorithm::rot13Hash($random); - $this->assertEquals(true, Algorithm::rot13Verify($random, $hash)); - - $this->assertEquals(false, Algorithm::rot13Verify($random, 'any other thing')); - } - - public function testBCryptVerify() - { - $random = bin2hex(openssl_random_pseudo_bytes(25)); - - //test hash compatibility - $hash = Algorithm::bcryptHash($random); - $this->assertEquals(true, Algorithm::bcryptVerify($random, $hash)); - - $this->assertEquals(false, Algorithm::bcryptVerify($random, 'any other thing')); - } - - public function testROT13() - { - $message = 'this is a small>exampleassertEquals($message_rot13, $rot_ed); - $this->assertEquals($message, Algorithm::rot13Hash($rot_ed, Algorithm::ROT13)); - - $this->assertEquals(true, Algorithm::rot13Verify($message, $rot_ed)); - } - - public function testHashCompatibility() - { - $message = openssl_random_pseudo_bytes(128); - - //test hash compatibility - $this->assertEquals(md5($message), Algorithm::opensslHash($message, Algorithm::MD5)); - $this->assertEquals(sha1($message), Algorithm::opensslHash($message, Algorithm::SHA1)); - } - - public function testBadHashPbkdf2() - { - $this->expectException(HashingException::class); - - Algorithm::pbkdf2('password', 'salt', 512, 3, 'bad-algo'); - } - - public function testBadAlgorithmPbkdf2() - { - $this->expectException(\InvalidArgumentException::class); - - Algorithm::pbkdf2('message', 'salt', 512, 3, ''); - } - - public function testBadCountHashPbkdf2() - { - $this->expectException(\InvalidArgumentException::class); - - Algorithm::pbkdf2('password', 'salt', 512, '3', Algorithm::SHA256); - } - - public function testBadKeylengthHashPbkdf2() - { - $this->expectException(\InvalidArgumentException::class); - - Algorithm::pbkdf2('password', 'salt', '512', 3, Algorithm::SHA256); - } - - public function testHashPbkdf2() - { - //test vectors from https://www.ietf.org/rfc/rfc6070.txt - $testVector = [ - ['password', 'salt', 20, 1, 'SHA1'], - ['password', 'salt', 20, 2, 'SHA1'], - ['password', 'salt', 20, 4096, 'SHA1'], - ['password', 'salt', 20, 16777216, 'SHA1'], - ['passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 25, 4096, 'SHA1'], - ["pass\0word", "sa\0lt", 16, 4096, 'SHA1'], - ]; - $resultsVector = [ - '0c60c80f961f0e71f3a9b524af6012062fe037a6', - 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957', - '4b007901b765489abead49d926f721d065a429c1', - 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984', - '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038', - '56fa6aa75548099dcc37d7f03425e0c3', - ]; - - foreach ($testVector as $testIndex => $testValue) { - //run the vector test allowing openssl hashing - $this->assertEquals($resultsVector[$testIndex], Algorithm::pbkdf2($testValue[0], $testValue[1], $testValue[2], $testValue[3], $testValue[4], false, false)); - } - } - - public function testSlowHashPbkdf2() - { - //test vectors from https://www.ietf.org/rfc/rfc6070.txt - $testVector = [ - ['password', 'salt', 20, 1, 'SHA1'], - ['password', 'salt', 20, 2, 'SHA1'], - ['password', 'salt', 20, 4096, 'SHA1'], - ['password', 'salt', 20, 16777216, 'SHA1'], - ['passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 25, 4096, 'SHA1'], - ["pass\0word", "sa\0lt", 16, 4096, 'SHA1'], - ]; - $resultsVector = [ - '0c60c80f961f0e71f3a9b524af6012062fe037a6', - 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957', - '4b007901b765489abead49d926f721d065a429c1', - 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984', - '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038', - '56fa6aa75548099dcc37d7f03425e0c3', - ]; - - foreach ($testVector as $testIndex => $testValue) { - //run the vector test forcing the hash library hashing - $this->assertEquals($resultsVector[$testIndex], Algorithm::pbkdf2($testValue[0], $testValue[1], $testValue[2], $testValue[3], $testValue[4], false, true)); - } - } -} + + */ +class AlgorithmTest extends TestCase +{ + public function testInvalidMessageForOpensslHash() + { + $this->expectException(\InvalidArgumentException::class); + + //test hash compatibility + Algorithm::opensslHash('', Algorithm::SHA512); + } + + public function testInvalidMessageForRot13Hash() + { + $this->expectException(\InvalidArgumentException::class); + + //test hash compatibility + Algorithm::rot13Hash(''); + } + + public function testInvalidMessageForBcryptHash() + { + $this->expectException(\InvalidArgumentException::class); + + Algorithm::bcryptHash(''); + } + + public function testInvalidMessageForOpensslVerify() + { + $this->expectException(\InvalidArgumentException::class); + + //test hash compatibility + Algorithm::opensslVerify('', ':)', Algorithm::SHA512); + } + + public function testInvalidMessageForRot13Verify() + { + $this->expectException(\InvalidArgumentException::class); + + //test hash compatibility + Algorithm::rot13Verify('', ':)'); + } + + public function testInvalidMessageForBcryptVerify() + { + $this->expectException(\InvalidArgumentException::class); + + //test hash compatibility + Algorithm::bcryptVerify('', ':)'); + } + + public function testInvalidMessageDigestForOpensslVerify() + { + $this->expectException(\InvalidArgumentException::class); + + //test hash compatibility + Algorithm::opensslVerify('My message', '', Algorithm::SHA512); + } + + public function testInvalidMessageDigestForRot13Verify() + { + $this->expectException(\InvalidArgumentException::class); + + //test hash compatibility + Algorithm::rot13Verify('My message', ''); + } + + public function testInvalidMessageDigestForBcryptVerify() + { + $this->expectException(\InvalidArgumentException::class); + + //test hash compatibility + Algorithm::bcryptVerify('My message', ''); + } + + public function testOpensslVerify() + { + $random = bin2hex(openssl_random_pseudo_bytes(25)); + + //test hash compatibility + $hash = Algorithm::opensslHash($random, Algorithm::SHA512); + $this->assertEquals(true, Algorithm::opensslVerify($random, $hash, Algorithm::SHA512)); + + $this->assertEquals(false, Algorithm::opensslVerify($random, 'any other thing', Algorithm::SHA512)); + } + + public function testRot13Verify() + { + $random = bin2hex(openssl_random_pseudo_bytes(25)); + + //test hash compatibility + $hash = Algorithm::rot13Hash($random); + $this->assertEquals(true, Algorithm::rot13Verify($random, $hash)); + + $this->assertEquals(false, Algorithm::rot13Verify($random, 'any other thing')); + } + + public function testBCryptVerify() + { + $random = bin2hex(openssl_random_pseudo_bytes(25)); + + //test hash compatibility + $hash = Algorithm::bcryptHash($random); + $this->assertEquals(true, Algorithm::bcryptVerify($random, $hash)); + + $this->assertEquals(false, Algorithm::bcryptVerify($random, 'any other thing')); + } + + public function testROT13() + { + $message = 'this is a small>exampleassertEquals($message_rot13, $rot_ed); + $this->assertEquals($message, Algorithm::rot13Hash($rot_ed, Algorithm::ROT13)); + + $this->assertEquals(true, Algorithm::rot13Verify($message, $rot_ed)); + } + + public function testHashCompatibility() + { + $message = openssl_random_pseudo_bytes(128); + + //test hash compatibility + $this->assertEquals(md5($message), Algorithm::opensslHash($message, Algorithm::MD5)); + $this->assertEquals(sha1($message), Algorithm::opensslHash($message, Algorithm::SHA1)); + } + + public function testBadHashPbkdf2() + { + $this->expectException(HashingException::class); + + Algorithm::pbkdf2('password', 'salt', 512, 3, 'bad-algo'); + } + + public function testBadAlgorithmPbkdf2() + { + $this->expectException(\InvalidArgumentException::class); + + Algorithm::pbkdf2('message', 'salt', 512, 3, ''); + } + + public function testBadCountHashPbkdf2() + { + $this->expectException(\InvalidArgumentException::class); + + Algorithm::pbkdf2('password', 'salt', 512, '3', Algorithm::SHA256); + } + + public function testBadKeylengthHashPbkdf2() + { + $this->expectException(\InvalidArgumentException::class); + + Algorithm::pbkdf2('password', 'salt', '512', 3, Algorithm::SHA256); + } + + public function testHashPbkdf2() + { + //test vectors from https://www.ietf.org/rfc/rfc6070.txt + $testVector = [ + ['password', 'salt', 20, 1, 'SHA1'], + ['password', 'salt', 20, 2, 'SHA1'], + ['password', 'salt', 20, 4096, 'SHA1'], + ['password', 'salt', 20, 16777216, 'SHA1'], + ['passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 25, 4096, 'SHA1'], + ["pass\0word", "sa\0lt", 16, 4096, 'SHA1'], + ]; + $resultsVector = [ + '0c60c80f961f0e71f3a9b524af6012062fe037a6', + 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957', + '4b007901b765489abead49d926f721d065a429c1', + 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984', + '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038', + '56fa6aa75548099dcc37d7f03425e0c3', + ]; + + foreach ($testVector as $testIndex => $testValue) { + //run the vector test allowing openssl hashing + $this->assertEquals($resultsVector[$testIndex], Algorithm::pbkdf2($testValue[0], $testValue[1], $testValue[2], $testValue[3], $testValue[4], false, false)); + } + } + + public function testSlowHashPbkdf2() + { + //test vectors from https://www.ietf.org/rfc/rfc6070.txt + $testVector = [ + ['password', 'salt', 20, 1, 'SHA1'], + ['password', 'salt', 20, 2, 'SHA1'], + ['password', 'salt', 20, 4096, 'SHA1'], + ['password', 'salt', 20, 16777216, 'SHA1'], + ['passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 25, 4096, 'SHA1'], + ["pass\0word", "sa\0lt", 16, 4096, 'SHA1'], + ]; + $resultsVector = [ + '0c60c80f961f0e71f3a9b524af6012062fe037a6', + 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957', + '4b007901b765489abead49d926f721d065a429c1', + 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984', + '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038', + '56fa6aa75548099dcc37d7f03425e0c3', + ]; + + foreach ($testVector as $testIndex => $testValue) { + //run the vector test forcing the hash library hashing + $this->assertEquals($resultsVector[$testIndex], Algorithm::pbkdf2($testValue[0], $testValue[1], $testValue[2], $testValue[3], $testValue[4], false, true)); + } + } +} diff --git a/tests/Security/Hashing/HasherTest.php b/tests/Security/Hashing/HasherTest.php index 04f2039d..0f3d225b 100644 --- a/tests/Security/Hashing/HasherTest.php +++ b/tests/Security/Hashing/HasherTest.php @@ -1,63 +1,80 @@ - -*/ -class HasherTest extends TestCase -{ - public function testBadAlgorithm() - { - $this->expectException(HashingException::class); - - $hasher = new Hasher('bad algo'); - } - - public function testBCrypt() - { - $random = bin2hex(openssl_random_pseudo_bytes(128)); - - $hasher = new Hasher(Algorithm::BCRYPT); - - $digest = $hasher->hash($random); - - $this->assertEquals(true, $hasher->verify($random, $digest)); - $this->assertEquals(false, $hasher->verify($random, 'anything else')); - } - - public function testOpenssl() - { - $random = bin2hex(openssl_random_pseudo_bytes(128)); - - $hasher = new Hasher(Algorithm::SHA256); - - $digest = $hasher->hash($random); - - $this->assertEquals(true, $hasher->verify($random, $digest)); - $this->assertEquals(false, $hasher->verify($random, 'anything else')); - } + +*/ +class HasherTest extends TestCase +{ + public function testBadAlgorithm() + { + $this->expectException(HashingException::class); + + $hasher = new Hasher('bad algo'); + } + + public function testBCrypt() + { + $random = bin2hex(openssl_random_pseudo_bytes(128)); + + $hasher = new Hasher(Algorithm::BCRYPT); + + $digest = $hasher->hash($random); + + $this->assertEquals(true, $hasher->verify($random, $digest)); + $this->assertEquals(false, $hasher->verify($random, 'anything else')); + } + + public function testOpenssl() + { + $random = bin2hex(openssl_random_pseudo_bytes(128)); + + $hasher = new Hasher(Algorithm::SHA256); + + $digest = $hasher->hash($random); + + $this->assertEquals(true, $hasher->verify($random, $digest)); + $this->assertEquals(false, $hasher->verify($random, 'anything else')); + } + + public function testPbkdf2() + { + $random = bin2hex(openssl_random_pseudo_bytes(128)); + + $hasher = new Hasher(Algorithm::PBKDF2); + + $digest = $hasher->hash($random); + + $this->assertEquals(true, $hasher->verify($random, $digest)); + $this->assertEquals(false, $hasher->verify($random, 'anything else')); + + $badHash = $digest; + $badHash[strlen($badHash) - 2] = 'a'; + + $this->assertEquals(false, $hasher->verify($random, $badHash)); + } } \ No newline at end of file diff --git a/tests/SetupTestingMongo.js b/tests/SetupTestingMongo.js index b7c1c30e..f1f17fcd 100644 --- a/tests/SetupTestingMongo.js +++ b/tests/SetupTestingMongo.js @@ -1,24 +1,24 @@ -/************************************************************************** -Copyright 2017 Benato Denis - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*****************************************************************************/ - -//create a root user -db.createUser({user:"travis", pwd:"45Jfh4oeF63:380xE",roles:["readWrite","dbAdmin"]}); - -//auth the new user -db.auth("travis", "45Jfh4oeF63:380xE"); - -//and use it to create a new database +/************************************************************************** +Copyright 2017 Benato Denis + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*****************************************************************************/ + +//create a root user +db.createUser({user:"travis", pwd:"45Jfh4oeF63:380xE",roles:["readWrite","dbAdmin"]}); + +//auth the new user +db.auth("travis", "45Jfh4oeF63:380xE"); + +//and use it to create a new database db.getSiblingDB("travis_ci_test"); \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index aa96dc62..5dd71a6c 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,36 +1,36 @@ -addPsr4('Gishiki\\Tests\\', __DIR__); - -include __DIR__.'/Application/FakeController.php'; - -file_put_contents("tests/db_test.sqlite", ""); - -\Gishiki\Gishiki::initialize(); +addPsr4('Gishiki\\Tests\\', __DIR__); + +require dirname(__DIR__).'/tests/FakeController.php'; + +file_put_contents("tests/db_test.sqlite", ""); + +\Gishiki\Gishiki::initialize();