initial commit

This commit is contained in:
norb 2025-12-17 21:38:05 +01:00
commit 0db3e92ee6
Signed by: norb
GPG key ID: 07FD40171026409B
59 changed files with 3384 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.log
vendor/
.directory

18
composer.json Executable file
View file

@ -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"
}
}

172
composer.lock generated Normal file
View file

@ -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"
}

85
index.php Normal file
View file

@ -0,0 +1,85 @@
<?php
use \VeruA\DomainObjects\{
DomainObject,
Owner,
Client,
Address,
ValueObjects\Date,
};
use \VeruA\DomainObjects\Validation\{
OwnerValidator,
ClientValidator,
ResultCollection,
};
const BASE_PATH = __DIR__;
require __DIR__ . '/vendor/autoload.php';
// owner
$owner = new Owner([
'id' => 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 "<h$level>".get_class($dmo)."</h$level>\n";
echo "<dl style=\"margin-left: {$level}em\">\n";
foreach ($dmo as $field => $value)
{
// display missing if ValueObject->__toString() is empty
if ((string)$value === '') $value = '<i>__ missing __</i>';
// output fieldname and value
echo "<dt style=\"float: left\">$field:</dt><dd style=\"margin-left: 10em\">$value</dd>\n";
// recursive print DMOs .. will obviously not work on self reference ..
if ($value instanceOf DomainObject)
{
$level++;
printDMO($value, $level);
$level--;
}
}
echo "</dl>\n";
}
function dumpValidationErrors(bool|ResultCollection $validationResult)
{
if ($validationResult !== true)
{
echo "<hr><strong>Validation Errors:</strong>\n<pre>";
var_dump($validationResult);
echo '</pre><hr>';
}
}

27
readme.md Normal file
View file

@ -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.

View file

@ -0,0 +1,49 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\DomainObjects\ValueObjects\IntKey;
use VeruA\DomainObjects\ValueObjects\Varchar;
use VeruA\DomainObjects\ValueObjects\Email;
use VeruA\DomainObjects\ValueObjects\Phone;
use VeruA\DomainObjects\ValueObjects\URL;
/**
* An Address
*
* @property ValueObjects\Integer $id The pk of the client
* @property ValueObjects\Varchar $street (str)
* @property ValueObjects\Varchar $zip (plz)
* @property ValueObjects\Varchar $city (ort)
* @property ValueObjects\Varchar $pobox (pfach)
* @property ValueObjects\Email $email
* @property ValueObjects\Phone $phone (tel)
* @property ValueObjects\Phone $cellPhone (natel)
* @property ValueObjects\Phone $officePhone (tel_geschaeft
* @property ValueObjects\Phone $fax
* @property ValueObjects\URL $website (webseite)
*/
class Address extends DomainObject
{
protected function fields(array ...$superFields): array
{
return parent::fields([
'id' => 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:
}}}*/

View file

@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
/**
* The Business interface represents the common parts of the Owner and Spitex DMO
*
* Implemented by {@see Owner} and {@see Spitex}
*
* Please note that it is mandatory to implement all listed properties in the implementing
* {@see Business::__get()} method
* as documented below:
*
* * **$name** : {@see ValueObjects\Varchar} <br> *The name of the Spitex or Owner in charge*<br>
* * **$gln** : {@see ValueObjects\EAN13} <br> *GLN-Number of the Business*<br>
* * **$isSpitex** : {@see ValueObjects\Boolean} <br> *Is it a Spitex Business*<br>
* * **$billcare** : {@see ValueObjects\Integer} <br> *Does Business use BillCare* `0 => false, 2 => MF`<br>
* * **$logo** : {@see ValueObjects\Varchar} <br> *The logo of the Spitex or Owner in charge*<br>
*
* @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;
}

View file

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\DomainObjects\DomainObject;
use VeruA\DomainObjects\Owner;
use VeruA\DomainObjects\ValueObjects\{IntKey, Varchar, Boolean, Integer};
class Canton extends DomainObject {
protected function fields(array ...$superFields): array
{
return parent::fields([
'id' => 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);
}
}

View file

@ -0,0 +1,64 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\Log;
use VeruA\DomainObjects\ValueObjects\DataType;
use VeruA\DomainObjects\ValueObjects\Integer;
use VeruA\DomainObjects\ValueObjects\IntKey;
use VeruA\DomainObjects\ValueObjects\Varchar;
use VeruA\DomainObjects\ValueObjects\Date;
use VeruA\DomainObjects\ValueObjects\Boolean;
use VeruA\DomainObjects\ValueObjects\EAN13;
use VeruA\DomainObjects\Organisation;
/**
* A DomainObject representation of Client (Klient)
*
* Includes the tables <pre>klient, personen, adressen</pre>
* @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:
}}}*/

View file

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
interface DataProvider
{
public function load(DomainObject $dmo): void;
}

View file

@ -0,0 +1,61 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
/**
* A DataSource Singelton to load a DomainObject without a dependency to the actual DataProvider
* A DataProvider only needs to implement the DataProvider Interface. An example of a DataProvider is
* the the MapperRegistry
* @see \VeruA\DataMapper\MapperRegistry
*/
class DataSource
{
/**
* The sole instance of the DataSource
*/
public static DataSource $instance;
/**
* The Dataprovider object which redirects the load call to the apropriate implementation
*/
public DataProvider $provider;
/**
* The private constructor is called by the init method and sets the provider instance
*/
private function __construct(DataProvider $provider)
{
$this->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:
}}}*/

View file

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\Log;
use VeruA\DomainObjects\ValueObjects\IntKey;
use VeruA\DomainObjects\ValueObjects\Varchar;
use VeruA\DomainObjects\ValueObjects\ZSR;
use VeruA\DomainObjects\ValueObjects\EAN13;
/**
* A DomainObject representation of Doctor (Arzt) extending Person
*
* Includes the tables `arzt, personen, adressen, fachr_arzt`
*
* @property ValueObjects\IntKey $id The pk of the Doctor
* @property ValueObjects\IntKey $idPers The pk of the Person
* @property ValueObjects\Varchar $field the field (`fachr`) the doctor is specialized in.
* Comes from the `fachr_arzt` table and is joined in the Mapper
* @property ValueObjects\Varchar $practice The name of the doctor's practice (`praxis`) if any
* @property ValueObjects\ZSR $zsr The zsr number (e.g. `A123456`)
* @property ValueObjects\EAN13 $gln The gln number of the doctor
**/
class Doctor extends Person
{
use Log;
protected function fields(array ...$superFields): array
{
return parent::fields([
'id' => 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:
}}}*/

View file

@ -0,0 +1,405 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\Log;
use VeruA\DomainObjects\Validation\{Validator, Validatable};
use VeruA\DomainObjects\ValueObjects\{Value, Primitive, Complex, DataType, Key};
/**
* A DomainObject reperesents a Basic data entity. e.g. a Client. // {{{
*
* DomainObjects represent mostly a table or several joined tables but are loaded from the database
* through the DataMappers and are independent from the latter
*
* To create a new DomainObject do the following:
* * extend from `DomainObject`
* * overwrite the `fields()` method like so
* ```php
* protected function fields(array ...$superFields): array
* {
* return parent::fields([
* // you need a field calld `id` with a type that implements the `Key` interface
* 'id' => 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:
}}}*/

View file

@ -0,0 +1,53 @@
<?php namespace VeruA\DomainObjects\Filter;
class EncodingFilter
{
private $data;
private $dataEnc;
private $outEenc;
private $inEenc;
// Constructor {{{
/**
*
*/
public function __construct( $data, $outEnc=null, $inEnc=null, $dataEnc=null )
{
$this->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:
}}}*/

View file

@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\Log;
use VeruA\DomainObjects\ValueObjects\Boolean;
use VeruA\DomainObjects\ValueObjects\DateTime;
use VeruA\DomainObjects\ValueObjects\EAN13;
use VeruA\DomainObjects\ValueObjects\IntKey;
use VeruA\DomainObjects\ValueObjects\Integer;
use VeruA\DomainObjects\ValueObjects\Varchar;
/**
*
* Health insurance object
*/
class HealthInsurance extends DomainObject {
protected function fields(array ...$sf): array
{
return parent::fields([
'id' => 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,
}
}

View file

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\DomainObjects\ValueObjects\IntKey;
use VeruA\DomainObjects\ValueObjects\Integer;
use VeruA\DomainObjects\ValueObjects\Varchar;
use VeruA\DomainObjects\ValueObjects\EAN13;
/**
* An Organisation
*
*/
class Organisation extends DomainObject
{
protected function fields(array ...$sf): array
{
return parent::fields([
'id' => 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);
}
}

196
src/DomainObjects/Owner.php Normal file
View file

@ -0,0 +1,196 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\Log;
use VeruA\DomainObjects\ValueObjects\{
IntKey,
Varchar,
Boolean,
EAN13,
Integer,
Image,
Password
};
/**
* A User from the Owner table
*
* @property ValueObjects\Integer $id The pk of the owner
* @property ValueObjects\Image $logo The logo of the owner
* @property ValueObjects\Varchar $firstname The firstname (vname)
* @property ValueObjects\Varchar $lastname The lastname (nname)
* @property ValueObjects\EAN13 $gln Users GLN-Number
* @property ValueObjects\Boolean $admin @deprecated Has user admin rights? use isAdmin()
* @property ValueObjects\Boolean $spitex @deprecated {@see https://rabeweb.planio.de/issues/19089} use isSpitex()
* @property ValueObjects\Varchar $username The username to authenticate (benutzer)
* @property ValueObjects\Password $password The password to authenticate (passwort)
*/
class Owner extends DomainObject implements Business
{
use Log;
private $clientsInCare = [];
protected function fields(array ...$superFields): array
{
return parent::fields([
'id' => 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:
}}}*/

View file

@ -0,0 +1,64 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\DomainObjects\ValueObjects\DataType;
use VeruA\DomainObjects\ValueObjects\IntKey;
use VeruA\DomainObjects\ValueObjects\Integer;
use VeruA\DomainObjects\ValueObjects\Varchar;
/**
* A Person
*
* @property ValueObjects\IntKey $id The pk of the client
* @property ValueObjects\Varchar $title (anrede) -> 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:
}}}*/

View file

@ -0,0 +1,113 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\Log;
use VeruA\DomainObjects\ValueObjects\{
IntKey,
Varchar,
Boolean,
EAN13,
Integer,
Image
};
/**
* A Spitex Business from the spitex table
*
* @property ValueObjects\Integer $id The primary key of the spitex table
* @property ValueObjects\Image $logo The logo-image of the spitex
* @property ValueObjects\Varchar $name The name of the spitex
* @property ValueObjects\EAN13 $gln Spitex GLN-Number
* @property bool $isSpitex always returns true
* @property Address $address adress Object? not implemented in mapper yet
*/
class Spitex extends DomainObject implements Business
{
use Log;
protected function fields(array ...$superFields): array
{
return parent::fields([
'id' => 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:
}}}*/

View file

@ -0,0 +1,82 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects;
use VeruA\DomainObjects\{
DomainObject,
Owner
};
use VeruA\DomainObjects\ValueObjects\{
Key,
IntKey,
Varchar,
DateTime
};
/**
* A Token DomainObject
*
* @property ValueObjects\Intkey $ownerId The pk of the Owner the token belongs to
* @property Owner $owner The Owner of the token
* @property ValueObjects\Varchar $string The token string
* @property ValueObjects\DateTime $creationTime The script or binary
*
* @author norb
*/
class Token extends DomainObject
{
use \VeruA\Log;
public function __construct($data=null)
{
parent::__construct($data);
if ($data instanceof Key)
{
$this->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:
}}}*/

View file

@ -0,0 +1,181 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\Log;
use VeruA\DomainObjects\DomainObject;
use VeruA\DomainObjects\ValueObjects\Value;
use VeruA\DomainObjects\Validation\ResultCollection;
/**
* Validator is a singleton base class for classes validating DomainObjects.
*/
abstract class AbstractValidator implements Validator
{
use Log;
/**
* Needs to be declared in every childclass
* @var callable[] A list of tests used to validate domainObjects dataTypes, indexed by fieldname
*/
protected $validators = [];
/**
* Stores the single instance of Validator.
* @var Validator
* @abstract Needs to be redeclared in every child class
*/
protected static AbstractValidator $instance;
private bool $validatorsLoaded = false;
/**
* Implement constructor in childclasses and assign the validator functions to using setValidators()
* All sub-classes should have protected constructors.
*/
protected abstract function __construct();
// getValidators() {{{
/**
* Returns the validator functions.
*/
public function getValidators()
{
return $this->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:
}}}*/

View file

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\DomainObjects\ValueObjects\DataType;
class AddressValidator extends AbstractValidator
{
protected static AbstractValidator $instance;
protected function __construct()
{
$this->validators = [
'id' => [DataType::required()],
'email' => [DataType::notBlank()],
];
}
}

View file

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\DomainObjects\ValueObjects\{
DataType,
Number,
Varchar
};
use VeruA\DomainObjects\Validation\PersonValidator;
class ClientValidator extends PersonValidator
{
use Number;
protected static AbstractValidator $instance;
protected function __construct()
{
$this->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()],
]);
}
}

View file

@ -0,0 +1,10 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
/**
* Result of an unsuccessful validation.
*/
class Error extends Result
{
}

View file

@ -0,0 +1,37 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\DomainObjects\ValueObjects\DataType;
class InvoiceValidator extends AbstractValidator
{
protected static AbstractValidator $instance;
protected function __construct()
{
$this->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')],
];
}
}

View file

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
class AddressValidator extends AbstractValidator
{
protected static AbstractValidator $instance;
protected function __construct()
{
$this->objectTests = [
];
}
}

View file

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\DomainObjects\ValueObjects\{
DataType,
Number,
Varchar
};
class OwnerValidator extends AbstractValidator
{
protected static AbstractValidator $instance;
protected function __construct()
{
$this->setValidators([
'password' => [
DataType::required(),
],
]);
}
}

View file

@ -0,0 +1,21 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\DomainObjects\ValueObjects\DataType;
class PersonValidator extends AbstractValidator
{
protected static AbstractValidator $instance;
protected function __construct()
{
$this->setValidators([
'id' => [DataType::required()],
'firstname' => [DataType::notBlank()],
'lastname' => [DataType::notBlank()],
'address' => [DataType::required()],
]);
}
}

View file

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
/**
* Result of an unsuccessful validation.
*/
abstract class Result
{
/** @var string Error code, MUST match one of the error codes in translations/errors_XX.xml. */
public $msgCode = '';
/** @var string Erroneous value. */
public $value = '';
/** @var string Possible reason for the error. */
public $hint = '';
public function __construct(string $msgCode, $value=null, ?string $hint=null)
{
$this->msgCode = $msgCode;
$this->value = $value ?? '';
$this->hint = $hint ?? '';
}
}

View file

@ -0,0 +1,63 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
/**
* A collection of validation-results.
*/
class ResultCollection implements \IteratorAggregate
{
/** @var array $reults Maps field-names to a list of validation-results. */
private $results = [];
private $DMO;
public function __construct(string $DMO='') {
$this->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);
}
}

