"Runn Me!" — tells us the next framework* PHP. And heard "Throw Me!". Part 2

* generally speaking this is not a framework, just a set of libraries, framework it will be later


Less than a week since the "mad success" (here opinions differ slightly, of course...) the first part of our story, as it's time to release a second.

Today we continue the journey into the bottomless depths of the library runn/core of the future framework "Runn Me!". Under the cut we will meet following the inhabitants of the abyss:

the
    the
  • the Concept of "multiline" and its implementation in the library
  • the
  • the Concept of object with an internal validation and reference implementation of such a facility
  • the
  • a Little look into the world of validators and sanitizers (a detailed story about them will come later)
  • the
  • Consider the implementation of the object with required fields




the

Previous series


    the
  1. Historical reference. ObjectAsArray. Collection. TypedCollection. Std class

the

Multiscene


In the beginning was the interface Throwable.

Tricky thing. Directly it is impossible to implement as many interfaces from the standard PHP library, but any object that implements this interface receives a special superpower: it can "throw" (throw). However, the word "any" here is an exaggeration: everything that is available to us is to inherit your own class from library a \Exception.

It is with this inheritance and the exceptions in "Runn Me!":

the
namespace Runn\Core;

class Exception
extends \Exception
implements \JsonSerializable
{
public function jsonSerialize()
{
return ['code' => $this->getCode(), 'message' => $this->getMessage()];
}
}

The implementation of the JsonSerializable interface for a reason added immediately to Runn\Core\Exception: this is a clear Foundation for the future, the bright future, when we will learn how to catch exceptions in our middleware, package it as JSON and give the response to the client.

However, the "Runn Me!" is not as boring as it may seem when reading listings. Very close to the Exception class in the namespace Runn\Core hiding another, almost imperceptible grade Runn\Core\Exceptions. What is it?

This multiscene:

the
    the
  • a Typed collection (see previous article)
  • the
  • elements of the collection — the Throwable
  • the
  • And while she is a Throwable!

Let's look at a simple example how such a construction can be used:

the
$exceptions = new Exceptions;

$exceptions- > add(new Exception('First'));
$exceptions- > add(new Exception('Second'));

assert(2 === count($exceptions));
assert('First' === $exceptions[0]->getMessage());
assert('Second' === $exceptions[1]->getMessage());

if (!$exceptions->empty()) {
throw $exception;
}

the

Where used multiscene?


Probably the most "bright" the application of this pattern — validation mechanism provided for in standard object "Runn Me!".

library Authors are well aware that even the hint that the validation error may be an exception that can cause a burning sensation in the backside of many programmers. However, we decided that the potential profit from such an approach far exceeds the disadvantages that can be expected of him. So don't flame for nothing — we have validation errors are implemented by exceptions and multistructure. This decision and is not appealable.

Let us consider in detail step by step how it works.

the

Methods of connection validation


Inside of a standard object (or rather, any heir HasInnerValidationInterface if you are not too departed from the reference implementation described in StdGetSetWValidateSanitizeTrait) you can define the method of connectivity validation.

The name of this method must start with "validate" followed by the key name, the value for which we will validate, with the first a capital letter. For example:
the
class extends Std ValidationExampleClass
{
protected function validateFoo($val)
{
return true;
}
protected function validateBar($val)
{
return true;
} 
}

$obj = new ValidationExampleClass;
$obj->foo = 42;
$obj- > bar = 'baz';

This code shows that we have created two methods for connection validation validateFoo() and validateBar(). The first one will be automatically called prior TO assignment of any value to the property $obj- > foo, and the second, respectively, to the actual changes $obj- > baz.

Five possible options the "conduct" method of connection validation:

    the
  1. Method returns false
  2. the
  3. unit Method throws an exception
  4. the
  5. Method throws an multiscene
  6. the
  7. the Method is the generator exception
  8. the
  9. And finally, the method just returned something that is not false

The most simple is options №№ 1 and 5.

In the first case, nothing happens, the assignment to "silence" is cancelled, the property gets a new value.

In the fifth case, the assignment of the new value of the property also to "silence" comes without causing any side effects.

