Giter Club home page Giter Club logo

php-option's Introduction

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): \PhpOption\Option
    {
        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): \PhpOption\Option
    {
        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
$entity = $this->findSomeEntity();
if (null === $entity) {
    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, and one additional method call to retrieve the value from the wrapper. 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 [email protected]. 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.

php-option's People

Contributors

asm89 avatar cviniciussdias avatar cystbear avatar erikn69 avatar grahamcampbell avatar lstrojny avatar matteosister avatar muglug avatar niconoe- avatar pborreli avatar resurtm avatar schmittjoh avatar strate avatar szepeviktor avatar vanderlee avatar villfa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

php-option's Issues

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?

composer install or composer require fail to download

hello i'm using an laravel app where is package is required but when doing a composer install or composer require command it fails to download the package. Any idea on how to fix this issue. ;)

cordially and thanks in advance

image

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

PHP 8.1: New #[ReturnTypeWillChange] attribute

Hi,

I came across an error after updating to PHP 8.1 while loading a via CraftCMS site locally. The same error also happened using PHP8.0.

PHPWatch posted a new PHP8.1 attribute New #[ReturnTypeWillChange] attribute which may solve the issue.

Here is the error.

Deprecated: Return type of PhpOption\Some::getIterator() should either be compatible with IteratorAggregate::getIterator(): Traversable, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /vendor/phpoption/phpoption/src/PhpOption/Some.php on line 152

Deprecated: Return type of PhpOption\None::getIterator() should either be compatible with IteratorAggregate::getIterator(): Traversable, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /vendor/phpoption/phpoption/src/PhpOption/None.php on line 118

Some.php Before

    public function getIterator()
    {
        return new ArrayIterator([$this->value]);
    }

Some.php After

 #[\ReturnTypeWillChange]
    public function getIterator()
    {
        return new ArrayIterator([$this->value]);
    }

None.php Before

public function getIterator()
    {
        return new EmptyIterator();
    }

None.php After

 #[\ReturnTypeWillChange]
 public function getIterator()
    {
        return new EmptyIterator();
    }

As a quick test, I took a look in AbstractVariables.php and added #[\ReturnTypeWillChange] before the public function declaration and the warning was suppressed. I read your contributing guidelines, and could try to make the changes and submit a PR, but I'm a junior PHP developer and might not code it correctly and there are many public function declarations that I may miss.

[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.

Additional methods to isDefined and isEmpty

I think the methods for isSome() and isNone() would be a great addition as the classes are actually called like that. Would you be willing to merge this? Note: This would be a non-breaking change as they would also use isDefined() and isEmpty() under the hood.

add .gitattributes file

.travis.yml		export-ignore
phpunit.xml.dist	export-ignore
tests			export-ignore
.gitignore		export-ignore
.gitattributes		export-ignore

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.

Generic typing of `Option::fromValue` results in `Option<valueType|null>`

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.

[PHP 8.1] Deprecation notice because of incompatible return type

Hello, and thank you for your package!

I'm trying to prepare the incoming PHP 8.1 on my projects that are using phpoptions, and I am experiancing deprecated messages because of incompatible return types, mainly on the PhpOption\None::getIterator() and PhpOption\Some::getIterator() methods.

This is due to this RFC being approved for PHP 8.1.

I'll try to provide some PR to fix this as soon as I can, and try to stay compatible with PHP 7.0.0.

Autocomplete

Hello, I have simple question, how can I have autocomplete for the result per I can not change @return annotation for Option::get/getOrElese/โ€ฆ methods.

<?php
$entity = $this->findSomeEntity()->getOrElse(new Entity());
$entity->lostAutocompleteHere();

Thank you

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

`Option::fromArraysValue` should have parameter `$key` typed as `int|string` (and possibly `null|int|string`)

When looking at the current version (1.9.2), the PHPDoc of Option::fromArraysValue tells that parameter $key must be a string, while it could also be an int, in case we want to use Option::fromArraysValue with an indexed array rather than an associative one.

This would also make sense regarding the PHPDoc of the $array parameter of the exact same method.

If I may, I would like to go further and allow null for $key too, which would automatically leads to returning a None instance.

Let's have this:

$fooEntitites; // array<int, Entity>
$barEntity->foo_id; // int|null

// $barEntity->foo_id can be:
// - `null` if this Bar isn't bound to any Foo
// - `int` but not an existing key in $fooEntities for some reasons
// - `int` and existing key in $fooEntities

$foo = Option::fromArraysValue($fooEntities, $barEntity->foo_id)->getOrElse(null);

This last line is currently forbidden to me by PHPStan/Psalm because of the typehint not being aligned with your current version of your library.

I will try to provide a PR if you're interested in.

Thanks a lot!

Collection based options

I'm starting to understand the null reference exception problem. But I tend to get this problem when working with arrays. There's always boilerplate code to check if an array value exists, or if a key exists (especially when referencing non-existent keys, I then need to setup a default value).

What do you think of a collection based option type?

Build targets: 5.3 fails (remove); nothing for 7.0+

Hi,

I notice that the master branch fails on PHP 5.3, which is old enough that I suspect it could safely be dropped from the Travis plan.

Would it be worth adding a 7.0+ build target to the Travis plan, for more recent releases?

Cheers!

Use SPDX license identifier

The composer.json file contains a license identifierApache2, which is not a valid SPDX identifier.
Some tools (such as composer itself) rely on this information to conform to SPDX.
Please use the Apache-2.0 identifier from https://spdx.org/licenses/

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.