View file

@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\DomainObjects\ValueObjects\{DataType, Number, Varchar};
class TokenValidator extends AbstractValidator
{
protected static AbstractValidator $instance;
protected function __construct()
{
$this->setValidators([
'ownerId' => [DataType::required()],
'owner' => [DataType::required()],
'token' => [
DataType::required(),
DataType::length(64),
Varchar::matchRegEx('/^[A-Za-z0-9]+$/')
],
'creationDate' => [DataType::required()],
]);
}
}

View file

@ -0,0 +1,17 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\DomainObjects\ValueObjects\Value;
interface Validatable
{
/**
* Validates the Object.
* @return ResultCollection|bool returns true if validations passed a ResultCollection otherwise
*/
public function validate();
public function getValueObjectOf(string $field): Value;
}

View file

@ -0,0 +1,19 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
interface Validator
{
/**
* Returnst the instances of the validator, instantiates a new one if there isn't one already.
* @return Validator
*/
public static function getInstance(): Validator;
/**
* Validates a DomainObject.
* @param Validatable $object
* @return ResultCollection|bool returns true if validations passed a ResultCollection otherwise
*/
public function validate(Validatable $object);
}

View file

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\Validation;
use VeruA\DomainObjects\ValidationError;
/**
* Result of a validation that was successful but produced a warning.
*/
class Warning extends Result
{
}

