maxonfjvipon/elegant-elephant

EO风格的PHP原语小型库

4.12.0 2023-05-20 15:53 UTC

README

logo

ElegantElephant

ElegantElephant - EO风格的PHP原语小型库。灵感来源于Cactoos,由@yegor256创建。

EO principles respected here DevOps By Rultor.com

Composer codecov Hits-of-Code License Tag Total lines

动机

PHP被设计成一种过程式语言。后来PHP开始支持面向对象范式,但并不是真正的纯面向对象。我们得到了类,但我们仍然以过程式的方式使用它们。这个库强制你以真正的面向对象方式编写对象。

原则

入门

要求

  • PHP >= 8.0

安装

composer require maxonfjvipon/elegant-elephant

片段

获取扁平数组

// The result will be [0, 1, 2, 2, 3, 3, 4, 5]
(new ArrFlatten(
  [0, [1], [[2, 2], [3, 3]], [4, 5]],
  deep: 2
))->asArray();

合并数组

/* 
 * If user is admin, result array will contain permissions field
 *
 * [
 *    'name' => ...,
 *    'age' => ...,
 *    'permissions' => [...],
 *    'projects' => [
 *       ['name' => ..., 'created_at' => ...],
 *       [...],
 *       ...
 *    ]
 * ]
 */
(new ArrMerged(
  [
    'name' => $user->name,
    'age' => $user->age,
  ],
  new ArrIf(
    $user->isAdmin(),
    ArrOf::func(fn () => [
      'permissions' => [...]
    ])
  ),
  new ArrObject(
    'projects',
    new ArrMapped(
      $projects,
      fn (Project $project) => [
        'name' => $project->name,
        'created_at' => $project->created_at
      ]
    ) 
  )
))-asArray();

操作文本

// To lower case
(new TxtLowered(
  TxtOf::str("Hello")
))->asString();

// To upper case
(new TxtUpper("Hello"))->asString();

// Join texts, if $isAdmin === TRUE the result will be "Hello Admin" else "Hello username, what'up"
(new TxtJoined([
  "Hello ",
  // Conditional text, behaves like first text if condition is TRUE, like second otherwise
  new TxtCond(
    $isAdmin,
    "Admin"
    TxtOf::func(fn () => $user->name())
  ),
  // Conditional text, behaves like first text if condition is TRUE, like empty string otherwise
  new TxtIf(
    !$isAdmin,
    ", what's up"
  )
]))->asString();

从点(x y)的数组中获取前x个

$points = [['x' => 1, 'y' => 1], ['x' => 2, 'y' => 2], [...], ...];

NumOf::any(
  new AtKey(
    'x',
    ArrOf::any(
      new FirstOf(
        $points
      )
    ) 
  )
)->asNumber(); // 1

获取过滤数组的长度

(new LengthOf(
  new ArrFiltered(
    [1, 2, 3, 4, 5, 6],
    fn (int $num) => $num > 3
  )
))->asNum(); // 3

商业项目中的复杂示例,没有明显的算法

(new AtKey(                                                   // 12. Get element by key "pump" from given array
  'pump',
  ArrOf::any(                                                 // 11. Try to cast given element to array
    new FirstOf(                                              // 10. Get first element of given array
      new ArrCond(                                            // 9.1. If given sorted jockey pumps are not empty - take them
        new Not(
          new IsEmpty(
            $jockeyPumps = new ArrSticky(                     // 8. Cache given sorted jockey pumps
              new ArrSorted(                                  // 7. Sort given mapped jockey pumps by "cost" key
                new ArrMapped(                                // 6. Map given jockey pumps
                  new ArrCond(                                // 5.1. If optimized jockey pumps are not empty - take them
                    new Not(
                      new IsEmpty(
                        $optimized = new ArrSticky(           // 4. Cached optimized jockey pumps
                          new ArrFiltered(                    // 3. Filter cached jockey pumps with optimization
                            $dbPumps = new ArrSticky(         // 2. Cache jockey pumps
                              new ArrPumpsForJockeySelecting( // 1. Take jockey pumps from somewhere
                                $request->jockey_series_ids,
                                $request->accounting_rests
                              )
                            ),
                            $filterPump(threeFifths: true)
                          )
                        )
                      )
                    ),
                    $optimized,
                    new ArrFiltered(                           // 5.2. If opmtimized jockey pumps are empty - filter jockey pumps without optimization
                      $dbPumps,
                      $filterPump(threeFifths: false)
                    )
                  ),
                  fn (Pump $pump) => [
                    'pump' => $pump,
                    'cost' => $pump->priceByRates($rates),
                  ]
                ),
                'cost'
              )
            )
          )
        ),
        $jockeyPumps,
        [['pump' => null]]                                      // 9.2. or take [['pump' => null]] otherwise
      ),
    )
  ),
))->value();

静态方法

由于PHP不允许在类中有多个构造函数,因此库中的一些类有公共静态方法,它们返回类的实例并替换次要构造函数。一些类有私有主要构造函数,以防止用户以错误的方式使用它们。

例如,看看具有私有构造函数但允许你通过几种静态方法创建Arr对象实例的类ArrOf

