Option Type for PHP

Overview

PHP Option Type

This package implements the Option type for PHP!

Banner

Software License Total Downloads Latest Version

Motivation

The Option type is intended for cases where you sometimes might return a value (typically an object), and sometimes you might return a base value (typically null) depending on arguments, or other runtime factors.

Often times, you forget to handle the case where a base value should be returned. Not intentionally of course, but maybe you did not account for all possible states of the system; or maybe you indeed covered all cases, then time goes on, code is refactored, some of these your checks might become invalid, or incomplete. Suddenly, without noticing, the base value case is not handled anymore. As a result, you might sometimes get fatal PHP errors telling you that you called a method on a non-object; users might see blank pages, or worse.

On one hand, the Option type forces a developer to consciously think about both cases (returning a value, or returning a base value). That in itself will already make your code more robust. On the other hand, the Option type also allows the API developer to provide more concise API methods, and empowers the API user in how he consumes these methods.

Installation

Installation is super-easy via Composer:

$ composer require phpoption/phpoption

or add it by hand to your composer.json file.

Usage

Using the Option Type in your API

class MyRepository
{
    public function findSomeEntity($criteria)
    {
        if (null !== $entity = $this->em->find(...)) {
            return new \PhpOption\Some($entity);
        }

        // We use a singleton, for the None case.
        return \PhpOption\None::create();
    }
}

If you are consuming an existing library, you can also use a shorter version which by default treats null as None, and everything else as Some case:

class MyRepository
{
    public function findSomeEntity($criteria)
    {
        return \PhpOption\Option::fromValue($this->em->find(...));

        // or, if you want to change the none value to false for example:
        return \PhpOption\Option::fromValue($this->em->find(...), false);
    }
}

Case 1: You always Require an Entity in Calling Code

$entity = $repo->findSomeEntity(...)->get(); // returns entity, or throws exception

Case 2: Fallback to Default Value If Not Available

$entity = $repo->findSomeEntity(...)->getOrElse(new Entity());

// Or, if you want to lazily create the entity.
$entity = $repo->findSomeEntity(...)->getOrCall(function() {
    return new Entity();
});

More Examples

No More Boiler Plate Code

// Before
if (null === $entity = $this->findSomeEntity()) {
    throw new NotFoundException();
}
echo $entity->name;

// After
echo $this->findSomeEntity()->get()->name;

No More Control Flow Exceptions

// Before
try {
    $entity = $this->findSomeEntity();
} catch (NotFoundException $ex) {
    $entity = new Entity();
}

// After
$entity = $this->findSomeEntity()->getOrElse(new Entity());

More Concise Null Handling

// Before
$entity = $this->findSomeEntity();
if (null === $entity) {
    return new Entity();
}

return $entity;

// After
return $this->findSomeEntity()->getOrElse(new Entity());

Trying Multiple Alternative Options

If you'd like to try multiple alternatives, the orElse method allows you to do this very elegantly:

return $this->findSomeEntity()
            ->orElse($this->findSomeOtherEntity())
            ->orElse($this->createEntity());

The first option which is non-empty will be returned. This is especially useful with lazy-evaluated options, see below.

Lazy-Evaluated Options

The above example has the flaw that we would need to evaluate all options when the method is called which creates unnecessary overhead if the first option is already non-empty.

Fortunately, we can easily solve this by using the LazyOption class:

return $this->findSomeEntity()
            ->orElse(new LazyOption(array($this, 'findSomeOtherEntity')))
            ->orElse(new LazyOption(array($this, 'createEntity')));

This way, only the options that are necessary will actually be evaluated.

Performance Considerations

Of course, performance is important. Attached is a performance benchmark which you can run on a machine of your choosing.

The overhead incurred by the Option type comes down to the time that it takes to create one object, our wrapper. Also, we need to perform one additional method call to retrieve the value from the wrapper.

  • Overhead: Creation of 1 Object, and 1 Method Call
  • Average Overhead per Invocation (some case/value returned): 0.000000761s (that is 761 nano seconds)
  • Average Overhead per Invocation (none case/null returned): 0.000000368s (that is 368 nano seconds)

The benchmark was run under Ubuntu precise with PHP 5.4.6. As you can see the overhead is surprisingly low, almost negligible.

So in conclusion, unless you plan to call a method thousands of times during a request, there is no reason to stick to the object|null return value; better give your code some options!

Security

If you discover a security vulnerability within this package, please send an email to one of the security contacts. All security vulnerabilities will be promptly addressed. You may view our full security policy here.

License

PHP Option Type is licensed under Apache License 2.0.

For Enterprise

Available as part of the Tidelift Subscription

The maintainers of phpoption/phpoption and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.