View file

@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* An implementation of AHV
* @todo rename to EAN and extend to use for arbitrary EAN encoded values
*/
class AHV extends EAN13
{
// in() {{{
/**
* Converts the $value stripping of all dots
*/
public function in( $value )
{
$this->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:
}}}*/

View file

@ -0,0 +1,54 @@
<?php namespace VeruA\DomainObjects\ValueObjects;
/**
* A boolean value
* Internally uses boolean
*/
class Boolean extends DataType implements Primitive
{
// in() {{{
/**
* Stores the $value as boolean casting it
*
* @param mixed The Value to be stored
*/
public function in( $value )
{
$this->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:
}}}*/

View file

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* A ValueObject that has a complex Value such as Date or Money which cannot be represented directly
* as a string and is thus returned from a DomainObject as is
*/
interface Complex
{
public function isComplex(): bool;
}

View file

@ -0,0 +1,233 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
use VeruA\DomainObjects\Validation\Error;
/**
* A DataType is the smallest entity of Data.
*
* Every value of an \VeruA\DomainObjects\DomainObject is represented as such.
*
*/
class DataType implements Value
{
/** @var mixed $value The internal value of the DataType */
protected $value;
// Constructor {{{
/**
* The constructor is normally called from the DomainObject when instatiated.
*
* @param mixde $value
*/
public function __construct( $value=null )
{
if (isset($value)) $this->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<br>
* 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:
}}}*/