A little more complicated variants №№ 2, 3 and 4. They all lead to cancellation of assignment. But to understand exactly what they are, we will go further.

the

Methods mass-assignment (padding)


In order to more fully use the potential of the connection methods validation in trace StdGetSetWValidateSanitizeTrait overridden an important method merge() (which, in particular, is used in the constructor of the class Std).

Show it's better with an example:

the
class extends Std ValidationExampleClass
{

// Case 2: a single exception
protected function validateFoo($val)
{
if (empty($val)) {
throw new Exception('foo is empty');
}
}

// Case 3: multiscene
protected function validateBar($val)
{
$errors = new ValidationErrors; // this class is, of course, inherited from Exceptions
if (strlen($val) < 6) {
$errors[] = new Exception('bar is too short');
}
if (preg_match('~\d~', $val)) {
$errors[] = new Exception('bar contains digits');
}
if (!$errors->empty()) {
throw $errors;
}
} 

// Case 4: generator exceptions
protected function validateBaz($val)
{
if (strlen($val) > 6) {
yield new Exception('baz is too long');
}
if (preg_match('~[a-z]~', $val)) {
yield new Exception('baz contains letters');
}
} 
}

Now that we defined all possible validation rules in all possible ways, let's try to create an object that deliberately violate all of these rules:

the
try {

$obj = new ValidationExampleClass([
'foo' => ", // violating the rule of "non-empty"
'bar' => '123', // two errors - "too short" and "contains digits",
'baz' => 'abcdefgh', // two errors - "too long" and "contains letters",
]);

} catch (Exceptions $errors) {
foreach ($errors as $error) {
// Perfect! We got all five of the validation errors!
echo $error->getMessage();
}
}

What happened?

To start the method merge() prepares an empty collection class Exceptions for future validation errors. Then for each key is called, if it exists, the connection method validation.

2. Connection method validation threw a single exception: it is added to the collection.
3. Method threw multiscene: it melds with the collection.
4. Method is a generator: everything it generates, is Throwable, which will be added to the collection.

If after all these operations a collection of validation errors was not empty — it is thrown. Next is you need it somewhere to catch and decide what to do with all these errors.

the

Methods of connection sanitizing


Well, here the story is not as exciting as about validation. It's simple:

the
class extends Std SanitizationExampleClass
{
protected function sanitizePhone($val)
{ 
return preg_replace('~\D~', ", $val);
}
}

$obj = new SanitizationExampleClass;
$obj->phone = '+7 (900) 123-45-67';
assert('79001234567' === $obj->phone);

Defined the method, it gets on input the value you wish to assign to the property, that returns would be rated. Trite, but useful.

the

a Small announcement


Of course, the "magic" methods in standard objects, the theme of validation and sanitation is not exhausted. One of the next articles will be devoted to the library runn/validation, which is currently being prepared for publication.
the

And finally, the promised mandatory fields


The most important thing, I tell you. Especially when we move to the topic of complex (I really want to pronounce this word with the accent on the "e": "complex") objects. But without them you can understand everything:

the
class testClassWithRequired extends Std {
protected static $required = ['foo', 'bar']; 
// if you do not have enough flexibility, you can override the method getRequiredKeys() according to your taste
}

try {
$obj = new testClassWithRequired();
} catch (Exceptions $errors) {

assert(2 == count($errors));
assert('Required property "foo" is missing' === $errors[0]->getMessage());
assert('Required property "bar" is missing' === $errors[1]->getMessage());
}

As you can see, it uses all the same familiar mechanism multiinclude to alert us that some required fields were unspecified in the constructor of the object. By the way, if there validation errors, we will see! All in the same collection $errors.

All for today. Stay tuned for the next articles!

PS a Detailed plan with timing of release of the framework in General, there is no desire to get to any next date. So don't ask "when". As the availability of individual library will get articles about them.

P. P. S. gratefully accept information about mistakes or typos in messages.

Good weekend to all!
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Automatically create Liquibase migrations for PostgreSQL

Vkontakte sync with address book for iPhone. How it was done

What part of the archived web