Comments
  • Feature Request: Allow `OptOut` of singleton approach for `None`

    Feature Request: Allow `OptOut` of singleton approach for `None`

    Request

    Allow opting out of the singleton pattern for the None class.

    Description

    The None class currently follows the singleton approach. Due to the ability of PHP 8.1 to use new initializers there are scenarios where it would be really useful to actually OptOut of the singleton approach.

    One example I currently facing is the usage of the symfony/serializer component, DTO, and a GraphQL API.

    Given the following example:

    mutation {
        example(
            requiredValue: String!
            optionalValue: String
        ): {
            requiredValue
            optionalValue
        }
    }
    
    class Dto {
       public function __construct(
           public readonly string $requiredValue,
           public readonly Option $optionalValue
       ) {
       }
    }
    
    $serializer = new Serializer([new ObjectNormalizer()], []);
    
    // This call will fail since no value has been provided for the constructor argument `$optionalValue`
    $dto = $serializer->denormalize(
        [
            'requiredValue' => '_value_',
        ],
        Dto::class
    );
    

    To avoid this error case, the optimal constructor would be:

    class Dto {
       public function __construct(
           public readonly string $requiredValue,
    -      public readonly Option $optionalValue
    +      public readonly Option $optionalValue = new None()
       ) {
       }
    }
    

    Sure it would always be possible to handle this before the denormalization process or by providing the list of fallback values to the denormalization process but this would lead to a great amount of duplication and maintenance overhead to a very low cost of runtime and extra memory consumption.

    Acceptance criteria

    • Constructor of None class is public
    opened by MaSpeng 10
  • Change Some::map() method semantics

    Change Some::map() method semantics

    When callback, that was passed to map method, returns null, Some(null) object will be returned. This behaviour is the same as in Scala, but I have doubts this behaviour meets php world correctly. I think in scala map works in that way, because null value in this language should not be used at all, so map method should never return null.

    In java8 on the other hand when callback produces null value, the None is returned.This behaviour differs from scala, because in java null is a normal value - it is fully supported by the language from the begining. I think the null value in php also is not unusual, so Some should take into accout that callback is able to return null value and handle this in natural for php way. Thanks to that behaviour, Option would be safer, it would be possible that code:

    //helper function
    function prop($name) { ... }
    
    $email = Option::fromValue($employee)
        ->map(prop('boss'))
        //if boss is null, None will be returned so we can safetly invoke another map, filter etc.
        ->map(prop('email'))
        ->getOrElse('[email protected]');
    

    I know there is flatMap that supports Option as return value of callback - but this enforces whole our code in every level and every layer to use Option type.

    opened by psliwa 7
  • then-like method to Option

    then-like method to Option

    JS Promise has a then method, which always returns a Promise, but handle callback's return value smartly: if it is already a Promise, then-created promise resolves to returned Promise value. I think that similar method helpful for PHP:

    Option::fromValue($value)
      ->then(function($v1) { return $v1->get('123'); /* returns Option */ })
      ->then(function($v2) { /*
        $v2 here is value of $v1->get('123') Option, if it Some.
        You can return value or Option from here
      */ })
    

    Method name is discussable, but I think then is good enought, because it implement same behaviour as js pomise's then method.

    opened by Strate 6
  • Option::make introduced

    Option::make introduced

    Option::make($value, $noneValue = null) implements "smart" factory behaviour:

    • If value is already an option, it simply returns
    • If value is a Closure, LazyOption created, and resolved to:
      • an option returned from original Closure, if it returns an Option (flatMap-like behaviour)
      • Some or None, depends on return Closure value and $noneValue passed to ::make()
    • In other cases $value just passes to Option::fromValue() method
    opened by Strate 5
  • Allow to pass non-Option value to orElse method

    Allow to pass non-Option value to orElse method

    PhpOption::fromValue($this->findValue())
      ->orElse($this->findDefault()) // same as ->orElse(PhpOption::fromValue($this->findDefault()))
    
    PhpOption::fromValue($this->findValue())
      ->orElse(function() { return $this->findDefault(); }) // same as ->orElse(PhpOption::fromReturn(function() { return $this->findDefault(); }))
    
    opened by Strate 5
  • [Composer\Downloader\TransportException]

    [Composer\Downloader\TransportException]

    Doing composer.phar update

    I get : RuntimeException]
    Failed to clone http://github.com/schmittjoh/php-option.git via git, https and http protocols, aborting.

    • git://github.com/schmittjoh/php-option.git
      fatal: remote error:
      Storage server temporarily offline. See http://status.github.com for GitHub system status.

    • https://github.com/schmittjoh/php-option.git
      error: The requested URL returned error: 503 Service Unavailable while accessing https://github.com/schmittjoh/php-option.git/info/refs

      fatal: HTTP request failed

    opened by tebo-ernstberger 5
  • Laravel upgrade to 5.8 version

    Laravel upgrade to 5.8 version

    Hi, when I try to upgrade laravel from 5.7 version to 5.8 I get Failed to download phpoption/phpoption from dist: The "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525" file could not be downloaded: failed to open stream.

    I'm using the following core:

    "require": { "php": "^7.1.3", "fideloper/proxy": "^4.0", "jenssegers/mongodb": "^3.4", "laravel/framework": "^5.7.0", "laravel/tinker": "^1.0", "lorisleiva/laravel-deployer": "^0.2.5", "maddhatter/laravel-fullcalendar": "^1.3", "pusher/pusher-php-server": "^3.2", "spatie/laravel-permission": "^2.16", "yajra/laravel-datatables": "^1.1", "yajra/laravel-datatables-oracle": "~8.0" }, "require-dev": { "composer/composer": "^1.9", "filp/whoops": "^2.0", "fzaninotto/faker": "^1.4", "mockery/mockery": "^1.0", "nunomaduro/collision": "^2.0", "phpunit/phpunit": "^7.0", "squizlabs/php_codesniffer": "3.*" },

    If I use 5.7 laravel version everything works fine.

    How can I solve this?
    
    opened by God9ps 4
  • Option::fromArraysValue() corrected

    Option::fromArraysValue() corrected

    If the first parameter is not and array, PhpOption\None is now returned. Before this commit, a string could be used and one of its characters could be accessed.

    opened by CViniciusSDias 4
  • Syntax sugar for some often cases

    Syntax sugar for some often cases

    While php's closure syntax is very explicit, it is very boring to write code like this:

    $repo->find(1)
      ->flatMap(function($object) { return $object->getNested(); })
      ->flatMap(function($nested) { return (string) $nested; })
      ->getOrElse('Not found!')
    

    I suggest to add helper functions: call for this case:

    $repo->find(1)
      ->call('getNested')
      ->call('__toString')
      ->getOrElse('Not found')
    

    Also I suggest to add get function, which can helps to get object's properties and array values:

    Some::create($object)
      ->get('property') // performs property_exists check and returns None if not found
    
    Some::create($array)
      ->get($index) // performs array_key_exists check and returns None if not found
    
    opened by Strate 4
  • Support PHPUnit 8

    Support PHPUnit 8

    These changes add support for PHPUnit 8.

    1. Adds the void return type to the setUp() method. This is required for the tests to work in PHPUnit 8.

    2. Replaces deprecated annotations. This is optional until PHPUnit 9. However, ignoring it would cause CI to fail due to warnings.

    3. Uses stdClass to mock the "Subject" class. This seemed to work either way, but PHPStan complained when passing a simple string.

    Note that the mandatory change (number 1 above) will unfortunately break compatibility with PHP < 7.1, because the void return type declaration was added in 7.1.

    I have not included any changes to officially end the support for older versions, though. The library itself is unaffected and thus still compatible with older versions, albeit no longer tested against those versions. You'll thus probably want to hold off on this change until you're ready to officially end pre-7.1 support.

    opened by rgson 3
  • Support ArrayAccess on `fromArraysValue`

    Support ArrayAccess on `fromArraysValue`

    When we updated to 1.5.2, it actually broke our builds. It turns out that this change prevents you from providing an object that implements ArrayAccess.

    This PR addresses that issue.

    opened by ruudk 3
  • Fix Option::fromValue typing

    Fix Option::fromValue typing

    These changes fix the issue https://github.com/schmittjoh/php-option/issues/63.

    Option::fromValue uses the same type parameter C for the $value and $noneValue parameters. As a result, the type of C is inferred to a union of the types of $value and $noneValue.

    We fix this issue by typing $noneValue to mixed.

    Thanks to @arnaud-lb for finding the bug and suggesting this fix !

    opened by Moufle 1
  • Generic typing of `Option::fromValue` results in `Option<valueType|null>`

    Generic typing of `Option::fromValue` results in `Option`

    Option::fromValue uses the same type parameter C for the $value and $noneValue parameters. As a result, the type of C is inferred to a union of the types of $value and $noneValue:

    https://phpstan.org/r/af2d3270-93fe-463a-9a29-4dd584012cc3

    Typing $noneValue to mixed fixes the problem:

    https://phpstan.org/r/c105ea3e-21e2-4dbc-a119-36fd1154ef3e

    I believe that this would be ok since $noneValue doesn't contribute to type of Option.

    opened by arnaud-lb 0
