"Runn Me!" - the next PHP * framework tells us. And you hear "Throw Me!" Part 2

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


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

    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:

    • The concept of "multi-exclusion" and its implementation in the library
    • The concept of an object with internal validation and the reference implementation of such an object
    • Let's look a little into the world of validators and sanitizers (a detailed story about them will be later)
    • Consider the implementation of an object with required fields




    Previous Series:


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

    Multi exception


    In the beginning was the Throwable interface.

    The tricky thing. It is impossible to directly implement, like many interfaces from the standard PHP library, but any object that implements this interface receives a special superpower: it can be "thrown" (throw). However, the word "any" 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 the work with exceptions in Runn Me! Begins:

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

    Implementation of the JsonSerializable interface was added for a reason to Runn \ Core \ Exception right away: this is a clear reserve for the future, for the bright future, when we learn to catch exceptions in our middleware, pack them in JSON, and give them back to the client.

    However, "Runn Me!" not as boring as it might seem when re-reading listings. Very close to the Exception class in the Runn \ Core namespace is another, almost invisible Runn \ Core \ Exceptions class. What is it?

    This is a multi-exception:

    • Typed collection (see previous article)
    • The item type for this collection is Throwable.
    • And while she herself is Throwable!

    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 do multi-exceptions apply?


    Perhaps the most “vivid” application of this pattern is the validation mechanism provided for in standard Runn Me! Objects.

    The authors of the libraries are well aware that even a hint that a validation error may be an exception can cause burning lower back than many programmers. However, we decided that the possible profit from this approach is many times higher than the minuses that can be expected from it. Therefore, do not burn in vain - we have validation errors implemented by exceptions and multi-exceptions. This decision has been made and is not subject to appeal.

    Let's consider in detail, step by step, how it works.

    Validation Connection Methods


    Inside the standard object (or rather, any descendant of HasInnerValidationInterface, if you have not deviated too much from the reference implementation described in StdGetSetWValidateSanitizeTrait) you can define validation connection methods.

    The name of such a method should 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 instance:

    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 created two methods to enable validateFoo () and validateBar () validation. The first of them will be automatically called BEFORE assigning a value to the property $ obj-> foo, the second, respectively, before the actual change of $ obj-> baz.

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

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

    The simplest are options No. 1 and 5.

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

    In the fifth case, assigning a new value to a property also "silently" occurs, without causing any side effects.

    Slightly more complicated options are Nos. 2, 3 and 4. All of them lead to the abolition of assignment. But to understand exactly what they are intended for, we will go further.

    Bulk Assignment Methods


    In order to make full use of the potential of validation connection methods in the StdGetSetWValidateSanitizeTrait tray, the important merge () method has been redefined (which is also used in the constructor of the Std class).

    To show his work is best illustrated by:

    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('~[a-z]~', $val)) {
          yield new Exception('baz contains letters');
        }
      }  
    }
    

    Now that we have identified 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 collection of the Exceptions class for future validation errors. Then, for each key, if valid, the method for connecting validation is called.

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

    If, after all these operations, the collection of validation errors turned out to be nonempty, it is thrown away. Then you should catch it somewhere and decide what to do with all these mistakes.

    Sanitation 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);
    

    We have defined a method, it receives at the input that value that you intend to assign to the property, that which 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 entirely devoted to the runn / validation library, which is currently being prepared for publication.

    And finally, the promised required fields


    The most important thing, I tell you. Especially when we move on 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 familiar multi-exclusion mechanism is used here to notify us that some required fields turned out to be undefined in the constructor of the object. By the way, if validation errors occur there, we will see them too! All in the same $ errors collection.

    That's all for today. Stay tuned for the following articles!

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

    PPS I am grateful to accept information about errors or typos in private messages.

    Have a great weekend everyone!

    Also popular now: