• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..15-Dec-2021-

Container/H15-Dec-2021-11560

Custom/H15-Dec-2021-273151

DateTime/H15-Dec-2021-7841

In/H15-Dec-2021-16989

Integer/H15-Dec-2021-11969

Logical/H15-Dec-2021-254151

Numeric/H15-Dec-2021-6642

Password/H15-Dec-2021-223146

String/H15-Dec-2021-663400

To/H15-Dec-2021-872470

URI/H15-Dec-2021-7444

examples/H15-Dec-2021-244141

CONTRIBUTING.mdH A D15-Dec-20212 KiB5741

Constraint.phpH A D15-Dec-20211.8 KiB7211

ConstraintViolationException.phpH A D15-Dec-20211.2 KiB5320

DeriveApplyToFromTransform.phpH A D15-Dec-2021708 3616

DeriveInvokeFromTransform.phpH A D15-Dec-2021623 3111

DeriveTransformFromApplyTo.phpH A D15-Dec-2021849 4119

Factory.phpH A D15-Dec-20213.5 KiB16364

IsNull.phpH A D15-Dec-2021892 3921

README.mdH A D15-Dec-202115.3 KiB477359

ROADMAP.mdH A D15-Dec-20211.2 KiB4832

Transformation.phpH A D15-Dec-20212.3 KiB629

maintenance.jsonH A D15-Dec-2021361 1717

README.md