Releases(1.9.0)
Owner
Johannes
Johannes
Primitives for functional programming in PHP

Functional PHP: Functional primitives for PHP NOTE: functional-php used to come with a C extension that implemented most of the functions natively. As

Lars Strojny 1.9k Jan 3, 2023
A Simple PHP Finite State Machine

Finite, A Simple PHP Finite State Machine Finite is a Simple State Machine, written in PHP. It can manage any Stateful object by defining states and t

Yohan Giarelli 1.3k Dec 31, 2022
A simple stateless production rules engine for PHP 5.3+

Ruler Ruler is a simple stateless production rules engine for PHP 5.3+. Ruler has an easy, straightforward DSL ... provided by the RuleBuilder: $rb =

Justin Hileman 1k Dec 28, 2022
Powerful implementation of the Specification pattern in PHP

RulerZ The central idea of Specification is to separate the statement of how to match a candidate, from the candidate object that it is matched agains

Kévin Gomez 865 Dec 22, 2022
A simple Monad library for PHP

MonadPHP This is a basic Monad library for PHP. Usage Values are "wrapped" in the monad via either the constructor: new MonadPHP\Identity($value) or t

Anthony Ferrara 283 Dec 29, 2022
Option Type for PHP

PHP Option Type This package implements the Option type for PHP! Motivation The Option type is intended for cases where you sometimes might return a v

