定制 fp4php/functional 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

fp4php/functional

最新稳定版本:v6.0.1

Composer 安装命令:

composer require fp4php/functional

包简介

PHP Functional Programming library

README 文档

README

PHP Functional Programming library. Monads and common use functions.

psalm level psalm type coverage phpunit coverage

Documentation

Installation

Composer

Supported installation method is via composer:

$ composer require fp4php/functional

Psalm integration

Please refer to the fp4php/functional-psalm-plugin repository.

Overview

Typesafe and concise.

Powerful combination: Collections + Option monad.

<?php

use Fp\Collections\ArrayList;
use Fp\Functional\Option\Option;

use function Fp\Evidence\of;
use function Fp\Evidence\proveString;

class PgSqlCurrencyArrayType extends Type
{
    public function convertToDatabaseValue($value, AbstractPlatform $platform): string
    {
        $currencies = Option::fromNullable($value)
            ->filter(is_iterable(...))
            ->getOrElse([]);

        return ArrayList::collect($currencies)
            ->flatMap(of(Currency::class))
            ->map(fn(Currency $currency) => $currency->getCurrencyCode())
            ->mkString('{', ',', '}');
    }

    /**
     * @return ArrayList<Currency>
     */
    public function convertToPHPValue($value, AbstractPlatform $platform): ArrayList
    {
        $csv = Option::fromNullable($value)
            ->flatMap(proveString(...))
            ->map(fn(string $pgSqlArray) => trim($pgSqlArray, '{}'))
            ->getOrElse('');

        return ArrayList::collect(explode(',', $csv))
            ->filterMap($this->parseCurrency(...));
    }

    /**
     * @return Option<Currency>
     */
    public function parseCurrency(string $currencyCode): Option
    {
        return Option::try(fn() => Currency::of($currencyCode));
    }
}

Examples

  • Type safety
<?php

use Fp\Collections\NonEmptyLinkedList;

/**
 * Inferred type is NonEmptyLinkedList<1|2|3>
 */
$collection = NonEmptyLinkedList::collectNonEmpty([1, 2, 3]);

/**
 * Inferred type is NonEmptyLinkedList<int>
 * 
 * Literal types are dropped after map transformation,
 * but NonEmpty collection prefix has been kept
 */
$mappedCollection = $collection->map(fn($elem) => $elem - 1);

/**
 * Inferred type is LinkedList<positive-int>
 * NonEmpty prefix has been dropped
 */
$filteredCollection = $mappedCollection->filter(fn(int $elem) => $elem > 0);
<?php

use Tests\Mock\Foo;
use Tests\Mock\Bar;
use Fp\Collections\NonEmptyArrayList;

$source = [new Foo(1), null, new Bar(2)];

/**
 * Inferred type is ArrayList<Foo|Bar>
 * Null type was removed
 * NonEmpty prefix was removed
 */
$withoutNulls = NonEmptyArrayList::collectNonEmpty($source)
    ->filter(fn(Foo|Bar|null $elem) => null !== $elem);

/**
 * Inferred type is ArrayList<Foo>
 * Bar type was removed
 */
$onlyFoos = $withoutNulls->filter(fn($elem) => $elem instanceof Foo);
  • Covariance
<?php

use Fp\Collections\NonEmptyLinkedList;

class User {}
class Admin extends User {}

/**
* @param NonEmptyLinkedList<User> $collection
*/
function acceptUsers(NonEmptyLinkedList $collection): void {}

/** 
 * @var NonEmptyLinkedList<Admin> $collection 
 */
$collection = NonEmptyLinkedList::collectNonEmpty([new Admin()]);

/**
 * You can pass collection of admins instead of users
 * Because of covariant template parameter
 */
acceptUsers($collection);
  • Immutability
<?php

use Fp\Collections\LinkedList;

$originalCollection = LinkedList::collect([1, 2, 3]);

/**
 * $originalCollection won't be changed
 */
$prependedCollection = $originalCollection->prepended(0);

/**
 * $prependedCollection won't be changed
 */
$mappedCollection = $prependedCollection->map(fn(int $elem) => $elem + 1);
  • Null safety
<?php

use Fp\Functional\Option\Option;
use Fp\Collections\ArrayList;

/**
 * @var ArrayList<int> $collection 
 */
$collection = getCollection();

/**
 * @return Option<float>
 */
function div(int $a, int $b): Option
{
    return Option::when(0 !== $b, fn() => $a / $b);
}

/**
 * It's possible there is no first collection element above zero
 * or divisor is zero.
 *
 * In this case the execution will short circuit (stop)
 * and no Null Pointer Exception will be thrown.
 */
$collection
    ->first(fn(int $elem) => $elem > 0)
    ->map(fn(int $elem) => $elem + 1)
    ->flatMap(fn(int $elem) => div($elem, $elem - 1))
    ->getOrElse(0)
<?php

use Tests\Mock\Foo;
use Fp\Functional\Option\Option;

use function Fp\Evidence\proveTrue;
use function Fp\Evidence\proveNonEmptyList;

/**
 * Inferred type is Option<Foo> 
 */ 
$maybeFooMaybeNot = Option::do(function() use ($untrusted) {
    // If $untrusted is not null then bind this value to $notNull
    $notNull = yield Option::fromNullable($untrusted);
 
    // If $notNull is non-empty-list<Tests\Mock\Foo> then bind this value to $nonEmptyListOfFoo 
    $nonEmptyList = yield proveNonEmptyList($notNull, of(Foo::class));

    // Continue computation if $nonEmptyList contains only one element
    yield proveTrue(1 === count($nonEmptyList));

    // I'm sure it's Foo object
    return $nonEmptyList[0];
});

// Inferred type is Tests\Mock\Foo
$foo = $maybeFooMaybeNot->getOrCall(fn() => new Foo(0));

Contribution

Build documentation

  1. Install dependencies
$ sudo apt install pandoc
  1. Generate doc from src
$ make

统计信息

  • 总下载量: 220.22k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 199
  • 点击次数: 1
  • 依赖项目数: 11
  • 推荐数: 0

GitHub 信息

  • Stars: 199
  • Watchers: 9
  • Forks: 6
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-01-04

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固