From 0db3e92ee649bd4ad13c65889a65009c0b14558a Mon Sep 17 00:00:00 2001 From: norb Date: Wed, 17 Dec 2025 21:38:05 +0100 Subject: [PATCH] initial commit --- .gitignore | 3 + composer.json | 18 + composer.lock | 172 ++++++++ index.php | 85 ++++ readme.md | 27 ++ src/DomainObjects/Address.php | 49 +++ src/DomainObjects/Business.php | 71 +++ src/DomainObjects/Canton.php | 32 ++ src/DomainObjects/Client.php | 64 +++ src/DomainObjects/DataProvider.php | 9 + src/DomainObjects/DataSource.php | 61 +++ src/DomainObjects/Doctor.php | 44 ++ src/DomainObjects/DomainObject.php | 405 ++++++++++++++++++ src/DomainObjects/Filter/EncodingFilter.php | 53 +++ src/DomainObjects/HealthInsurance.php | 40 ++ src/DomainObjects/Organisation.php | 33 ++ src/DomainObjects/Owner.php | 196 +++++++++ src/DomainObjects/Person.php | 64 +++ src/DomainObjects/Spitex.php | 113 +++++ src/DomainObjects/Token.php | 82 ++++ .../Validation/AbstractValidator.php | 181 ++++++++ .../Validation/AddressValidator.php | 18 + .../Validation/ClientValidator.php | 29 ++ src/DomainObjects/Validation/Error.php | 10 + .../Validation/InvoiceValidator.php | 37 ++ .../Validation/OrganisationValidator.php | 14 + .../Validation/OwnerValidator.php | 23 + .../Validation/PersonValidator.php | 21 + src/DomainObjects/Validation/Result.php | 25 ++ .../Validation/ResultCollection.php | 63 +++ .../Validation/TokenValidator.php | 24 ++ src/DomainObjects/Validation/Validatable.php | 17 + src/DomainObjects/Validation/Validator.php | 19 + src/DomainObjects/Validation/Warning.php | 12 + src/DomainObjects/ValueObjects/AHV.php | 40 ++ src/DomainObjects/ValueObjects/Boolean.php | 54 +++ src/DomainObjects/ValueObjects/Complex.php | 12 + src/DomainObjects/ValueObjects/DataType.php | 233 ++++++++++ src/DomainObjects/ValueObjects/Date.php | 72 ++++ src/DomainObjects/ValueObjects/DateTime.php | 72 ++++ src/DomainObjects/ValueObjects/Decimal.php | 33 ++ src/DomainObjects/ValueObjects/EAN13.php | 112 +++++ src/DomainObjects/ValueObjects/Email.php | 14 + src/DomainObjects/ValueObjects/File.php | 103 +++++ src/DomainObjects/ValueObjects/Image.php | 22 + src/DomainObjects/ValueObjects/IntKey.php | 51 +++ src/DomainObjects/ValueObjects/Integer.php | 50 +++ src/DomainObjects/ValueObjects/Key.php | 11 + src/DomainObjects/ValueObjects/Number.php | 42 ++ src/DomainObjects/ValueObjects/Password.php | 51 +++ src/DomainObjects/ValueObjects/Phone.php | 14 + src/DomainObjects/ValueObjects/Primitive.php | 12 + src/DomainObjects/ValueObjects/Street.php | 58 +++ src/DomainObjects/ValueObjects/Text.php | 24 ++ src/DomainObjects/ValueObjects/URL.php | 14 + src/DomainObjects/ValueObjects/Value.php | 15 + src/DomainObjects/ValueObjects/Varchar.php | 73 ++++ src/DomainObjects/ValueObjects/ZSR.php | 40 ++ src/Log.php | 48 +++ 59 files changed, 3384 insertions(+) create mode 100644 .gitignore create mode 100755 composer.json create mode 100644 composer.lock create mode 100644 index.php create mode 100644 readme.md create mode 100644 src/DomainObjects/Address.php create mode 100644 src/DomainObjects/Business.php create mode 100644 src/DomainObjects/Canton.php create mode 100644 src/DomainObjects/Client.php create mode 100644 src/DomainObjects/DataProvider.php create mode 100644 src/DomainObjects/DataSource.php create mode 100644 src/DomainObjects/Doctor.php create mode 100644 src/DomainObjects/DomainObject.php create mode 100644 src/DomainObjects/Filter/EncodingFilter.php create mode 100644 src/DomainObjects/HealthInsurance.php create mode 100644 src/DomainObjects/Organisation.php create mode 100644 src/DomainObjects/Owner.php create mode 100644 src/DomainObjects/Person.php create mode 100644 src/DomainObjects/Spitex.php create mode 100644 src/DomainObjects/Token.php create mode 100644 src/DomainObjects/Validation/AbstractValidator.php create mode 100644 src/DomainObjects/Validation/AddressValidator.php create mode 100644 src/DomainObjects/Validation/ClientValidator.php create mode 100644 src/DomainObjects/Validation/Error.php create mode 100644 src/DomainObjects/Validation/InvoiceValidator.php create mode 100644 src/DomainObjects/Validation/OrganisationValidator.php create mode 100644 src/DomainObjects/Validation/OwnerValidator.php create mode 100644 src/DomainObjects/Validation/PersonValidator.php create mode 100644 src/DomainObjects/Validation/Result.php create mode 100644 src/DomainObjects/Validation/ResultCollection.php create mode 100644 src/DomainObjects/Validation/TokenValidator.php create mode 100644 src/DomainObjects/Validation/Validatable.php create mode 100644 src/DomainObjects/Validation/Validator.php create mode 100644 src/DomainObjects/Validation/Warning.php create mode 100644 src/DomainObjects/ValueObjects/AHV.php create mode 100644 src/DomainObjects/ValueObjects/Boolean.php create mode 100644 src/DomainObjects/ValueObjects/Complex.php create mode 100644 src/DomainObjects/ValueObjects/DataType.php create mode 100644 src/DomainObjects/ValueObjects/Date.php create mode 100644 src/DomainObjects/ValueObjects/DateTime.php create mode 100644 src/DomainObjects/ValueObjects/Decimal.php create mode 100644 src/DomainObjects/ValueObjects/EAN13.php create mode 100644 src/DomainObjects/ValueObjects/Email.php create mode 100644 src/DomainObjects/ValueObjects/File.php create mode 100644 src/DomainObjects/ValueObjects/Image.php create mode 100644 src/DomainObjects/ValueObjects/IntKey.php create mode 100644 src/DomainObjects/ValueObjects/Integer.php create mode 100644 src/DomainObjects/ValueObjects/Key.php create mode 100644 src/DomainObjects/ValueObjects/Number.php create mode 100644 src/DomainObjects/ValueObjects/Password.php create mode 100644 src/DomainObjects/ValueObjects/Phone.php create mode 100644 src/DomainObjects/ValueObjects/Primitive.php create mode 100644 src/DomainObjects/ValueObjects/Street.php create mode 100644 src/DomainObjects/ValueObjects/Text.php create mode 100644 src/DomainObjects/ValueObjects/URL.php create mode 100644 src/DomainObjects/ValueObjects/Value.php create mode 100644 src/DomainObjects/ValueObjects/Varchar.php create mode 100644 src/DomainObjects/ValueObjects/ZSR.php create mode 100644 src/Log.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f282627 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.log +vendor/ +.directory diff --git a/composer.json b/composer.json new file mode 100755 index 0000000..64f113c --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "verua/assessment", + "license": "AGPL-3.0-or-later", + "autoload": { + "psr-4": { + "VeruA\\": "src/" + } + }, + "authors": [ + { + "name": "norb", + "email": "norbert.wagner@verua.ch" + } + ], + "require": { + "monolog/monolog": "^3.9" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a92795b --- /dev/null +++ b/composer.lock @@ -0,0 +1,172 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "06188d9a0d54c92495e101cc6232cee3", + "packages": [ + { + "name": "monolog/monolog", + "version": "3.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2025-03-24T10:02:05+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..a6fb9c6 --- /dev/null +++ b/index.php @@ -0,0 +1,85 @@ + 1, + 'firstname' => 'Arthur', + 'lastname' => 'Dent', + 'gln' => 7653298712357, +// 'gln' => 7653298712358, // GLN with valid checksum + 'email' => 'dear@example.com', + 'username' => 'ardente' +]); + + +// client +$client = new Client([ + 'id' => 1, + 'firstname' => '', +// 'firstname' => 'Ford', + 'lastname' => 'Prefect', + 'ahv' => 1234, + 'birthday' => new Date('12.02.1842'), + 'address' => new Address([ + 'id' => 1, + 'street' => '58 Orionis', + 'city' => 'Beteigeuze', + 'email' => null, + ]), +]); + +printDMO($owner); +dumpValidationErrors($owner->validate(OwnerValidator::getInstance())); + +printDMO($client); +dumpValidationErrors($client->validate(ClientValidator::getInstance())); + + +function printDMO(DomainObject $dmo, int $level = 1) +{ + echo "".get_class($dmo)."\n"; + echo "
\n"; + foreach ($dmo as $field => $value) + { + // display missing if ValueObject->__toString() is empty + if ((string)$value === '') $value = '__ missing __'; + + // output fieldname and value + echo "
$field:
$value
\n"; + + // recursive print DMOs .. will obviously not work on self reference .. + if ($value instanceOf DomainObject) + { + $level++; + printDMO($value, $level); + $level--; + } + } + echo "
\n"; +} + +function dumpValidationErrors(bool|ResultCollection $validationResult) +{ + if ($validationResult !== true) + { + echo "
Validation Errors:\n
";
+		var_dump($validationResult);
+		echo '

'; + } +} \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e751884 --- /dev/null +++ b/readme.md @@ -0,0 +1,27 @@ +# Assessment: VeruA DomainObjects & Validation +The following assessment may be solved with the help of additional sources, such as Q&A or LLMs but must be indicated as such. + +## General +In the example `index.php` there are three DomainObjects that are instantiated with default values. An [Owner](https://docs.verua.ch/latest/classes/VeruA-DomainObjects-Owner.html)-Object and a [Client](https://docs.verua.ch/latest/classes/VeruA-DomainObjects-Client.html)-Object are build directly. The third, an [Address](https://docs.verua.ch/latest/classes/VeruA-DomainObjects-Address.html)-Object is passed into the address field of the `Client`-Object. + +All three are then validated acording to their respective validators. The Address object is validated through the [ClientValidator](https://code.verua.online/VeruA/Assessment/src/commit/83166a50af0cca2873d74ae0955cad67d20a5d02/src/DomainObjects/Validation/ClientValidator.php#L24). + +## Exercises + +1. Find out where and when the *default validators* of a `ValueObject` are executed. + +1. A *default validator* has to check that an e-mail address is correct on the `email` field of `Owner` and of `Client::Address` if set, but the field can be empty. + Implement it in the `ValueObjects/Email` class and use php's [filter_var(FILTER_VALIDATE_EMAIL)](https://www.php.net/manual/en/filter.constants.php#constant.filter-validate-email) + +1. The validation works well, but the requirements changed. Redesign it, to implement the possibility to have multiple validators at once. As an example implement a new `ExportAddressValidator` in addition to the standard `AddressValidator` that requires an e-mail address and a valid AHV-number. The goal is, to check, if a given `DomainObject` is valid against different validators in different circumstances, configured and executed in one run. + +1. `DomainObjects` are often loaded from a database with the *DataMapper Pattern*, where you get entire collections of `DomainObjects` containing more child objects. + E.g. you have a collection of `Invoice`-Objects that contains an `Owner`-Object, a `Client`-Object that in turn contains an `Address`-Object. + We need a possibility to preconfigure the validators that have to be used to validate the whole collection of `DomainObjects` + This excecise is about the design decisions and not the code. Feel free to design with UML, sample code or with whatever you feel the most comfortable. + +## Considerations + +- Rewrite it with the possibility to have different Validators from different packages. + +- Keep in mind, that if a field is not set at all, it is never validated. diff --git a/src/DomainObjects/Address.php b/src/DomainObjects/Address.php new file mode 100644 index 0000000..f63c22e --- /dev/null +++ b/src/DomainObjects/Address.php @@ -0,0 +1,49 @@ + IntKey::class, + 'street' => Varchar::class, + 'zip' => Varchar::class, + 'city' => Varchar::class, + 'pobox' => Varchar::class, + 'email' => Email::class, + 'phone' => Phone::class, + 'cellPhone' => Phone::class, + 'officePhone' => Phone::class, + 'fax' => Phone::class, + 'website' => URL::class, + ], ...$superFields); + } + +} +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/Business.php b/src/DomainObjects/Business.php new file mode 100644 index 0000000..18beae1 --- /dev/null +++ b/src/DomainObjects/Business.php @@ -0,0 +1,71 @@ + *The name of the Spitex or Owner in charge*
+ * * **$gln** : {@see ValueObjects\EAN13}
*GLN-Number of the Business*
+ * * **$isSpitex** : {@see ValueObjects\Boolean}
*Is it a Spitex Business*
+ * * **$billcare** : {@see ValueObjects\Integer}
*Does Business use BillCare* `0 => false, 2 => MF`
+ * * **$logo** : {@see ValueObjects\Varchar}
*The logo of the Spitex or Owner in charge*
+ * + * @link https://docs.verua.ch/latest/files/public-data-init-db.html + */ +interface Business +{ + /** + * Magic getter, override to implement above mentioned properties + * + * ``` php + * switch ($field) + * { + * case 'name': return $this->name(); + * case 'gln': return $this->gln(); + * ... + * } + * return parent::__get($field); + * ``` + */ + public function __get(string $field); + + /** + * Magic setter, override if special handling on write is needed + */ + public function __set(string $name, $value); + + /** + * Returns the complete name of the Business + */ + public function name(); + + /** + * Returns true if the Business is a Spitex, false otherwise + */ + public function isSpitex(): bool; + + /** + * Activates / Deactivates Modules + * @todo put moduels in a separate table. Do not use the fields directly to change state, to ease + * migration + * @param $modules 'field' => true|false + */ + public function modules(Array $modules); + + /** + * Returns true if the given Module is active for this Spitex or Owner + */ + public function isModuleActive(string $module): bool; + + /** + * Returns true only if all given modules are active + */ + public function areModulesActive(array $modules): bool; +} diff --git a/src/DomainObjects/Canton.php b/src/DomainObjects/Canton.php new file mode 100644 index 0000000..8de9dfa --- /dev/null +++ b/src/DomainObjects/Canton.php @@ -0,0 +1,32 @@ + IntKey::class, + 'taxwert' => Varchar::class, // decimal on db + 'kanton' => Varchar::class, + 'prioritaet' => Integer::class, + 'del' => Boolean::class, + 'tarifsystem' => Integer::class, + 'zsr_nr' => Varchar::class, + 'kuerzel_kanton' => Varchar::class, + 'kvg_uvg' => Integer::class, + 'tariffs_pa' => Boolean::class, + 'color' => Varchar::class, + 'user' => Integer::class, + 'date' => Varchar::class, + + 'owner' => Owner::class // @see blameable object in Symfony + ], ...$superFields); + } +} \ No newline at end of file diff --git a/src/DomainObjects/Client.php b/src/DomainObjects/Client.php new file mode 100644 index 0000000..e6cba62 --- /dev/null +++ b/src/DomainObjects/Client.php @@ -0,0 +1,64 @@ +klient, personen, adressen + * @uses ValueObjects\DataType + * @uses ValueObjects\AHV + * + * @property ValueObjects\Intkey $id The pk of the client + * @property ValueObjects\EAN13 $ahv Clients AHV-Number (SSN) + * @property ValueObjects\Date $birthday The Birthdate + * @property ValueObjects\Integer $childCount Number of Children + * @property ValueObjects\Varchar $citizenship Citizenship + * @property ValueObjects\Varchar $cardId cardId is the unique 20 digits card identifier + * @property ValueObjects\Date $expiryDate expiryDate is the expiry date of the card + * @property Organisation $insurance Organisation object of Insurance (normally KV) + * + * @property-read ValueObjects\Varchar $fullName A pointer to Person fullName + */ +class Client extends Person +{ + use Log; + + protected function fields(array ...$superFields): array + { + return parent::fields([ + // klient + 'id' => IntKey::class, + 'id_pers' => Integer::class, + 'insuranceId' => Integer::class, + 'birthday' => Date::class, + 'ahv' => EAN13::class, + 'insuredId' => Varchar::class, + 'caseId' => Varchar::class, + 'cardId' => Varchar::class, + 'expiryDate' => Date::class, + 'bemerkung' => DataType::class, + 'plz_a' => DataType::class, + 'ort_a' => DataType::class, + 'zivilstand' => DataType::class, + 'childCount' => Integer::class, + 'citizenship' => Varchar::class, + 'insurance' => Organisation::class, + ], ...$superFields); + } +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/DataProvider.php b/src/DomainObjects/DataProvider.php new file mode 100644 index 0000000..5c776b8 --- /dev/null +++ b/src/DomainObjects/DataProvider.php @@ -0,0 +1,9 @@ +provider = $provider; + } + + /** + * The static load() method can be called to load the data into the DomainObject + */ + public static function load(DomainObject $obj): void + { + self::dataProvider()->load($obj); + } + + /** + * the init method has to be called before using the load() method + */ + public static function init(DataProvider $provider): void + { + self::$instance = new DataSource($provider); + } + + /** + * @return DataProvider The DataProvider redirects the load call to the apropriate implementation + * @throws \Exception if init() has not been called already + */ + private static function dataProvider(): DataProvider + { + if (! isset(self::$instance)) throw new \Exception('DataSource needs to be initalized first'); + + return self::$instance->provider; + } +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/Doctor.php b/src/DomainObjects/Doctor.php new file mode 100644 index 0000000..4339c12 --- /dev/null +++ b/src/DomainObjects/Doctor.php @@ -0,0 +1,44 @@ + IntKey::class, + 'idPers' => IntKey::class, + 'field' => Varchar::class, + 'practice' => Varchar::class, + 'zsr' => ZSR::class, + 'gln' => EAN13::class, + ], ...$superFields); + } + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/DomainObject.php b/src/DomainObjects/DomainObject.php new file mode 100644 index 0000000..58189fd --- /dev/null +++ b/src/DomainObjects/DomainObject.php @@ -0,0 +1,405 @@ + IntKey::class, + * // any Class that implements the Value interface + * 'field1' => Value::class, + * // ... + * // or any class that descends from DomainObject + * 'node1' => DomainObject::class, + * // ... + * // make sure to pass on the $superFields (remember the dots) to make sure inheritance works + * ], ...$superFields); + * } + * ``` + * + * @uses ValueObjects\DataType + */ +//}}} +abstract class DomainObject implements Validatable, Value, \Iterator +{ + use Log; + + protected const NEW = 0; + protected const DIRTY = 1; + protected const GHOST = 2; + protected const LOADING = 3; + protected const LOADED = 4; + + /** + * @var ValueObjects\DataType[] $data Contains the ValueObjects with their respective values + */ + protected array $data = []; + + /** + * Contains all configured fields with their respective Values + * + * @todo should we replace this entierly by the fields method + * @var Value[] gets assigned in the constructor by running the fields() method + */ + protected array $dataTypes = []; + + /** + * @var Validator $validator the Validator in use + */ + protected Validation\Validator $validator; + + /** + * @var int state + */ + protected int $state = self::DIRTY; + + /** + * @var bool[] array that contains every field as key that changed after construction with value true + */ + protected array $dirty = []; + + protected bool $isDirty = false; + + protected bool $valid = false; + protected bool $validated = false; + protected ?Violations $violations = null; + + // Constructor {{{ + /** + * Creates a new DomainObject + * + * The DomainObject can be created in the following ways: + * * If an Object of Type ValueObjects\Key is passed, the DomainObjects state is GHOST and will be loaded + * according to the DataSource, when a field is accessed + * * Without a parameter an empty DomainObject is created in the NEW state + * * If $data is an array, it has to be in the form 'field => 'value' as configured in the fields method + * @see ValueObjects\IntKey + * @param ValueObjects\Key|array $data Sets the values of the DomainObject. Every value is passed to the respective + * {@link \VeruA\DomainObjects\ValueObjects} or if a Key is passed a new empty GHOST is created + * @return void + */ + public function __construct($data=null) + { + $this->dataTypes = $this->fields(); + + if ($data instanceof Key) + { + // var_dump($data); + $this->set('id', $data); + $this->markGhost(); + } + // var_dump(get_class(), isset($this->id)); + if (is_array($data)) { + foreach ($data as $name => $value) { + $this->$name = $value; + } + } + if (!isset($this->id)) $this->markNew(); + // var_dump($this->state); + } // }}} + + // load() {{{ + /** + * Puts all fields of $dmo into this DomainObject. + * + * @param DomainObject $dmo a DomainObject + */ + public function load(DomainObject $dmo) + { + foreach ($dmo as $field => $value) + { + $this->data[$field] = $value; + } + } // }}} + + // values() {{{ + /** + * Returns the internal data Array, containing all the DataType Objects + */ + public function values(): array + { + return $this->data; + } // }}} + + // getValueObjectOf() {{{ + public function getValueObjectOf(string $field): Value + { + return $this->get($field); + } // }}} + + // dataTypes() {{{ + /** + * Returns the DataType configuration + */ + public function dataTypes(): array + { + return $this->dataTypes; + } + // }}} + + // fields() {{{ + /** + * Configures the DomainObjects Fields + * + * Overwrite this method to assign the filds with their respective ValueObjects + * make sure to call the parent an pass along all of the $superFields arrays like so ...$superFields + * The concept is simple. Every SuperClass adds its fields in a separate array, and passes them to + * the parent, DomainObject::fields merges them together to have all of the inherited fields available + */ + protected function fields(array ...$superFields): array + { + $fields = array_merge($this->dataTypes(), ...$superFields); +// var_dump($fields); + return $fields; + } // }}} + + // state methods {{{ + + // isNew() {{{ + public function isNew(): bool + { + return ($this->state === self::NEW); + } + // }}} + + // isGhost() {{{ + public function isGhost(): bool + { + return ($this->state === self::GHOST); + } + // }}} + + // isDirty() {{{ + public function isDirty(): bool + { + return $this->isDirty; + } + // }}} + + public function markGhost() { + $this->state = self::GHOST; + $this->dirty = []; + $this->isDirty = false; + } + + public function markNew() { + $this->state = self::NEW; + $this->dirty = []; + $this->isDirty = false; + } + + public function markLoaded() { + $this->state = self::LOADED; + $this->dirty = []; + $this->isDirty = false; + } + + public function markDirty() { + $this->state = self::DIRTY; + $this->isDirty = true; + foreach ($this as $field => $value) { + $this->dirty[$field] = true; + } + } + // }}} + + // __set() {{{ + /** + * Sets a value instatiating a new DataType Object if none is set, or sets the value of the DataType + * @throws \OutOfRangeException if the field does not exist + * @param string $name The name of the field + * @param mixed $value The value + */ + public function __set(string $name, $value) + { + $this->dirty[$name] = true; + $this->isDirty = true; + $this->set($name, $value); + } // }}} + + // __get() {{{ + /** + * Returnes the {@link \VeruA\DomainObjects\DataType} of the requested field + * @throws \OutOfRangeException if the field does not exist + * @param string $name The name of the field + * @return \VeruA\DomainObjects\ValueObjects\Value|mixed Returns the DataType of the field + */ + public function __get(string $field) + { + $valueObject = $this->get($field); + return (! $valueObject instanceof Complex) ? $valueObject->value() : $valueObject; + } // }}} + + // __isset() / __unset() {{{ + public function __isset( $field ) + { + if ($this->isGhost() && $field != 'id') { + DataSource::load($this); + } + return ( isset( $this->data[$field] ) ); + } + + public function __unset( $name ) + { + unset( $this->data[$name] ); + } + // }}} + + // Value methods {{{ + public function __toString() { + return (string)$this->id; + } + public function in($dmo) { + $this->load($dmo); + } + public function out() { + return $this->__toString(); + } + public function value() { + return $this; + } + // defaultValidators() {{{ + /** + * Overwrite this message if a DataType needs validation methods that are always applied + */ + public static function defaultValidators(): array + { + return []; + } + // }}} + // }}} + + // validation methods {{{ + /** + * Returns if this DMO is valid. + * If the DMO is not in a validated state, or because it was not already validated, or because changes + * has been made, it is validated. + * If a validator is given as argument, the DMO is always validated and the internal state is not altered + */ + public function isValid(?Validator $validator=null): bool + { + if (! $this->validated) { + $result = $this->validate($validator); + } + return ($validator) ? (($result === true) ? true : false) : $this->valid; + } + + /** + * @return bool|Validation\ResultCollection Returns true if validation passed or a ResultCollection + */ + public function validate(?Validator $validator=null) + { + $result = ($validator) ? $validator->validate($this) : $this->validator->validate($this); + if ($result === true && $validator) { + $this->valid = true; + } + elseif ($result !== true && !$validator) { + $this->violations = $result; + } + return $result; + } + + /** + * @return Validation\ResultCollection Returns true if validation passed or a ResultCollection + */ + public function violations() : ?Validation\ResultCollection + { + return $this->violations; + } + // }}} + + // iterator methods {{{ + public function rewind(): void { + reset($this->data); + } + public function current(): mixed { + return current($this->data); + } + public function key(): mixed { + return key($this->data); + } + public function next(): void { + next($this->data); + } + public function valid(): bool { + return key($this->data) !== null; + } + // }}} + + // set / get methods {{{ + protected function set(string $field, $value): void + { + if (! (isset($this->dataTypes[$field]) || array_key_exists($field, $this->dataTypes))) { + throw new \OutOfRangeException( 'Can not set unknown index for '.get_class($this).': ' . $field ); + } + + // Value or DomainObject Instance: store in $data array + if ($value instanceof Value || $value instanceof DomainObject) + { + if (is_a($value, $this->dataTypes[$field])) { + $this->data[$field] = $value; + } + else { + $message = "Cannot set $value for $field in ".get_class($this) + ."\nWrong DataType ".get_class($value)." expected: ".$this->dataTypes[$field]; + throw new \InvalidArgumentException($message); + } + } + // handle ValueObject + elseif (is_a($this->dataTypes[$field], Value::class, true)) + { + if (! isset($this->data[$field])) { + // var_dump(get_class($this), $field, $value); + $this->data[$field] = new $this->dataTypes[$field](); + } + + $this->data[$field]->in( $value ); + // var_dump($this->data[$field]); + } + // handle DomainObject + elseif ($this->dataTypes[$field] instanceof DomainObject) + { + $this->data[$field] = new $this->dataTypes[$field]($value); + } + else { + throw new \InvalidArgumentException("Cannot set $value for $field in ".get_class($this) ); + } + + } + + protected function get(string $field) + { + if (!isset($this->dataTypes[$field])) { + throw new \OutOfRangeException( 'Unknown index in '.get_class($this).': ' . $field ); + } + + if ($this->isGhost() && $field != 'id') { + DataSource::load($this); + } +// if ($field == 'name') var_dump($this); + + return $this->data[$field]; + } + //}}} + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/Filter/EncodingFilter.php b/src/DomainObjects/Filter/EncodingFilter.php new file mode 100644 index 0000000..19d5d0a --- /dev/null +++ b/src/DomainObjects/Filter/EncodingFilter.php @@ -0,0 +1,53 @@ +data = $data; + $this->dataEnc = $dataEnc; + $this->outEenc = $outEnc; + $this->inEenc = $inEnc; + } // }}} + + // Magic getter/setter methods {{{ + /** + * + */ + public function __set( $name, $value ) + { + $filtered = ( isset($this->inEnc) ) + ? mb_convert_encoding($value, $this->inEnc, $this->dataEnc) : $value; + + $this->data->$name = $filtered; + } + + public function __get( $name ) + { + $filtered = ( isset($this->outEnc) ) + ? mb_convert_encoding($this->data->$name, $this->outEnc, $this->dataEnc) + : $this->data->$name; + + return $filtered; + } + + public function __isset( $name ) + { + return isset( $this->data->namme ); + } + // }}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/HealthInsurance.php b/src/DomainObjects/HealthInsurance.php new file mode 100644 index 0000000..183b721 --- /dev/null +++ b/src/DomainObjects/HealthInsurance.php @@ -0,0 +1,40 @@ + IntKey::class, + 'health_insurance_name' => Varchar::class, + 'insurance_gln' => EAN13::class, + 'recipient_gln' => EAN13::class, + 'gln_number' => EAN13::class, + 'electronic_tp' => Boolean::class, + 'fk_adressen' => Integer::class, + 'id_law_code' => Integer::class, + 'created_at' => DateTime::class, + 'updated_at' => DateTime::class, + 'published_at' => DateTime::class, + + 'address' => Address::class, + 'law_code' => LegalCategorisation::class, + 'mediport_status' => MediportStatus::class, + 'owner' => Owner::class, + } +} \ No newline at end of file diff --git a/src/DomainObjects/Organisation.php b/src/DomainObjects/Organisation.php new file mode 100644 index 0000000..07d5940 --- /dev/null +++ b/src/DomainObjects/Organisation.php @@ -0,0 +1,33 @@ + IntKey::class, + 'id_adr' => Integer::class, + 'id_kontakt' => Integer::class, + + 'name' => Varchar::class, + 'zusatz1' => Varchar::class, + 'zusatz2' => Varchar::class, + 'gln' => EAN13::class, + + 'address' => Address::class, + ], ...$sf); + } + +} \ No newline at end of file diff --git a/src/DomainObjects/Owner.php b/src/DomainObjects/Owner.php new file mode 100644 index 0000000..9c334c8 --- /dev/null +++ b/src/DomainObjects/Owner.php @@ -0,0 +1,196 @@ + IntKey::class, + 'logo' => Image::class, + 'firstname' => Varchar::class, + 'lastname' => Varchar::class, + 'gln' => EAN13::class, + 'admin' => Boolean::class, + 'billcare' => Integer::class, + 'spitex' => Integer::class, + + 'email' => Varchar::class, + + 'username' => Varchar::class, + 'password' => Password::class, + + // Modules - todo: normalize in additional table + 'bcModule' => Integer::class, + 'xmlModule' => Boolean::class, + 'ekarusModule' => Boolean::class, + 'complementaryModule' => Boolean::class, + 'popModule' => Boolean::class, + 'qrModule' => Boolean::class, + 'dpModule' => Boolean::class, + 'tpModule' => Boolean::class, + 'dpRights' => Integer::class, + 'tpRights' => Integer::class, + + // Components - todo: normalize in additional table, same as modules? + 'componentAccounting' => Boolean::class, + 'componentBasic' => Boolean::class, + 'componentStatistics' => Boolean::class, + 'componentOther' => Boolean::class, + ], ...$superFields); + } + + public function __get(string $field) + { + switch ($field) { + case 'name': return $this->name(); + case 'isAdmin': return $this->isAdmin(); + case 'isSpitex': return $this->isSpitex(); + case 'clientsInCare': return $this->clientsInCare; + case 'hasAccessToDP': return $this->hasAccessToModule('dp'); + case 'hasAccessToTP': return $this->hasAccessToModule('tp'); + case 'logo': return $this->logo(); + } + return parent::__get($field); + } + + public function __set(string $field, $value): void + { + switch ($field) + { + case 'clientsInCare': + $this->clientsInCare = $value; + break; + default: + parent::__set($field, $value); + } + } + + public function name() + { + $name = $this->orga ?? $this->fullName(); + return $name; + } + + public function logo() + { + $logo = $this->data['logo']; + + if (! $logo->isReadable()) { + $logo->in('logo_leer/logo_leer.jpg'); + } + return $logo; + } + + // fullName() {{{ + /** + * A commodity function to get the full Clients name composed as `nname, vname` + * @return string The full name + * @todo add a parameter to customize the formatting/composition + */ + public function fullName() + { + return new Varchar($this->lastname .', '. $this->firstname); + } // }}} + + public function isSpitex(): bool + { + return false; + } + + public function isAdmin(): bool + { + return $this->admin; + } + + /** + * returns true if the owner has the permission to access the clients data + */ + public function hasAccessToClient($clientId): bool + { + // if false not applicable so access granted + return (! $clientId) ? true : $this->inCareOf($clientId); + } + + /** + * returns true if the owner is in care of the client + */ + public function inCareOf($clientId): bool + { + return isset($this->clientsInCare[$clientId]); + } + + /** + * Activate / Deactivate Modules + * @todo put moduels in a separate table. Do not use the fields directly to change state, to ease + * migration + * @param modules 'field' => true|false + */ + public function modules(Array $modules) + { + foreach ($modules as $module => $value) { + $this->{$module.'Module'} = $value; + } + } + + public function isModuleActive(string $module): bool + { + return $this->{$module.'Module'}; + } + + public function areModulesActive(array $modules): bool + { + $areActive = true; + foreach ($modules as $module) + { + if (!$this->{$module.'Module'} === true) { + $areActive = false; + } + } + return $areActive; + } + + public function hasAccessToModule(string $module): bool + { + switch ($module) + { + case 'dp': return ($this->dpRights > 0) ? true : false; + case 'tp': return ($this->tpRights > 1) ? true : false; + default: return $this->isModuleActive($module); + } + } +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/Person.php b/src/DomainObjects/Person.php new file mode 100644 index 0000000..066047d --- /dev/null +++ b/src/DomainObjects/Person.php @@ -0,0 +1,64 @@ + should be replaced by new column gender + * @property ValueObjects\Varchar $suffix (titel) + * @property ValueObjects\Varchar $firstname (vname) + * @property ValueObjects\Varchar $lastname (nname) + * @property ValueObjects\Varchar $other (zusatz) + * @property ValueObjects\Varchar $remark (bemerkung) + * @property Address $address Address object + */ +class Person extends DomainObject +{ + + protected function fields(array ...$superFields): array + { + return parent::fields([ + 'id' => IntKey::class, + 'title' => Varchar::class, // anrede -> should be replaced by new column gender + 'suffix' => Varchar::class, // titel + 'firstname' => Varchar::class, + 'lastname' => Varchar::class, + 'other' => Varchar::class, // zusatz + 'remark' => Varchar::class, + + 'address' => Address::class, + ], ...$superFields); + } + + public function __get(string $name) + { + switch ($name) + { + case 'fullName': + return $this->fullName(); + } + return parent::__get($name); + } + + // fullName() {{{ + /** + * A convenience function to get the full Clients name composed as `lastname, firstname` + * @return string The full name + * @todo add a parameter to customize the formatting/composition + */ + public function fullName() + { + return new Varchar($this->lastname .', '. $this->firstname); + } // }}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/Spitex.php b/src/DomainObjects/Spitex.php new file mode 100644 index 0000000..bf0992e --- /dev/null +++ b/src/DomainObjects/Spitex.php @@ -0,0 +1,113 @@ + IntKey::class, + 'logo' => Image::class, + 'name' => Varchar::class, + 'gln' => EAN13::class, + 'billcare' => Integer::class, + 'address' => Address::class, + + // Modules - todo: normalize in additional table + 'bcModule' => Integer::class, + 'xmlModule' => Boolean::class, + 'ekarusModule' => Boolean::class, + 'popModule' => Boolean::class, + 'qrModule' => Boolean::class, + 'dpModule' => Boolean::class, + 'tpModule' => Boolean::class, + ], ...$superFields); + } + + public function __get(string $field) + { + switch ($field) { + case 'isSpitex': return $this->isSpitex(); + case 'logo': return $this->logo(); + } + return parent::__get($field); + } + + public function name() + { + return $this->name; + } + + public function logo() + { + $logo = $this->data['logo']; + + if (! $logo->isReadable()) { + $logo->in('logo_leer/logo_leer.jpg'); + } + return $logo; + } + + public function isSpitex(): bool + { + return true; + } + + /** + * Activate / Deactivate Modules + * @todo put moduels in a separate table. Do not use the fields directly to change state, to ease + * migration + * @param modules 'field' => true|false + */ + public function modules(Array $modules) + { + foreach ($modules as $module => $value) { + $this->{$module.'Module'} = $value; + } + } + + public function isModuleActive(string $module): bool + { + return $this->{$module.'Module'}; + } + + public function areModulesActive(array $modules): bool + { + $areActive = true; + foreach ($modules as $module) + { + if (!$this->{$module.'Module'} === true) { + $areActive = false; + } + } + return $areActive; + } +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/Token.php b/src/DomainObjects/Token.php new file mode 100644 index 0000000..ff272ed --- /dev/null +++ b/src/DomainObjects/Token.php @@ -0,0 +1,82 @@ +set('idOwner', $data); + $this->markNew(); + $this->createToken(); + } + + $this->log()->debug("Values after instantiation", $this->data); + } + + protected function fields(array ...$superFields): array + { + return parent::fields([ + 'id' => IntKey::class, + 'idOwner' => IntKey::class, + 'owner' => Owner::class, + 'string' => Varchar::class, + 'creationTime' => DateTime::class, + ], ...$superFields); + } + + /** + * Returns a random generated token consisting of letters and numbers + * + * @param int $length The character length of the token + * @return string The token + */ + public function createToken(int $length = 64): string + { + $this->string = bin2hex(random_bytes($length/2)); + $this->creationTime = new \DateTimeImmutable(); + return $this->string; + } + + /** + * Checks if the token is still valid + * + * @return true if creationTime not older than 14 days + */ + public function good(): bool + { + return ($this->creationTime->value()->diff(new \DateTimeImmutable())->days < 14); + } + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/Validation/AbstractValidator.php b/src/DomainObjects/Validation/AbstractValidator.php new file mode 100644 index 0000000..7e4bcf9 --- /dev/null +++ b/src/DomainObjects/Validation/AbstractValidator.php @@ -0,0 +1,181 @@ +validators; + } // }}} + + // setValidators() {{{ + /** + * Sets the validator functions. + */ + protected function setValidators($tests) + { + $parentClass = get_parent_class($this); + $parentTests = []; + + if ($parentClass !== false && $parentClass !== AbstractValidator::class) + { + $parentTests = $parentClass::getInstance()->getValidators(); + } + $this->validators = array_merge($parentTests, $tests); + } // }}} + + // addValidator() {{{ + /** + * Dynamically add a validator function + * @param string $field The DomainObject Field to validate + * @param callable $validator The Validator function to use + * @param string $validatorIndex The internal index used to store the validator function. Use this to + * remove or overwrite the validator + */ + public function addValidator(string $field, callable $validator, ?string $validatorIndex=null) + { + if (!isset($this->validators[$field])) $this->validators[$field] = []; + $this->validators[$field][$validatorIndex] = $validator; + } // }}} + + // removeValidator() {{{ + /** + * Dynamiocally remove a validator function + */ + public function removeValidator(string $field, $validatorIndex=null) + { + if (isset($validatorIndex)) + { + unset($this->validators[$field][$validatorIndex]); + } else + { + unset($this->validators[$field]); + } + } // }}} + + // getInstance() {{{ + /** + * Returnst the instances of the validator, instantiates a new one if there isn't one already. + * @return Validator + */ + public static function getInstance(): Validator + { + if (!isset(static::$instance)) { + static::$instance = new static(); + } + + assert( + array_column( + (new \ReflectionClass(static::$instance)) + ->getProperties(\ReflectionProperty::IS_STATIC | \ReflectionProperty::IS_PROTECTED), + 'class', 'name' + )['instance'] === get_class(static::$instance), + new \Exception('$instance has to be redeclared in '.get_class(static::$instance)) + ); + + return static::$instance; + } // }}} + + // validate() {{{ + /** + * Validates a DomainObject. + * @param DomainObject $domainObject + * @return ResultCollection|bool returns true if validations passed a ResultCollection otherwise + */ + public function validate(Validatable $domainObject) + { + $this->loadDefaultValidators($domainObject); + + $rc = new ResultCollection((new \ReflectionClass($domainObject))->getShortName()); + $passed = true; + + // Validate properties + foreach ($this->validators as $col => $validators) + { + if (!isset($domainObject->$col)) { + $this->log()->notice('DomainObject '.get_class($domainObject)." has no field '$col'"); + continue; + } + try { + $valueObject = $domainObject->getValueObjectOf($col); + foreach ($validators as $test) + { + if (true !== ($result = $test($valueObject))) { + $rc->$col = $result; + $passed = false; + } + } + } + catch (\OutOfRangeException $e) { + self::log()->notice('Field not set', ['name' => $col]); + } + } + + return $passed ?: $rc; + } // }}} + + // loadDefaultValidators() {{{ + /** + * load vaildators defined in ValueObjects. + * Defined as Default and as such meaning always used. An example is the EAN13 ValueObject + * which always validates checksum and length + */ + private function loadDefaultValidators(DomainObject $domainObject) + { + if (! $this->validatorsLoaded) + { + foreach ($domainObject->dataTypes() as $col => $dt) + { + if (is_subclass_of($dt, Value::class) && !empty($validators = ($dt)::defaultValidators())) + { + if (!isset($this->validators[$col])) $this->validators[$col] = []; + + $this->validators[$col] = array_merge($this->validators[$col], $validators); + } + } + self::log()->debug('Registered Validators', $this->validators); + + $this->validatorsLoaded = true; + } + } // }}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/Validation/AddressValidator.php b/src/DomainObjects/Validation/AddressValidator.php new file mode 100644 index 0000000..55526a5 --- /dev/null +++ b/src/DomainObjects/Validation/AddressValidator.php @@ -0,0 +1,18 @@ +validators = [ + 'id' => [DataType::required()], + 'email' => [DataType::notBlank()], + ]; + } +} diff --git a/src/DomainObjects/Validation/ClientValidator.php b/src/DomainObjects/Validation/ClientValidator.php new file mode 100644 index 0000000..ce46650 --- /dev/null +++ b/src/DomainObjects/Validation/ClientValidator.php @@ -0,0 +1,29 @@ +setValidators([ + 'id' => [DataType::required()], + 'birthday' => [DataType::required()], + 'ahv' => [DataType::required(), /* DataType::unique() */], + 'childCount' => [self::min(0)], // from Number trait + 'address' => [[AddressValidator::getInstance(), 'validate']], + 'cardId' => [DataType::ifExists(DataType::length(20)), Varchar::isDecimal()], + 'cardId' => [DataType::notBlank()], + ]); + } +} diff --git a/src/DomainObjects/Validation/Error.php b/src/DomainObjects/Validation/Error.php new file mode 100644 index 0000000..b5abd41 --- /dev/null +++ b/src/DomainObjects/Validation/Error.php @@ -0,0 +1,10 @@ +validators = [ + 'reveiver' => [DataType::required(), DataType::length(2)], + 'id_client' => [DataType::relation('client')], + 'id_owner' => [DataType::relation('owner', 'spitex')], + // @Deprecated: 'id_vers' => [DataType::relation('organisation')], + 'id_vers' => [DataType::notNull()], + 'vers_nr' => [], + 'id_arzt' => [DataType::relation('arzt')], + 'reason' => [], + 'status' => [], + 'id_kanton' => [DataType::relation('taxwert')], + 'client' => [DataType::relation('id_klient')], + 'biller' => [DataType::relation('id_owner')], + 'insurance' => [DataType::relation('id_vers')], + 'referrer' => [DataType::relation('id_arzt')], + 'schreiben' => [], + 'gesetz' => [DataType::unechterelation('tarif', 'taxwert')], + 'id_rechempfänger' => [DataType::relation('id', 'person')], + 'id_kt' => [DataType::relation('organisation')], + 'id_kt_2' => [DataType::relation('organisation')], + 'sr' => [DataType::relation('sammelrechnung')], + 'id_zk' => [DataType::relation('zusatzkosten')], + ]; + } +} diff --git a/src/DomainObjects/Validation/OrganisationValidator.php b/src/DomainObjects/Validation/OrganisationValidator.php new file mode 100644 index 0000000..bba9e6d --- /dev/null +++ b/src/DomainObjects/Validation/OrganisationValidator.php @@ -0,0 +1,14 @@ +objectTests = [ + ]; + } +} diff --git a/src/DomainObjects/Validation/OwnerValidator.php b/src/DomainObjects/Validation/OwnerValidator.php new file mode 100644 index 0000000..b9af600 --- /dev/null +++ b/src/DomainObjects/Validation/OwnerValidator.php @@ -0,0 +1,23 @@ +setValidators([ + 'password' => [ + DataType::required(), + ], + ]); + } +} diff --git a/src/DomainObjects/Validation/PersonValidator.php b/src/DomainObjects/Validation/PersonValidator.php new file mode 100644 index 0000000..5b319fa --- /dev/null +++ b/src/DomainObjects/Validation/PersonValidator.php @@ -0,0 +1,21 @@ +setValidators([ + 'id' => [DataType::required()], + 'firstname' => [DataType::notBlank()], + 'lastname' => [DataType::notBlank()], + 'address' => [DataType::required()], + ]); + } +} + diff --git a/src/DomainObjects/Validation/Result.php b/src/DomainObjects/Validation/Result.php new file mode 100644 index 0000000..bb15918 --- /dev/null +++ b/src/DomainObjects/Validation/Result.php @@ -0,0 +1,25 @@ +msgCode = $msgCode; + $this->value = $value ?? ''; + $this->hint = $hint ?? ''; + } +} diff --git a/src/DomainObjects/Validation/ResultCollection.php b/src/DomainObjects/Validation/ResultCollection.php new file mode 100644 index 0000000..146ead9 --- /dev/null +++ b/src/DomainObjects/Validation/ResultCollection.php @@ -0,0 +1,63 @@ +DMO = strtolower($DMO); + } + + public function getIterator(): \ArrayIterator + { + return (new \ArrayObject($this->results))->getIterator(); + } + + public function __get(string $fieldName) + { + switch ($fieldName) + { + case 'DMO': + return $this->DMO; + } + + $result = []; + if (isset($this->results[$fieldName])) { + $result = $this->results[$fieldName]; + } + + return $result; + } + + /** + * @param string $fildName + * @param Result|ResultCollection $fieldResult + */ + public function __set(string $fieldName, $fieldResult) + { + $this->results[$fieldName][] = $fieldResult; + } + + public function getResults(): Array + { + return $this->results; + } + + public function isInResults( string $field ): bool + { + return array_key_exists( $field, $this->results ); + } + + public function resultsAreNtCaseIds():bool + { + return !array_key_exists('insuredId', $this->results) && !array_key_exists('caseId', $this->results); + } +} diff --git a/src/DomainObjects/Validation/TokenValidator.php b/src/DomainObjects/Validation/TokenValidator.php new file mode 100644 index 0000000..9ea9e4f --- /dev/null +++ b/src/DomainObjects/Validation/TokenValidator.php @@ -0,0 +1,24 @@ +setValidators([ + 'ownerId' => [DataType::required()], + 'owner' => [DataType::required()], + 'token' => [ + DataType::required(), + DataType::length(64), + Varchar::matchRegEx('/^[A-Za-z0-9]+$/') + ], + 'creationDate' => [DataType::required()], + ]); + } +} diff --git a/src/DomainObjects/Validation/Validatable.php b/src/DomainObjects/Validation/Validatable.php new file mode 100644 index 0000000..45506cf --- /dev/null +++ b/src/DomainObjects/Validation/Validatable.php @@ -0,0 +1,17 @@ +value = trim( str_replace( '.', '', (string)$value ) ); + } // }}} + + // out() {{{ + /** + * returns the value with the AHV typical dots on the correct places + */ + public function out(): string + { + $outValue = $this->value; + if ($outValue) + { + foreach ([ 3, 8, 13 ] as $dotPos) + { + $outValue = substr( $outValue, 0, $dotPos ) .'.'. substr( $outValue, $dotPos ); + } + } + return $outValue; + } // }}}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Boolean.php b/src/DomainObjects/ValueObjects/Boolean.php new file mode 100644 index 0000000..1397d7e --- /dev/null +++ b/src/DomainObjects/ValueObjects/Boolean.php @@ -0,0 +1,54 @@ +value = (bool)$value; + } // }}} + + // __toString() {{{ + /** + * converts true to 1 and false to 0 + * @return string + */ + public function __toString() + { + return ($this->value) ? '1' : '0'; + } // }}} + + // equals() {{{ + /** + * Compares the boolean value + * @param bool|\VeruA\DomainObjects\Boolean the value to compare + */ + public function equals( $compare ) + { + if ($compare instanceof Boolean) $compare = $compare->value(); + return ($this->value === $compare); + } // }}} + + // out() {{{ + /** + * Returns the $value as integer for db queries + * @return int + */ + public function out(): int + { + return (int)$this->value; + } // }}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Complex.php b/src/DomainObjects/ValueObjects/Complex.php new file mode 100644 index 0000000..e28f80d --- /dev/null +++ b/src/DomainObjects/ValueObjects/Complex.php @@ -0,0 +1,12 @@ +in($value); + }// }}} + + // __toString() {{{ + /** + * Make sure to overwrite this method in the implementations + * @return string The string representation of the DataType + */ + public function __toString() + { + return (string)$this->out(); + }// }}} + + // equals() {{{ + /** + * Compares the internal value with the $compare argument + * + * Remember that you get a DomainObject when using the DomainObjects values, thus comparison can have + * undesired results, use the equals method to compare ValueObjects
+ * This has to be overwritten in the descendants to get the desired comparison behaviour + * @param DataType|mixed $compare The value to be compared + */ + public function equals( $compare ) + { + if ($compare instanceof DataType) $compare = $compare->value(); + return ($this->value == $compare); + } // }}} + + // in() {{{ + /** + * Stores the $value applying the input filters + * + * This method should be overwritten in the concrete implementation + * @todo Filters are yet to be implemented! + * @param mixed The Value to be stored + */ + public function in( $value ) + { + $this->value = $value; + } // }}} + + // out() {{{ + /** + * Returns the $value applying the output filters + * + * This method should be overwritten in the concrete implementation + * @todo Filters are yet to be implemented! + */ + public function out(): mixed + { + return (string)$this->value; + } // }}} + + // value() {{{ + /** + * Returns the internal value + */ + public function value() + { + return $this->value; + } // }}} + + // defaultValidators() {{{ + /** + * Overwrite this message if a DataType needs validation methods that are always applied + */ + public static function defaultValidators(): array + { + return []; + } + // }}} + + // validation methods {{{ + /** + * @return callable Returns a predicate that tests for the existence of it's argument. + */ + public static function required(): callable + { + return function (?Value $valueObject) + { + return isset($valueObject) ? true : new Error( + $msgCode = 'VALIDATION_REQUIRED', + ); + }; + } + + /** + * NotBlank + * @return callable + */ + public static function notBlank(): callable + { + return function (Value $valueObject) { + return ($valueObject->value()) ? true : new Error('VALIDATION_NOTBLANK'); + }; + } + + /** + * NotNull + * @return callable + */ + public static function notNull(): callable + { + return function (Value $valueObject) { + return ($valueObject->value() !== null) ? true : new Error('VALIDATION_NOTNULL'); + }; + } + + /** + * @return callable Returns a predicate that tests a value using $test if it exists. + */ + public static function ifExists(callable $test) : callable + { + return function (?Value $valueObject) use ($test) + { + return isset($valueObject) ? $test($valueObject) : true; + }; + } + + /** + * @return callable Returns a predicate that tests if its argument is of length $n. + */ + public static function length(int $n): callable + { + // assert($n >= 0, 'Strings can only be tested for positive lengths!'); + + return function(Value $valueObject) use ($n) + { + $value = (string) $valueObject->value(); + return (strlen($value) === $n) ? true : new Error( + $msgCode = 'VALIDATION_LENGTH', + $value = (string) $value, + $hint = (string) $n, + ); + }; + } + + /** + * @return callable Returns a predicate that tests if its argument is of length $n or greater. + */ + public static function minLength(int $n) + { + assert($n >= 0, 'Strings can only be tested for positive lengths!'); + + return function(Value $valueObject) use ($n) + { + $value = (string) $valueObject->value(); + return (strlen($value) >= $n) ? true : new Error( + $msgCode = 'VALIDATION_LENGTH', + $value = (string) $value, + $hint = (string) $n, + ); + }; + } + + /** + * @return callable Returns a predicate that tests if its argument is of length $n or less. + */ + public static function maxLength(int $n) + { + assert($n >= 0, 'Strings can only be tested for positive lengths!'); + + return function(Value $valueObject) use ($n) + { + $value = (string) $valueObject->value(); + return (strlen($value) <= $n) ? true : new Error( + $msgCode = 'VALIDATION_LENGTH', + $value = (string) $value, + $hint = (string) $n, + ); + }; + } + + /** + * not implemented - remove? + * @return callable Returns a predicate that tests if it's argument is unique in it's environment. + */ + public static function unique() + { + // TODO: Not implemented yet! + return function ($uniqueValue) + { + return new Error(); + }; + } + + /** + * @return callable Returns a predicate that test if it's argument is one of several elements. + */ + public static function oneOf($lst) + { + return function (Value $valueObject) use ($lst) + { + $value = $valueObject->value(); + return in_array($value, $lst, true) ? true : new Error( + $msgCode = 'INVALID_VALUE', + $value = (string) $value, + $hint = implode("|", $lst), + ); + }; + } + // }}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Date.php b/src/DomainObjects/ValueObjects/Date.php new file mode 100644 index 0000000..db7cad0 --- /dev/null +++ b/src/DomainObjects/ValueObjects/Date.php @@ -0,0 +1,72 @@ +value = $value; + } else { + $this->value = new \DateTime($value); + } + } + } // }}} + + // out() {{{ + public function out(): string + { + return (isset($this->value)) ? $this->value->format("Y-m-d") : ''; + } // }}} + + // __toString() {{{ + /** + * + */ + public function __toString() + { + return (isset($this->value)) ? $this->value->format("d.m.Y") : ''; + } // }}} + + // __get() {{{ + /** + * + */ + public function __get($name) + { + switch ($name) + { + case 'timestamp': + return $this->value()->getTimestamp(); + } + } // }}} + + // isComplex() {{{ + public function isComplex(): bool { + return true; + } // }}} + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/DateTime.php b/src/DomainObjects/ValueObjects/DateTime.php new file mode 100644 index 0000000..4390d19 --- /dev/null +++ b/src/DomainObjects/ValueObjects/DateTime.php @@ -0,0 +1,72 @@ +value = $value; + } else { + $this->value = new \DateTime($value); + } + } + } // }}} + + // __toString() {{{ + /** + * + */ + public function __toString() + { + return (isset($this->value)) ? $this->value->format("d.m.Y H:i:s") : ''; + } // }}} + + // out() {{{ + public function out(): string + { + return (isset($this->value)) ? $this->value->format("Y-m-d H:i:s") : ''; + } // }}} + + // __get() {{{ + /** + * + */ + public function __get($name) + { + switch ($name) + { + case 'timestamp': + return $this->value()->getTimestamp(); + } + } // }}} + + // isComplex() {{{ + public function isComplex(): bool { + return true; + } // }}} + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Decimal.php b/src/DomainObjects/ValueObjects/Decimal.php new file mode 100644 index 0000000..ab3d017 --- /dev/null +++ b/src/DomainObjects/ValueObjects/Decimal.php @@ -0,0 +1,33 @@ +value = (double)$value; + } // }}} + + // equals() {{{ + /** + * Compares the Float value + * @param float|\VeruA\DomainObjects\Float the value to compare + */ + public function equals( $compare ) + { + if (! $compare instanceof Float) $compare = new Float($compare); + return ($this->value === $compare->value()); + } // }}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/EAN13.php b/src/DomainObjects/ValueObjects/EAN13.php new file mode 100644 index 0000000..24fde52 --- /dev/null +++ b/src/DomainObjects/ValueObjects/EAN13.php @@ -0,0 +1,112 @@ +value() === $compare->value()); + } // }}} + + // __toString() {{{ + /** + * @return string The value as is + */ + public function __toString(): string + { + $outValue = (string)$this->value; + if ($outValue) + { + foreach ([ 3, 8, 13 ] as $dotPos) + { + $outValue = substr( $outValue, 0, $dotPos ) .'.'. substr( $outValue, $dotPos ); + } + } + return $outValue; + } // }}} + + // in() {{{ + /** + * Converts the $value stripping of all dots + */ + public function in( $value ) + { + $this->value = (isset($value)) ? trim( str_replace( '.', '', (string)$value ) ) : ''; + } // }}} + + // out() {{{ + /** + * returns the value with the EAN13 typical dots on the correct places + */ + public function out(): string + { + return $this->value; + } // }}}} + + // defaultValidators() {{{ + public static function defaultValidators(): array + { + return [ + DataType::length(13), + EAN13::checkdigit() + ]; + } + // }}} + + // validation methods {{{ + public static function checkdigit() + { + return function(EAN13 $ean) + { + $value = $ean->value(); + + if (!is_numeric($value)) { + return new Error( + $msgCode = "VALIDATION_EAN13_NONDIGITS", + $value, + $hint = '' + ); + } + + $lastPos = strlen( $value ) - 1; + $sum = 0; $weight = 3; + + for ( $i = $lastPos - 1; $i >= 0 ; $i-- ) { + $sum += $value[ $i ] * $weight; + $weight = ($weight == 3) ? 1 : 3; + } + if ( (ceil($sum / 10) * 10) - $sum == $value[ $lastPos ] ) { // checkdigit matches + return true; + } + else { + return new Error( + $msgCode = "VALIDATION_EAN13_CHECKDIGIT", + $value, + $hint = "" + ); + } + }; + } + // }}} + + // isComplex() {{{ + public function isComplex(): bool { + return true; + } // }}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Email.php b/src/DomainObjects/ValueObjects/Email.php new file mode 100644 index 0000000..7bc721b --- /dev/null +++ b/src/DomainObjects/ValueObjects/Email.php @@ -0,0 +1,14 @@ +directory . '/'; + $path = realpath($path) ? realpath($path) . '/' : ''; + $this->value = new \SPLFileInfo($path . $value); + $this->log()->debug($this); + + } + + /** + * Magic getter to retrieve file properties. + * + * @param string $name The name of the property. + * @return mixed + */ + public function __get($name) + { + switch ($name) + { + case 'readable': return $this->isReadable(); + case 'realPath': return $this->value()->getRealPath(); + case 'relativePath': return $this->computeRelativePath(); + } + } + + /** + * Checks if the file is readable. + * + * @return bool True if the file is readable, false otherwise. + */ + public function isReadable(): bool + { + return $this->value()->isReadable(); + } + + /** + * Indicates if the object represents a complex type. + * + * @return bool Always returns true for this class. + */ + public function isComplex(): bool + { + return true; + } + + /** + * Retrieves the basename of the file without any directory components + * + * @return string The basename of the file. + */ + public function out(): string + { + return $this->value()->getBasename(); + } + + /** + * Computes the relative path of the file from the document root directory. + * + * This method calculates the relative path by removing the root directory's + * real path from the file's absolute path. + * + * @return string The relative path of the file. + */ + private function computeRelativePath(): string + { + return str_replace(realpath(__DIR__ . '/../../../'), '', $this->realPath); + } +} diff --git a/src/DomainObjects/ValueObjects/Image.php b/src/DomainObjects/ValueObjects/Image.php new file mode 100644 index 0000000..8ee3ede --- /dev/null +++ b/src/DomainObjects/ValueObjects/Image.php @@ -0,0 +1,22 @@ +value = null; + return; + } + if (!is_numeric($value)) { + throw new \InvalidArgumentException("Argument has to be parseable to int: $value"); + } + + $this->value = (int)$value; + } // }}} + + // equals() {{{ + /** + * Compares the Integer value + * @param int|\VeruA\DomainObjects\Integer the value to compare + */ + public function equals( $compare ) + { + if (! $compare instanceof Integer) $compare = new Integer($compare); + return ($this->value === $compare->value()); + } // }}} + + // toIndex() {{{ + public function toIndex(): int + { + return $this->value(); + } // }}} + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Integer.php b/src/DomainObjects/ValueObjects/Integer.php new file mode 100644 index 0000000..eef3be9 --- /dev/null +++ b/src/DomainObjects/ValueObjects/Integer.php @@ -0,0 +1,50 @@ +value = null; + return; + } + if (!is_numeric($value)) { + throw new \InvalidArgumentException("Argument has to be parseable to int: $value"); + } + + $this->value = (int)$value; + } // }}} + + // equals() {{{ + /** + * Compares the Integer value + * @param int|\VeruA\DomainObjects\Integer the value to compare + */ + public function equals( $compare ) + { + if (! $compare instanceof Integer) $compare = new Integer($compare); + return ($this->value === $compare->value()); + } // }}} + + // out() {{{ + public function out(): int + { + return (int)$this->value; + } // }}} +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Key.php b/src/DomainObjects/ValueObjects/Key.php new file mode 100644 index 0000000..4b49354 --- /dev/null +++ b/src/DomainObjects/ValueObjects/Key.php @@ -0,0 +1,11 @@ +value() <= $n) ? true : new Error( + $msgCode = 'INVALID_VALUE', + $value = (string) $x, + $hint = (string) $n, + ); + }; + } + + /** + * @return callable Returns a test which determines if a value is above a given threshold. + */ + public static function min($n): callable + { + return function ($x) use ($n) + { + return ($x->value() >= $n) ? true : new Error( + $msgCode = 'INVALID_VALUE', + $value = (string) $x, + $hint = (string) $n, + ); + }; + } +} + diff --git a/src/DomainObjects/ValueObjects/Password.php b/src/DomainObjects/ValueObjects/Password.php new file mode 100644 index 0000000..4c24619 --- /dev/null +++ b/src/DomainObjects/ValueObjects/Password.php @@ -0,0 +1,51 @@ +in($this->hash($password)); + } + + /** + * Takes the clearttext password and returnes the hashed string + * + * @todo Replace all usages of password hashing with this method, and update to phps + * password_hash() function + * + * @param string $password The cleartext password to be hashed + * @return bool|Error True if validatiion passed Error with the errors + */ + public function hash(string $password): string + { + return md5($password); + } + + public static function validate(string $password) + { + return DataType::minLength(8)(new DataType($password)); + } + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Phone.php b/src/DomainObjects/ValueObjects/Phone.php new file mode 100644 index 0000000..51d2b65 --- /dev/null +++ b/src/DomainObjects/ValueObjects/Phone.php @@ -0,0 +1,14 @@ +value = (string)trim($value); + preg_match('/^(?P(\d*\D+ (\d* )?)+)(?P\d+.*)$/', $this->value, $match); + $this->name = $match['name']; + $this->number = $match['number']; + } // }}} + + public function __get(string $field): string + { + switch ($field) + { + case 'name': + return $this->name; + case 'number': + return $this->number; + default: + throw new \OutOfRangeException('A Street Object is composed of only street->name and street->number'); + } + } + + public function __set(string $field, string $value) + { + switch ($field) + { + case 'name': + $this->name = trim($value); + break; + case 'number': + $this->number = trim($value); + break; + default: + throw new \OutOfRangeException('A Street Object is composed of only street->name and street->number'); + } + $this->value = "$this->name $this->value"; + } + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/Text.php b/src/DomainObjects/ValueObjects/Text.php new file mode 100644 index 0000000..81aa016 --- /dev/null +++ b/src/DomainObjects/ValueObjects/Text.php @@ -0,0 +1,24 @@ +value = (string)$value; + } // }}} + + public function like( string $text ) + { + + } +} \ No newline at end of file diff --git a/src/DomainObjects/ValueObjects/URL.php b/src/DomainObjects/ValueObjects/URL.php new file mode 100644 index 0000000..d032421 --- /dev/null +++ b/src/DomainObjects/ValueObjects/URL.php @@ -0,0 +1,14 @@ +value = (string)$value; + } // }}} + + // equals() {{{ + /** + * Compares the String value + * @param string|\VeruA\DomainObjects\Varchar the value to compare + */ + public function equals( $compare ) + { + if (! $compare instanceof Varchar) $compare = new String($compare); + return ($this->value === $compare->value()); + } // }}} + + /** + * Retuns a test tesing if its input matches a regular expression. + * @param string $regex The regex to check against. + * {@see https://www.php.net/manual/en/reference.pcre.pattern.syntax.php PCRE Regex Syntax} + * @param string $translationId The translation id for the Error message from `translations/errors_[DE|FR].xml` + * @return callable + */ + public static function matchRegEx(string $regex, string $translationId='INVALID_VALUE'): callable + { + return function (Value $value) use ($regex, $translationId) + { + return (preg_match($regex, (string) $value) === 1) ? true : new Error( + $msgCode = $translationId, + $value = (string) $value, + $hint = $regex, + ); + }; + } + + /** + * Returns a test testing if a string is a decimal number. + */ + public static function isDecimal(): callable + { + return function (Value $valueObject) + { + $value = (string) $valueObject->value(); + // TODO: Rewrite without using a regex! + return preg_match('/^(\+|-)?[0-9]+(\.[0-9]+)?$/', (string) $value) ? true : new Error( + $msgCode = 'INVALID_VALUE', + $value = (string) $value, + $hint = 'Number expected!', + ); + }; + } +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/DomainObjects/ValueObjects/ZSR.php b/src/DomainObjects/ValueObjects/ZSR.php new file mode 100644 index 0000000..2518783 --- /dev/null +++ b/src/DomainObjects/ValueObjects/ZSR.php @@ -0,0 +1,40 @@ +value = (isset($value)) ? trim( str_replace( ['.', ' '], '', $value ) ) : ''; + } // }}} + + // defaultValidators() {{{ + public static function defaultValidators(): array + { + return [ + DataType::length(7), + Varchar::matchRegex('/^[A-Z][0-9]{6}$/', 'VALIDATION_ZSR_FORMAT'), + ]; + } + // }}} + + // isComplex() {{{ + public function isComplex(): bool { + return true; + } // }}} + +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/ diff --git a/src/Log.php b/src/Log.php new file mode 100644 index 0000000..99e432f --- /dev/null +++ b/src/Log.php @@ -0,0 +1,48 @@ +log()->debug('your log message'); + * // ... + * } + * } + * ``` + */ +trait Log +{ + private static $logger; + + private static function log($file = 'debug'): Logger + { + if (! isset(self::$logger)) + { + self::$logger = new Logger(get_called_class()); + self::$logger->pushHandler(new StreamHandler('debug.log')); + } + return self::$logger; + } +} + +/* jEdit buffer local properties {{{ + * :folding=explicit:collapseFolds=1: + }}}*/