use Maxonfjvipon\ElegantElephant\Arr\ArrOf;
use Maxonfjvipon\ElegantElephant\Any\AnyOf;

// From items
ArrOf::items($item1, $item2, $item3)->asArray(); // [$item1, $item2, $item3]

// From array
ArrOf::array([1, 2, 3])->asArray(); // [1, 2, 3]

// From Maxonfjvipon\ElegantElephant\Any
ArrOf::any(AnyOf::arr(["Hello", "Jeff"]))->asArray(); // ["Hello", "Jeff"]

// From callback
ArrOf::func(fn () => ["Hello", "World"])->asArray(); // ["Hello", "World"]

如果你编写代码并发现你不能通过new关键字创建对象 - 那么这个类肯定是有私有构造函数但公共静态方法来替换构造函数的类,所以尝试使用它们。

联合参数

几乎每个类都允许你将联合类型参数传递给构造函数。

例如,类TxtJoined的行为像一个连接的字符串,并接受一个包含字符串、Txt实例或它们的组合的arrayArr实例。

use Maxonfjvipon\ElegantElephant\Txt\TxtJoined;
use Maxonfjvipon\ElegantElephant\Txt\TxtOf;
use Maxonfjvipon\ElegantElephant\Arr\ArrOf;
use Maxonfjvipon\ElegantElephant\Any\AnyOf;

$joined1 = new TxtJoined([
  TxtOf::str("Hello"), 
  " ", 
  TxtOf::any(AnyOf::str("Jeff"))
]);

$joined2 = new TxtJoined(
  ArrOf::func(fn () => [
    new TxtJoined(["Hello", " "]),
    "Jeff"
  ])
);

$joined1->asString() === $joined2->asString(); // "Hello Jeff" === "Hello Jeff" => TRUE

因此,当你编写代码时,你可能不必担心将参数转换为所需类型,例如将string转换为Maxonfjvipon\ElegantElephant\Txt。只需将你拥有的内容传递给对象,它知道如何处理它。

任何类型

具有任意(混合)类型值的任何事物。任何接口只有一个公共方法value,它必须返回mixed类型的值。

任何对象

测试

查看Any单元测试以更好地理解。

Arr

优雅的数组。Arr接口只有一个公共方法asArray(),它必须返回一个数组。

扩展

你可以在自己的类中使用另一个接口 - IterableArr,它扩展了Arr和\IteratorAggregate

\IteratorAggregate 允许您将扩展运算符 ... 应用于您的类。如果您想在类中使用扩展运算符而不实际调用 asArray() 方法,您应该让您的类实现 IterableArr 并使用 HasArrIterator 特性。该特性是扩展 Arr 类的默认实现。现在当您使用 ... 与您的类特性时,幕后会调用 asArray()

以下是一个更好的理解示例

use Maxonfjvipon\ElegantElephant\Arr;
use Maxonfjvipon\ElegantElephant\Arr\IterableArr;
use Maxonfjvipon\ElegantElephant\Arr\HasArrIterator;

class MyArr implements Arr { /** code */ }

class MyIterableArr implements IterableArr {
  use HasArrIterator;
  /** code */
}

$arr = [...(new MyArr())->asArray()];         // good
$arr = [...new MyIterableArr()];              // good, no actual calling asArray()
$arr = [...(new MyIterableArr())->asArray()]; // good, but verbose
$arr = [...new MyArr()];                      // wrong

库中的所有 Arr 类都可以扩展。

Arr 类

测试

请参阅 Arr 单元测试 以获得更好的理解。

逻辑

优雅的布尔值。 Logic 接口只有一个方法 asBool(),它必须返回 bool

Logic 类

测试

请参阅 Logic 单元测试 以获得更好的理解。

Num

优雅的数字。 Num 接口只有一个方法 asNumber(),它必须返回 floatint

Num 类

测试

请参阅 Num 单元测试 以获得更好的理解。

Txt

优雅的字符串。 Txt 接口只有一个方法 asString(),它必须返回 string

Txt->__toString()

还有一个接口,您可以在自己的类中使用 - StringableTxt,它扩展了 Txt\Stringable

\Stringable 允许您通过调用 __toString() 方法将您的类转换为字符串。因此,您可以使用 StringableTxt 接口和 TxtToString 辅助特性,它幕后调用 asString()

以下是一个更好的理解示例

use Maxonfjvipon\ElegantElephant\Txt\StringableTxt;
use Maxonfjvipon\ElegantElephant\Txt\TxtToString;
use Maxonfjvipon\ElegantElephant\Txt;

class MyTxt implements Txt { /** code */ }

class MyStringableTxt implements StringableTxt {
  use TxtToString;
  /** code */
}

$txt = new MyTxt();
$stringableTxt = new MyStringableTxt();

echo $txt->asString();           // good
echo $stringableTxt;             // good, no actual calling asString()
echo $stringableTxt->asString(); // good, but verbose
echo $txt;                       // wrong

库中的所有 Txt 类都实现了 StringableTxt

Txt 类

测试

请参阅 Txt 单元测试 以获得更好的理解。

贡献

分支存储库,进行更改,发送拉取请求。为了避免挫折,在发送您的拉取请求之前,请运行

$ ./pre-push.sh

并确保没有错误。