View file

@ -0,0 +1,72 @@
<?php namespace VeruA\DomainObjects\ValueObjects;
/**
* A Date Value
*
*/
class Date extends DataType implements Complex
{
// Constructor {{{
/**
*
*/
public function __construct( $value=null, $props=[] )
{
parent::__construct( $value, $props );
} // }}}
// in() {{{
/**
* Stores the $value as a DateTime Object
*/
public function in( $value )
{
if (isset($value))
{
if ($value instanceof \DateTime || $value instanceof \DateTimeImmutable) {
$this->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:
}}}*/

View file

@ -0,0 +1,72 @@
<?php namespace VeruA\DomainObjects\ValueObjects;
/**
* A Date Value
*
*/
class DateTime extends DataType implements Complex
{
// Constructor {{{
/**
*
*/
public function __construct( $value=null, $props=[] )
{
parent::__construct( $value, $props );
} // }}}
// in() {{{
/**
* Stores the $value as a DateTime Object
*/
public function in( $value )
{
if (isset($value))
{
if ($value instanceof \DateTime || $value instanceof \DateTimeImmutable) {
$this->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:
}}}*/

View file

@ -0,0 +1,33 @@
<?php namespace VeruA\DomainObjects\ValueObjects;
/**
* An implementation of Decimal
*/
class Decimal extends DataType implements Primitive
{
// in() {{{
/**
* Stores the $value as int casting it
*
* @param mixed The Value to be stored
*/
public function in( $value )
{
$this->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:
}}}*/

View file

@ -0,0 +1,112 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
use VeruA\DomainObjects\Validation\Error;
/**
* An implementation of EAN13
*/
class EAN13 extends DataType implements Complex
{
// equals() {{{
/**
* Compares the EAN13-Numbers internal value
* @param EAN13|string $compare the EAN Number to be compared with the datatypes one
*/
public function equals( $compare ): bool
{
if (! $compare instanceof EAN13) $compare = new EAN13($compare);
return ($this->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:
}}}*/

View file

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* An implementation of Email
*/
class Email extends Varchar
{
}
/* jEdit buffer local properties {{{
* :folding=explicit:collapseFolds=1:
}}}*/

View file

@ -0,0 +1,103 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
use VeruA\Log;
/**
* Class File
*
* Represents a file as a value object.
* This class handles file-related operations such as resolving paths,
* checking readability, and computing relative paths.
*
* @property-read bool $readable Indicates whether the file is readable.
* @property-read string $realPath The absolute real path of the file.
* @property-read string $relativePath The relative path of the file from the document root directory.
*/
class File extends DataType implements Complex
{
/**
* @var string The default directory for file storage.
*/
protected $directory = 'file';
use Log;
/**
* Stores the provided value as an SPLFileInfo object.
*
* This method takes the given path, resolves it relative to the defined directory,
* and stores it as an SPLFileInfo instance.
*
* @param string $value The relative path to the file.
* @return void
*/
public function in($value)
{
$path = __DIR__ . '/../../../' . $this->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);
}
}

