📜 ⬆️ ⬇️

Duck says “quack-krya”, the cow says “mu-mu”, “Runn Me!” - another PHP framework * tells us. Part 1

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

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


')
So why do I, with perseverance, worthy of a better use, step on uncomfortable pitfalls and present my work to the public of the court of comrades? Knowing in advance that the anger of the critics, how powerful the tsunami will overwhelm this post and bury it at the very bottom of Habr?

I do not know. As Columbus did not know in his time, why did he sail from the cozy shores of Spain. Did he hope to find a way to India? Of course yes. But I didn’t know for sure - will he swim?

Apparently, PHP programmers, for whom I have been counting myself for 13 years, have the same internal need - to expose my code and squeeze my eyes, waiting for the reaction of colleagues.

What awaits you under the cut?


Well, of course, the history of the invention of the next bike on the framework's crutch drive *!

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



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


Yes, actually, from nowhere. She always was.

Various interesting projects in which I had the opportunity to participate during my programmer career put standard solutions for the standard tasks into my personal piggy bank - so I, like any normal programmer, had my own library of functions, classes and libraries.

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

Thanks to this experience, I had the opportunity to study almost all the frameworks that existed then in the PHP ecosystem. Along the way, another event occurred, the next “transition from quantity to quality” - I began to teach programming. First, in a well-known online school, then focused on the development of its own service. He began to "grow into" teaching methods, teaching materials and, of course, students. The idea of ​​some “learning framework”, intentionally simplified for beginners to understand, but at the same time allowing to successfully develop simple web applications in accordance with modern standards and trends, arose in this get-together.

Three years ago, as the implementation of this “learning framework” idea, a small MVC framework called “T4” * was born. There is nothing special in the title, just an abbreviation for "Technological layout, version 4". I think it is clear that the previous three versions came out unsuccessful and only from the fourth attempt we, together with my students of that time, managed to create something really interesting.

* I later found out that this was how the program to sterilize and kill incurably sick people was called so in the Third Reich ... of course, the question of changing the name immediately came up

T4 successfully developed and grew, became known, as they say, “in narrow circles” (very narrow), a number of quite large projects were made on it, but internal discontent 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. We try to keep healthy minimalism where it is possible.
  3. The framework itself for web and console applications is also one of the libraries, thus we avoid solidity.
  4. We try not to reinvent the wheel and maximally save the approaches and the code that have already proven themselves in T4.
  5. We refuse support for outdated versions of PHP, 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 the framework users with the ability 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!”

That's what I imagine today.

By the way, the word “runn” is artificially constructed: on the one hand, to be clear to everyone and to cause associations with “run”, on the other - so that it does not coincide with any of the vocabulary words. I generally like the “run” letter combination: I still had time to participate in RunCMS :)

At the moment, the project “Runn Me!” Is in the middle of the way - 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 transfer.

In the beginning was Core


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

So I decided to break the story about the project into several posts. In today's talk about the base library, which is called "Core".


An array? An object? Or all together?


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

Do it once: define an interface that allows you to cast your object to an array and vice versa: turn an array into an object, not forgetting in this interface a couple of useful methods (merge () to merge an object with external data and recursive casting to an 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;
}

: , -- , : , , , , «-».

github.com/RunnMe/Core/blob/master/src/Core/ObjectAsArrayInterface.php
namespace Runn\Core;

interface ObjectAsArrayInterface
  extends \ArrayAccess, \Countable, \Iterator, ArrayCastingInterface, HasInnerCastingInterface, \Serializable, \JsonSerializable
{
  ...
}

: , . . github.com/RunnMe/Core/blob/master/src/Core/ObjectAsArrayTrait.php

«--». ObjectAsArrayInterface ObjectAsArrayTrait :

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

ObjectAsArrayTrait « -» -, :

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,   

: null is set!


, -, null, .

, - . , , , ORM:

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']);

?


! , — . \Runn\Core\ObjectAsArrayInterface , « »: Collection Std.


Runn Me! — -, :

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 = []);
}


, CollectionTrait, ( ) \Runn\Core\Collection, .

:

$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!'),
),
]
*/

?

  1. , .
  2. .
  3. — .


«» Runn\Core , , . .

:

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


«» , - , « ». :

: «».

namespace Runn\Core;

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

: (. github.com/RunnMe/Core/blob/master/src/Core/StdGetSetTrait.php )

: «» , StdGetSetInterface . github.com/RunnMe/Core/blob/master/src/Core/Std.php

, . :

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

, «» Std chaining-, , , . : , .. , .

?


! :


. , ! , , ! :)



P.S. , - . «». .

P.P.S. .

©


() Mart Virkus 2016

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


All Articles