📜 ⬆️ ⬇️

“Runn Me!” - tells us another PHP framework *. And heard "Throw Me!". Part 2

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


Not even a week passed since the moment of “crazy success” (opinions differ a bit here, of course ...) of the first part of our story, how it is time to release the second one.

Today we continue our journey into the bottomless depths of the runn / core library of the future runn me! Framework. Under the cut, we will meet the following inhabitants of the abyss:
')




Previous series:


  1. History reference. ObjectAsArray. Collection. TypedCollection. Std class

Multi-exception


In the beginning was the Throwable interface.

Sly thing. You cannot directly implement it, like many interfaces from the standard PHP library, but any object that implements this interface gets a special superpower: you can “throw it away” (throw). However, the word "any" here is an obvious exaggeration: all that is available to us is to inherit our own class from the library \ Exception.

It is with this inheritance that work begins with exceptions in Runn Me!

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 is not immediately added to Runn \ Core \ Exception: this is an obvious foundation for the future, for that bright future, when you and I learn how to catch exceptions in our middleware, package in JSON, and respond to the client.

However, “Runn Me!” Is not as boring as it may seem when rereading listings. Right next to the Exception class in the Runn \ Core namespace is another, almost imperceptible class Runn \ Core \ Exceptions. What is it?

This is a multi-exception:


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

 $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 $exceptions; } 

Where are multi-inclusions applied?


Perhaps the most "bright" application of this pattern is the validation mechanism provided for in the standard Runn Me! Objects.

The authors of the libraries are well aware that even a hint that a validation error can be an exception can cause burning of the back of many programmers. However, we decided that the possible profit from such an approach many times exceeds the disadvantages that can be expected from it. Therefore, do not burn in vain - we have validation errors implemented by exceptions and multi-exclusions. This decision is made and not appealable.

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

Validation Connection Methods


Inside the standard object (more precisely, any descendant of HasInnerValidationInterface, if you are not too far away from the reference implementation described in StdGetSetWValidateSanitizeTrait) you can define validation connection methods.

The name of such a method must begin with the word “validate” followed by the name of the key, the value for which we will validate, with the first capital letter. For example:

 class ValidationExampleClass extends Std { 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 connecting validate validateFoo () and validateBar (). The first one will be automatically called BEFORE assigning a value to the $ obj-> foo property, the second, respectively, before the actual change of $ obj-> baz.

There are five possible “behaviors” of the validation connection method:

  1. Method returns false
  2. The method throws a single exception.
  3. The method throws out a multi-exception.
  4. The method is an exception generator.
  5. And finally, the method simply returned something that is not false.

The simplest ones are options №№ 1 and 5.

In the first case, just nothing happens, the assignment "silently" is canceled, the property does not receive a new value.

In the fifth case, the assignment of a new value to the property also “silently” occurs without causing any side effects.

The options Nos. 2, 3 and 4 are a little more complicated. All of them lead to the cancellation of assignment. But in order to understand exactly what they are intended for, we will go further.

Methods of mass assignment (filling)


In order to make the most of the potential of validation connection methods in the StdGetSetWValidateSanitizeTrait, an important merge () method has been redefined (which, among other things, is used in the Std class constructor).

To show his work is best by example:

 class ValidationExampleClass extends Std { //   2:   protected function validateFoo($val) { if (empty($val)) { throw new Exception('foo is empty'); } } //   3:  protected function validateBar($val) { $errors = new ValidationErrors; //  , ,   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; } } //   4:   protected function validateBaz($val) { if (strlen($val) > 6) { yield new Exception('baz is too long'); } if (preg_match('~[az]~', $val)) { yield new Exception('baz contains letters'); } } } 

Now that you and I have defined all possible validation rules in all possible ways, let's try to create an object that intentionally violates all these rules:

 try { $obj = new ValidationExampleClass([ 'foo' => '', //   "" 'bar' => '123', //   - " "  " ", 'baz' => 'abcdefgh', //   - " "  " ", ]); } catch (Exceptions $errors) { foreach ($errors as $error) { // !      ! echo $error->getMessage(); } } 

What happened?

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

2. The validation connection method threw a single exception: it is added to the collection.
3. The method threw out a multi-exclusion: it is combined with the collection.
4. The method is a generator: everything that it generates, which is Throwable, will be added to the collection.

If, after all these operations, the collection of validation errors was non-empty, it is thrown out. Then you have to catch it somewhere and decide what to do with all these errors.

Sanitizing Connection Methods


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

 class SanitizationExampleClass extends Std { protected function sanitizePhone($val) { return preg_replace('~\D~', '', $val); } } $obj = new SanitizationExampleClass; $obj->phone = '+7 (900) 123-45-67'; assert('79001234567' === $obj->phone); 

The method defined, it receives as input the value that you intend to assign to the property, what it returns will be really assigned. Trite, but useful.

Small announcement


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

And finally, the promised mandatory fields


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

 class testClassWithRequired extends Std { protected static $required = ['foo', 'bar']; //     ,    getRequiredKeys()    } 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, the same multi-exclusion mechanism is used here to notify us that some required fields were not installed in the object constructor. By the way, if validation errors occur there, we will see them too! All in the same $ errors collection.

That's all for today. Follow the following articles!

PS We don’t have a detailed plan with the release dates of the framework as a whole, nor is there a desire to be in time for some regular date. Therefore, do not ask "when." As soon as individual libraries are ready, articles about them will be published.

PPS With gratitude I will accept information about errors or typos in personal messages.

Have a great weekend everyone!

Source: https://habr.com/ru/post/328080/


All Articles