Johannes 2.4k Dec 26, 2022
An object-oriented option parser library for PHP, which supports type constraints, flag, multiple flag, multiple values, required value checking

GetOptionKit Code Quality Versions & Stats A powerful option parser toolkit for PHP, supporting type constraints, flag, multiple flag, multiple values

Yo-An Lin 140 Sep 28, 2022
An object-oriented option parser library for PHP, which supports type constraints, flag, multiple flag, multiple values, required value checking

GetOptionKit Code Quality Versions & Stats A powerful option parser toolkit for PHP, supporting type constraints, flag, multiple flag, multiple values

Yo-An Lin 140 Sep 28, 2022
A Simple and Lightweight WordPress Option Framework for Themes and Plugins

A Simple and Lightweight WordPress Option Framework for Themes and Plugins. Built in Object Oriented Programming paradigm with high number of custom fields and tons of options. Allows you to bring custom admin, metabox, taxonomy and customize settings to all of your pages, posts and categories. It's highly modern and advanced framework.

Codestar 241 Dec 23, 2022
The easiest to use WordPress option framework.

Titan Framework The easiest to use WordPress options framework. Titan Framework allows theme and plugin developers to create admin pages, options, met

Gambit Technologies 374 Nov 14, 2022
A plugin to disable the drop cap option in Gutenberg editor paragraph block. This is version 2.

Disable Drop Cap (v2) A plugin to disable drop cap option in the Gutenberg editor block editor paragraph block. Note for WordPress 5.8 With WordPress

Johannes Siipola 4 Jan 4, 2022
The easiest to use WordPress option framework.

Titan Framework allows theme and plugin developers to create admin pages, options, meta boxes, and theme customizer options with just a few simple lines of code.

Gambit Technologies 374 Nov 14, 2022
A WordPress plugin that displays proxied war news from the free world to Russian IP address visitors with option to block further access.

A WordPress plugin that displays proxied war news from the free world to Russian IP address visitors with option to block further access.

null 5 Jul 15, 2022
This Magento 2 module adds the option to use Flagpack icons in your Hyvä frontend.

Siteation - Hyva Icon Pack - Flags This Magento 2 module adds the option to use Flagpack icons in your Hyvä frontend. This requires that you have a wo

Siteation 5 Jun 12, 2023
📦 "PHP type names" contains the list of constants for the available PHP data types.

PHP type names PHP type names ?? Description Simple library containing the list of constants for the available PHP data types. Use those constant type

♚ PH⑦ de Soria™♛ 4 Dec 15, 2022
A simple, type-safe, zero dependency port of the javascript fetch WebApi for PHP.

A simple, type-safe, zero dependency port of the javascript fetch WebApi for PHP.

Matias Navarro Carter 105 Jan 4, 2023
A PHP tool to generate templateable markdown documentation from the docblocks or type-hints of your codebase.

Roster Installation To install, simply require the package using composer: composer require

Jordan LeDoux 14 Sep 25, 2022
A package for adding more type safety to your PHP projects.

Table of Contents Overview Installation Usage Simple Checks Advanced Checks Custom Checks Skipping Checks Testing Security Contribution Credits Change

Ash Allen 14 Aug 31, 2022
Collection of value objects that represent the types of the PHP type system

sebastian/type Collection of value objects that represent the types of the PHP type system. Installation You can add this library as a local, per-proj

Sebastian Bergmann 1.1k Dec 29, 2022