1# Refinery
2
3The `Refinery` library is used to unify the way input is
4processed by the ILIAS project.
5
6**Table of Contents**
7- [General](#general)
8- [Quickstart example](#quickstart-example)
9- [Usage](#usage)
10  * [Factory](#factory)
11    + [Groups](#groups)
12      - [to](#to)
13        * [Natives](#natives)
14        * [Structures](#structures)
15      - [in](#in)
16        * [series](#series)
17        * [parallel](#parallel)
18  * [Custom Transformation](#custom-transformation)
19    + [DeriveApplyToFromTransform](#deriveapplytofromtransform)
20      - [Error Handling](#error-handling)
21    + [DeriveTransformFromApplyTo](#derivetransformfromapplyto)
22      - [Error Handling](#error-handling-1)
23- [Libraries](#libraries)
24  * [Transformation](#transformation)
25  * [Validation](#validation)
26
27## General
28
29This library contains various implementations and
30interfaces to establish a way to secure input
31and transform values in a secure way.
32
33The initial concept for this library can be found
34[here](/docs/documentation/input-processing.md).
35
36These library also consists of sub-libraries,
37that can be used for transformations and
38validation.
39Checkout the [chapter](#libraries) about these
40additional libraries.
41
42## Quickstart example
43
44```php
45global $DIC;
46
47$refinery = $DIC->refinery();
48
49$transformation = $refinery->in()->series(
50    array(
51        new Refinery\To\IntegerTransformation(),
52        new Refinery\To\IntegerTransformation()
53    )
54);
55
56$result = $transformation->transform(5);
57
58$data = $refinery->data('alphanumeric')->transform(array($result));
59
60echo $data->getData();
61```
62
63The output will be a `integer` value: `5`
64
65## Usage
66
67### Factory
68
69The factory of the refinery interface can create
70an implementations of very different [groups](#groups).
71These groups can be used for several validations and
72transformations.
73
74A concrete implementation of the `Refinery\Factory`
75interface is `Refinery\Factory\BasicFactory`.
76This implementation will create new instances of the
77different [groups](#groups).
78The `Refinery\Factory\BasicFactory` can also be accessed
79via the `ILIAS Dependency Injection Container(DIC)`.
80
81```php
82global $DIC;
83
84$refinery = $DIC->refinery();
85$transformation = $refinery->to()->string();
86// ...
87```
88
89Checkout the [examples](/src/Refinery/examples) to
90see how these library can be used.
91
92_Info: These examples are just for a show case.
93These examples are non-operable from the console,
94because of the missing initialization of ILIAS_
95
96#### Groups
97
98The different groups are used to validate and/or transform
99the input given to the certain transformation.
100
101Because of the usage of the `Transformation` interface
102these groups can interact with each other and
103with other implementation interfaces.
104E.g. transformation from the `to` group can be used in
105the `in` group and vice versa.
106
107##### to
108
109The `to` group consists of combined validations and transformations
110for native data types that establish a baseline for further constraints
111and more complex transformations.
112
113A concrete implementation for the `Refinery\To\Group` interface
114is the `Refinery\To\BasicGroup`.
115
116To learn more about transformations checkout the
117[README about Transformations](/src/Refinery/Transformation/README.md).
118
119The transformations of this group are very strict, which means
120that there are several type checks before the transformation is
121executed.
122
123```php
124$transformation = $refinery->to()->int();
125
126$result = $transformation->transform(3.5); // Will throw exception because, values is not an integer value
127$result = $transformation->transform('hello'); // Will throw exception because, values is not an integer value
128$result = $transformation->transform(3); // $result = 3
129```
130
131In this example the `Refinery\To\IntegerTransformation` of the `to` group is
132used.
133The `Refinery\To\IntegerTransformation` of this group is very strict,
134so only elements of the `integer` type are allowed.
135Every non-matching value will throw an exception.
136
137To avoid exception handling the `applyTo` method can be used instead.
138Find out more about the `applyTo` method of instances of the `Transformation`
139interface in the
140[README about Transformations](/src/Refinery/Transformation/README.md).
141
142###### Natives
143
144As seen in the example of the [previous chapter](#to)
145there are transformations which cover the native data
146types of PHP (`int`, `string`, `float` and `boolean`).
147
148* `string()`   - Returns an object that allows to transform a value to a string value.
149* `int()`      - Returns an object that allows to transform a value to a integer value.
150* `float()`    - Returns an object that allows to transform a value to a float value.
151* `bool()`     - Returns an object that allows to transform a value to a boolean value.
152
153###### Structures
154
155Beside the [native transformations](#natives) there also
156transformation to create structures like `list`, `dictonary`,
157`record` and `tuple`.
158
159* `listOf()`   - Returns an object that allows to transform an value in a given array
160                 with the given transformation object.
161                 The transformation will be executed on every element of the array.
162* `dictOf()`   - Returns an object that allows to transform an value in a given array
163                 with the given transformation object.
164                 The transformation will be executed on every element of the array.
165* `tupleOf()`  - Returns an object that allows to transform the values of an array
166                 with the given array of transformations objects.
167                 The length of the array of transformations MUST be identical to the
168                 array of values to transform.
169                 The keys of the transformation array will be the same as the key
170                 from the value array e.g. Transformation on position 2 will transform
171                 value on position 2 of the value array.
172* `recordOf()` - Returns an object that allows to transform the values of an
173                 associative array with the given associative array of
174                 transformations objects.
175                 The length of the array of transformations MUST be identical to the
176                 array of values to transform.
177                 The keys of the transformation array will be the same as the key
178                 from the value array e.g. Transformation with the key "hello" will transform
179                 value with the key "hello" of the value array.
180* `toNew()`    - Returns either an transformation object to create objects of an
181                 existing class, with variations of constructor parameters OR returns
182                 an transformation object to execute a certain method with variation of
183                 parameters on the objects.
184* `data()`     - Returns a data factory to create a certain data type
185
186##### in
187
188The `in` group is a group with a dict of `Transformations`
189as parameters that define the content at the indices.
190
191A concrete implementation for the `Refinery\In\Group` interface
192is the `Refinery\In\BasicGroup`.
193
194There are currently two different strategies supported by this group,
195that are accessible by the methods:
196
197* [series](#series)
198* [parallel](#parallel)
199
200###### series
201
202The transformation `series` takes an array of transformations and
203performs them one after another on the result of the previous transformation.
204
205```php
206$transformation = $refinery->in()->series(
207    array(
208        new Refinery\To\IntegerTransformation(),
209        new Refinery\To\StringTransformation()
210    )
211);
212
213$result = $transformation->transform(5.5);
214// $result => '5'
215```
216
217The result will be the end result of the transformations that were executed
218in the strict order added in the `series` method.
219
220In this case it is a `string` with the value '5'.
221
222###### parallel
223
224The transformation `parallel` takes an array of transformations and
225performs each on the input value to form a tuple of the results.
226
227```php
228$transformation = $refinery->in()->parallel(
229    array(
230        new Refinery\To\IntegerTransformation(),
231        new Refinery\To\IntegerTransformation()
232    )
233);
234
235$result = $transformation->transform(5);
236// $result => array(5, 5)
237```
238
239The result will be an array of results of each transformation.
240
241In this case this is an array with an `integer` and a `string`
242value.
243
244##### Custom
245
246The `Custom` group contains of `Transformations` and `Constraints`
247that can be used to create individual transformations and constraints.
248
249##### Logical
250
251The `Logical` group contains of `Constraints` that can be used to create
252different logical operation that can be used on concrete `Constraints`-
253
254##### Null
255
256`Null` group contains of constraints that can be used to identify the
257`null` value via a `Constraint`.
258
259##### Numeric
260
261`Numeric` group consists of a constraints that can be used to identify a
262numeric value via a `Constraint`.
263
264##### Password
265
266`Password` consists of a contains that can be used to create constraints
267for validating password.
268
269##### String
270
271`String` consist of transformations and constraints which can be applied
272to string inputs.
273
274### Custom Transformation
275
276Sometimes the default transformations of this library are not enough, so a
277custom transformation is needed.
278
279As every other transformation it must implement the
280`ILIAS\Refinery\To\Transformation` interface.
281
282By default these transformation need an implementation for the
283methods `transformation` and `applyTo`.
284Because these methods are always containing the same basic process
285(with different results types and exception handling),
286this library contains traits to ease the creation of new transformation.
287
288The traits that can be used are:
289 * [DeriveApplyToFromTransform](#deriveapplytofromtransform)
290 * [DeriveTransformFromApplyTo](#derivetransformfromapplyto)
291
292An example shows how one of the traits can be used.
293
294```php
295class BooleanTransformation implements Transformation
296{
297	use DeriveApplyToFromTransform;
298
299	/**
300	 * @inheritdoc
301	 */
302	public function transform($from)
303	{
304		if (false === is_bool($from)) {
305			throw new ConstraintViolationException(
306				'The value MUST be of type boolean',
307				'not_boolean'
308			);
309		}
310		return (bool) $from;
311	}
312
313	/**
314	 * @inheritdoc
315	 */
316	public function __invoke($from)
317	{
318		return $this->transform($from);
319	}
320}
321```
322
323In the above example we use the trait `DeriveApplyToFromTransform`
324and only define the `transform` method.
325
326Please be aware that the error handling can vary
327by using these traits.
328Checkout the  following chapters for more information.
329
330#### DeriveApplyToFromTransform
331
332This trait is used define `applyTo` on its own.
333Just the `transform` method needs to be created in the new transformation class.
334
335##### Error Handling
336
337Exceptions thrown inside the `transformation` method will be
338caught and added to new
339[error result object (`Result\Error`)](/src/Data/README.md#result).
340
341The origin exception can be accessed through this error object.
342
343#### DeriveTransformFromApplyTo
344
345This trait is used define `transform` on its own.
346Just the `applyTo` method needs to be created in the new transformation class.
347
348##### Error Handling
349
350Exceptions thrown inside the `applyTo` method will be
351**not be** caught.
352On return of an [error result object (`Result\Error`)](/src/Data/README.md#result)
353the `transform` method will throw an exception.
354
355* If the content of the error object is an exception the exception will be
356  thrown.
357* If the content of the error object is an string this string will be added
358  to a an `Exception` which will be thrown.
359
360## Libraries
361
362These library consists of several sub-libraries,
363which have their own descriptions.
364
365### Transformation
366
367A transformation is a function from one type or structure of data to another.
368It MUST NOT perform any sideeffects, i.e. it must be morally impossible to observe
369how often the transformation was actually performed. It MUST NOT touch the provided
370value, i.e. it is allowed to create new values but not to modify existing values.
371This would be an observable sideeffect.
372
373The actual usage of this interface is quite boring, but we could typehint on
374`Transformation` to announce we indeed want some function having the aforementioned
375properties. Typehinting on `Transformation` will be useful when code talks about
376structures containing data in some sense, e.g. lists or trees, where the code is
377involved with containing structure but not with the contained data. This would be
378a classic case for generics in languages that support them. PHP unfortunately is
379a language that does not support generics.
380
381The use case that actually led to the proposal of this library is the forms
382abstraction in the UI framework, where the abstraction deals with forms, extraction
383of data from them and validation of data in them. The concept of transformation
384is required, not matter if we typehint on them or not. Other facilities in PHP
385do not allow a more accurate typehinting, due to lack of generics.
386
387Having common transformations ready in a factory, connected with the promise
388given by the developer that the `Transformation` indeed respects the intended
389properties, should be useful in other scenarios as well, especially at the
390boundaries of the system, where data needs to be re- and destructured to fit
391interfaces to other systems or even users.
392
393```php
394
395require_once(__DIR__."\Factory.php");
396
397$f = new \ILIAS\Refinery\Factory;
398
399// Adding labels to an array to name the elements.
400$add_abc_label = $f->container()->addLabels(["a", "b", "c"]);
401$labeled = $add_abc_label->transform([1,2,3]);
402assert($labeled === ["a" => 1, "b" => 2, "c" => 3]);
403
404// Split a string at some delimiter.
405$split_string_at_dot = $f->string()->splitString(".");
406$split = $split_string_at_dot->transform("a.b.c");
407assert($split === ["a", "b", "c"]);
408
409// Use a closure for the transformation.
410$int_to_string = $f->custom(function ($v) {
411	if (!is_int($v)) {
412		throw new \InvalidArgumentException("Expected int, got ".get_type($v));
413	}
414	return "$v";
415});
416$str = $int_to_string->transform(5);
417assert($str === "5");
418assert($str !== 5);
419```
420### Validation
421
422A validation checks some supplied value for compliance with some constraints.
423Validations MUST NOT modify the supplied value.
424
425Having an interface to Validations allows to typehint on them and allows them
426to be combined in structured ways. Understanding validation as a separate service
427in the system with objects performing the validations makes it possible to build
428a set of common validations used throughout the system. Having a known set of
429validations makes it possible to perform at least some of the validations on
430client side someday.
431
432```php
433
434// In reality this has dependencies that need to be satisfied...
435$f = new ILIAS\Refinery\Factory;
436
437// Build some basic constraints
438$gt0 = $f->integer()->greaterThan(0);
439$lt10 = $f->integer()->lessThan(10);
440
441// Check them and react:
442if (!$gt0->accepts(1)) {
443	assert(false); // does not happen
444}
445
446// Let them throw an exception:
447$raised = false;
448try {
449	$lt10->check(20);
450	assert(false); // does not happen
451}
452catch (\UnexpectedValueException $e) {
453	$raised = true;
454}
455assert($raised);
456
457// Get to know what the problem with some value is:
458assert(is_string($gt0->problemWith(-10)));
459
460// Combine them in a way that the constraints are checked one after another:
461$between_0_10 = $f->logical()->sequential([$gt0, $lt10]);
462
463// Or in a way that they are checked independently:
464$also_between_0_10 = $f->logical()->parallel([$gt0, $lt10]);
465
466// One can also create a new error message by supplying a builder for an error
467// message:
468
469$between_0_10->withProblemBuilder(function($txt, $value) {
470	return "Value must be between 0 and 10, but is '$value'.";
471});
472
473// To perform internationalisation, the provided $txt could be used, please
474// see `ILIAS\Refinery\Validation\Constraint::withProblemBuilder` for further information.
475
476```
477