Back to Home

The duck says “quack-quack”, the cow says “moo-moo,” “Runn Me!” - the next PHP * framework tells us. Part 1

php · php 7 · framework · library · lgpl · composer · github

The duck says “quack-quack”, the cow says “moo-moo,” “Runn Me!” - the next PHP * framework tells us. Part 1

    “Oh no!”, The reader will exclaim, tired of various mini-micro-slim frameworks and QueryBuilders, and he will be right.

    There is nothing more boring than another PHP framework. Is that a “fundamentally new” CMS or a new dating .



    So why am I, with tenacity worthy of a better application, walking along uncomfortable pitfalls and putting my comrades ’court to the public’s amusement ? Knowing in advance that the anger of critics, how powerful a tsunami will fall on this post and bury it on the very bottom of Habr?

    I do not know. As Columbus did not know at one time why he was sailing off the cozy coast of Spain. Was he hoping to find a way to India? Of course yes. But he didn’t know for sure - would he swim?

    Apparently, PHP programmers, to which I have been classifying myself for 13 years, have the same inner need - to expose their code and close their eyes, waiting for the reaction of colleagues.

    What awaits you under the cut?

    • Open Source, LGPL License
    • Code Fully Compatible with PHP 7.0-7.2
    • 100% unit test coverage
    • Libraries, time-tested in real projects (and only damn procrastination prevented me from publishing them earlier!)

    Well and, of course, the story of the invention of the next bike on a crutch drive of the framework *!

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



    A bit of history or Where did the idea to “write another framework come from?”


    Yes, actually, out of nowhere. She has always been.

    Various interesting projects, in which I participated in during my programming career, put standard solutions for standard tasks into my personal money-box - like me, like any normal programmer, my own library of functions, classes and libraries was accumulated.

    About six years ago, the management of the company I worked for at that time set the task: to develop our own framework. Make a lightweight MVC framework, taking only the most necessary, add specific domain libraries to it (believe me - very specific!) And put together some kind of universal solution. The solution, it should be noted, turned out, but the specificity of the subject area did not allow it to become widespread - the code was not published, installations were sold on the client’s site. It's a pity. Some things were really ahead of their time: it’s enough to say that even though the primitive, but still pretty similar kind of composer, the team and I did completely independently and a little earlier than the actual stable public composer appeared :)

    Thanks to this experience, I was able to study almost all the frameworks that existed in the PHP ecosystem then. Along the way, another event happened, another “transition of quantity into quality” - I began to teach programming. At first in one well-known online school, then he focused on the development of his own service. He began to “grow” teaching methods, teaching materials and, of course, students. In this party the idea of ​​a “training framework” arose, which was deliberately simplified for beginners to understand, but at the same time allowing them to successfully develop simple web applications in accordance with modern standards and trends.

    Three years ago, as the realization of this idea of ​​the “training framework”, a small MVC framework was born under the name “T4” *. The name is nothing special, just an abbreviation for “Technological layout, version 4”. I think it is clear that the previous three versions were unsuccessful and only with the fourth attempt we, together with my students of that time, managed to create something really interesting.

    * later I learned that the name of the program for sterilization and killing of terminally ill people was called so in the Third Reich ... of course, the question immediately arose of changing the name

    T4 successfully developed and grew, became known, as they say, “in narrow circles” (very narrow), a number of rather large projects were made on it, but internal dissatisfaction with this decision grew.

    At the beginning of this year, I finally matured to reformat the accumulated code. Together with a group of like-minded people who also actively used T4, we adopted a number of basic principles for building a new framework:

    1. We make it a loosely coupled set of libraries, so that each lib can be connected and used separately.
    2. Trying to keep healthy minimalism where possible
    3. The framework itself for web and console applications is also one of the libraries, thereby avoiding monolithicity.
    4. We try not to reinvent the wheel and as much as possible we keep those approaches and that code which already proved in T4.
    5. We refuse support for obsolete versions of PHP, we write code for the most current version.
    6. We try to make the code as flexible as possible. If possible, instead of classes and inheritance, we use interfaces, traits and code composition, leaving users of the framework the opportunity to replace the reference implementation of any component with their own.
    7. We cover the code with tests, achieving 100% coverage.

    So the project was born, which was first called "Running.FM", and then finally renamed to "Runn Me!"

    It is him that I represent today.

    By the way, the word “runn” is constructed artificially: on the one hand, to be understandable to everyone and evoke associations with “run”, on the other hand, so that it does not coincide with any of the dictionary words. I generally like the combination of “run”: I still managed to participate in RunCMS at one time :)

    At the moment, the project "Runn Me!" It’s in the middle of the path - some libraries can already be used in production, some in the process of transferring from old projects and refactoring, and some have not yet begun to be transferred.

    In the beginning was Core


    Fit in one post a story about each library of the Runn Me! Project impossible: there are a lot of them, I want to tell in detail about each, well, and besides, this is a living project in which everything changes for the better literally every day :)

    Therefore, I decided to divide the story about the project into several posts. Today we will talk about the base library, which is called "Core".

    • Purpose: implementation of the base classes of the framework
    • GitHub: github.com/RunnMe/Core
    • Composer: github.com/RunnMe/Core
    • Installation: with the composer require runn / core command
    • Versions: as in any other library of the “Runn Me!” Project, three versions are supported that correspond to the previous, current and future versions of PHP:
      7.0. *, 7.1. * And 7.2. *

    An array? An object? Or all together?


    The gracious idea of ​​an object consisting of arbitrary properties that can be created and deleted on the fly, like elements in an array, comes to the mind of every PHP programmer. And every second one implements this idea. I and my team were no exception: I want to start your acquaintance with the Runn \ Core library with a story about the ObjectAsArray concept.

    Do it once: define an interface that will allow you to cast your object to the array and vice versa: turn the array into an object without forgetting a couple of useful methods in this interface (merge () for merging the object with external data and recursive casting to the array)

    github.com/ RunnMe / Core / blob / master / src / Core / ArrayCastingInterface.php
    namespace Runn\Core;
    interface ArrayCastingInterface
    {
        public function fromArray(iterable $data);
        public function merge(iterable $data);
        public function toArray(): array;
        public function toArrayRecursive(): array;
    }
    

    Do two: compile a megainterface that describes the behavior of the future object-as-array as fully as possible, laying there the maximum of useful things: serialization, iteration, counting the number of elements, obtaining a list of keys and values, searching for the element in this "array object".

    github.com/RunnMe/Core/blob/master/src/Core/ObjectAsArrayInterface.php
    namespace Runn\Core;
    interface ObjectAsArrayInterface
      extends \ArrayAccess, \Countable, \Iterator, ArrayCastingInterface, HasInnerCastingInterface, \Serializable, \JsonSerializable
    {
      ...
    }
    

    Do three: write a trait that will become the reference implementation of the mega-interface. See github.com/RunnMe/Core/blob/master/src/Core/ObjectAsArrayTrait.php

    As a result, we got a full-fledged implementation of an "object-as-array". Using the ObjectAsArrayInterface interface and the ObjectAsArrayTrait trait allows us to do something like this:

    class someObjAsArray implements \Runn\Core\ObjectAsArrayInterface 
    {
      use \Runn\Core\ObjectAsArrayTrait;
    }
    $obj = (new someObjAsArray)->fromArray([1 => 'foo', 2 => 'bar']);
    $obj[] = 'baz';
    $obj[4] = 'bla';
    assert(4 === count($obj));
    assert([1 => 'foo', 2 => 'bar', 3 => 'baz', 4 => 'bla'] === $obj->values());
    foreach ($obj as $key => $val) {
      // ...
    }
    assert('{"1":"foo","2":"bar","3":"baz","4":"bla"}' === json_encode($obj));
    

    In addition to the basic capabilities, ObjectAsArrayTrait implements the ability to intercept the assignment and reading of "elements of an array object" using custom setter-getters, a sort of groundwork for future classes:

    class customObjAsArray implements \Runn\Core\ObjectAsArrayInterface 
    {
      use \Runn\Core\ObjectAsArrayTrait;
      protected function getFoo() 
      {
        return 42;
      }
      protected function setBar($value)
      {
        echo $value;
      }
    }
    $obj = new customObjAsArray;
    assert(42 === $obj['foo']);
    $obj['bar'] = 13; // выводит 13, присваивания не происходит
    

    Important: null is set!


    Yes, an element of an array object whose value is null is considered defined.

    This decision caused a lot of controversy, but still it was made. Believe me, there are serious reasons that will be discussed later in the story about the ORM library:

    class someObjAsArray implements \Runn\Core\ObjectAsArrayInterface 
    {
      use \Runn\Core\ObjectAsArrayTrait;
    }
    $obj = new someObjAsArray;
    assert(false === isset($obj['foo']));
    assert(null === $obj['foo']);
    $obj['foo'] = null;
    assert(true === isset($obj['foo']));
    assert(null === $obj['foo']);
    

    And why is that all?


    Well then! Everything I talked about above is just the beginning. Other interfaces are inherited from the \ Runn \ Core \ ObjectAsArrayInterface interface and implement classes that give life to two "class branches": Collection and Std.

    Collections


    Collections at Runn Me! - these are array objects equipped with a large number of additional useful methods:

    Here you can see them all
    namespace Runn\Core;
    interface CollectionInterface
        extends ObjectAsArrayInterface
    {
        public function add($value);
        public function prepend($value);
        public function append($value);
        public function slice(int $offset, int $length = null);
        public function first();
        public function last();
        public function existsElementByAttributes(iterable $attributes);
        public function findAllByAttributes(iterable $attributes);
        public function findByAttributes(iterable $attributes);
        public function asort();
        public function ksort();
        public function uasort(callable $callback);
        public function uksort(callable $callback);
        public function natsort();
        public function natcasesort();
        public function sort(callable $callback);
        public function reverse();
        public function map(callable $callback);
        public function filter(callable $callback);
        public function reduce($start, callable $callback);
        public function collect($what);
        public function group($by);
        public function __call(string $method, array $params = []);
    }
    


    Of course, the developer immediately has both the reference implementation of this interface in the form of the CollectionTrait trait and the \ Runn \ Core \ Collection class, which is ready for use (or inheritance), adding a convenient constructor to the implementation of the methods from the trait.

    Using collections, it becomes possible to write something like this:

    $collection = new Collection([1 => 'foo', 2 => 'bar', 3 => 'baz']);
    $collection->prepend('bla');
    $collection
      ->reverse()
      ->map(function ($x) { 
        return $x . '!'; 
      })
      ->group(function ($x) {
        return substr($x, 0, 1);
      });
    /*
    получится что-то вроде
    [
      'b' => new Collection([0 => 'baz!', 1 => 'bar!', 2 => 'bla!']),
      'f' => new Collection([0 => 'foo!'),
    ),
    ]
    */
    

    What is important to know about collections?

    1. Most methods do not modify the original collection, but return a new one.
    2. Most methods do not guarantee the preservation of element keys.
    3. The best use of collections is to store many homogeneous or similar objects in them.

    Typed Collections


    In addition to “regular” collections, the Runn \ Core library includes an interesting tool that allows you to fully control the objects that may be contained in the collection. These are typed collections.

    Everything is very, very simple:

    class UsersCollection extends \Runn\Core\TypedCollection 
    {
      public static function getType()
      {
        return User::class; // тут может быть и название скалярного типа, кстати
      }  
    }
    $collection = new UsersCollection;
    $collection[] = 42; // Exception: Typed collection type mismatch
    $collection->prepend(new stdClass); // Exception: Typed collection type mismatch
    $collection->append(new User); // Success!
    

    Std


    The second “branch” of the code, somewhat opposed to collections, is called the “Standard Object”. It is also being built step by step:

    Do it once: define an interface for “magic”.

    namespace Runn\Core;
    interface StdGetSetInterface
    {
        public function __isset($key);
        public function __unset($key);
        public function __get($key);
        public function __set($key, $val);
    }
    

    Do two: add a standard implementation to it (see github.com/RunnMe/Core/blob/master/src/Core/StdGetSetTrait.php )

    Do three: assemble from a "spare part" class based on StdGetSetInterface with many additional features. github.com/RunnMe/Core/blob/master/src/Core/Std.php

    At the end of the path, we get with you a fairly universal class suitable for solving many problems. Here are just a few examples:

    $obj = new Std(['foo' => 42, 'bar' => 'bla-bla', 'baz' => [1, 2, 3]]);
    assert(3 === count($obj));
    assert(42 === $obj->foo);
    assert(42 === $obj['foo']);
    assert(Std::class == get_class($obj->baz));
    assert([1, 2, 3] === $obj->baz->values());
    // о, да, реализация вот этой штуки весьма монструозна:
    $obj = new Std;
    $obj->foo->bar = 42;
    assert(Std::class === get_class($obj->foo));
    assert(42 === $obj->foo->bar);
    

    Of course, the "skills" of the Std class are not limited to chaining, access to properties, as to elements of an array, and vice versa, by casting to the class itself. It can do much more: validate and clear data, track required properties, etc. But more about this later in other articles of the cycle.

    What next?


    It's only the beginning! Ahead of us are stories about:

    • Multi exceptions
    • Validators and sanitizers
    • About storages, serializers and configs
    • About implementing Value Objects and Entities
    • About HTML and Server-Side Form Submission
    • About DBAL's own library, including, of course, QueryBuilder!
    • ORM library
    • and how the finale - MVC-framework

    But this is all in future articles. In the meantime, happy holiday, comrades! Peace, labor, code! :)



    PS We don’t have a detailed plan with deadlines, just as 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.

    ©


    KDPV (s) Mart Virkus 2016
    Picture in the conclusion of the article from Google image search

    Read Next