View file

@ -0,0 +1,22 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* Class Image
*
* Represents an image as a value object.
* This class extends the File class, inheriting its functionality,
* and sets a specific directory for image storage.
*
* @property-read bool $readable Indicates whether the file is readable.
* @property-read string $realPath The absolute real path of the file.
* @property-read string $relativePath The relative path of the file from the document root directory.
*/
class Image extends File
{
/**
* @var string The default directory for image storage.
*/
protected $directory = 'img';
}

View file

@ -0,0 +1,51 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* A key implementatin class storing an int key
*/
class IntKey extends Integer implements Key
{
// in() {{{
/**
* Stores the $value as int casting it
*
* @param mixed The Value to be stored
*/
public function in( $value )
{
if ($value === null)
{
$this->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:
}}}*/

View file

@ -0,0 +1,50 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* An implementation of Integer
*/
class Integer extends DataType implements Primitive
{
// in() {{{
/**
* Stores the $value as int casting it
*
* @param mixed The Value to be stored
*/
public function in( $value )
{
if ($value === null)
{
$this->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:
}}}*/

View file

@ -0,0 +1,11 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* A Key ValueObject
*/
interface Key
{
public function toIndex();
}

View file

@ -0,0 +1,42 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
use VeruA\DomainObjects\Validation\Error;
/**
* A Value that is a Number should use this trait
*/
trait Number
{
/**
* @return callable Returns a test which determines if a value is below a given threshold.
*/
public static function max($n): callable
{
return function ($x) use ($n)
{
return ($x->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,
);
};
}
}

View file

@ -0,0 +1,51 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
use VeruA\DomainObjects\Validation\Error;
/**
* A Password with validation and hashing functionality
* @author norb
*/
class Password extends Varchar
{
/**
* The constructor takes the unhhashed password and stores it hashed
*
* You have to manually execute the validatio method before you pass the cleartext password to
* the constructor!
*
* The DomainObjects call the constructor with no argument and call the in() method manually,
* not hashing the password. This is important if it is loaded from the database.
*/
public function __construct(string $password=null)
{
if (isset($password)) $this->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:
}}}*/

View file

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* An implementation of a Phonenumber
*/
class Phone extends Varchar
{
}
/* jEdit buffer local properties {{{
* :folding=explicit:collapseFolds=1:
}}}*/

View file

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* A ValueObject that has a primitive Value like int or string and is thus returned from a DomainObject
* as value and not as ValueObject Instance
*/
interface Primitive
{
}

View file

@ -0,0 +1,58 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* An implementation of a Street composed by name and number
*/
class Street extends Varchar
{
private string $name;
private string $number;
// in() {{{
/**
*
*/
public function in($value)
{
$this->value = (string)trim($value);
preg_match('/^(?P<name>(\d*\D+ (\d* )?)+)(?P<number>\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:
}}}*/

View file

@ -0,0 +1,24 @@
<?php declare(strict_types = 1);
namespace VeruA\DomainObjects\ValueObjects;
use VeruA\DomainObjects\Validation\Error;
class Text extends DataType implements Primitive {
// in() {{{
/**
* Stores the $value as string casting it
*
* @param mixed The Value to be stored
*/
public function in( $value )
{
$this->value = (string)$value;
} // }}}
public function like( string $text )
{
}
}

View file

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* An implementation of URL
*/
class URL extends Varchar
{
}
/* jEdit buffer local properties {{{
* :folding=explicit:collapseFolds=1:
}}}*/

View file

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* A ValueObject
*/
interface Value
{
public function __toString();
public function in($value);
public function out();
public function value();
public static function defaultValidators(): array;
}

View file

@ -0,0 +1,73 @@
<?php declare(strict_types = 1);
namespace VeruA\DomainObjects\ValueObjects;
use VeruA\DomainObjects\Validation\Error;
/**
* An implementation of Varchar
*/
class Varchar extends DataType implements Primitive
{
// in() {{{
/**
* Stores the $value as string casting it
*
* @param mixed The Value to be stored
*/
public function in( $value )
{
$this->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:
}}}*/

View file

@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace VeruA\DomainObjects\ValueObjects;
/**
* An implementation of a ZSR Number
*
* Allowed Format `/^[A-Z][0-9]{6}$/`
*/
class ZSR extends DataType implements Complex
{
// in() {{{
/**
* Converts the $value stripping of all dots
*/
public function in( $value )
{
$this->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:
}}}*/

48
src/Log.php Normal file
View file

@ -0,0 +1,48 @@
<?php declare(strict_types=1);
namespace VeruA;
use VeruA\App;
use Monolog\{
Logger,
Handler\StreamHandler
};
/**
* Log trait, `use` it in every class you want to use the monolog logger
*
* ``` php
* use VeruA\Log; // declare the namespace
*
* class Whatever
* {
* use Log; // use the trait in the class
*
* public function example()
* {
* // ...
* // now you can use the log() method
* $this->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:
}}}*/