All PHP functions, rewritten to throw exceptions instead of returning false, now for php8

Related tags

Miscellaneous safe8
Overview

Latest Stable Version Total Downloads Latest Unstable Version License Build Status Continuous Integration codecov

Safe PHP

Work in progress

A set of core PHP functions rewritten to throw exceptions instead of returning false when an error is encountered.

The problem

Most PHP core functions were written before exception handling was added to the language. Therefore, most PHP functions do not throw exceptions. Instead, they return false in case of error.

But most of us are too lazy to check explicitly for every single return of every core PHP function.

// This code is incorrect. Twice.
// "file_get_contents" can return false if the file does not exists
// "json_decode" can return false if the file content is not valid JSON
$content = file_get_contents('foobar.json');
$foobar = json_decode($content);

The correct version of this code would be:

$content = file_get_contents('foobar.json');
if ($content === false) {
    throw new FileLoadingException('Could not load file foobar.json');
}
$foobar = json_decode($content);
if (json_last_error() !== JSON_ERROR_NONE) {
    throw new FileLoadingException('foobar.json does not contain valid JSON: '.json_last_error_msg());
}

Obviously, while this snippet is correct, it is less easy to read.

The solution

Enter thecodingmachine/safe aka Safe-PHP.

Safe-PHP redeclares all core PHP functions. The new PHP functions act exactly as the old ones, except they throw exceptions properly when an error is encountered. The "safe" functions have the same name as the core PHP functions, except they are in the Safe namespace.

use function Safe\file_get_contents;
use function Safe\json_decode;

// This code is both safe and simple!
$content = file_get_contents('foobar.json');
$foobar = json_decode($content);

All PHP functions that can return false on error are part of Safe. In addition, Safe also provide 2 'Safe' classes: Safe\DateTime and Safe\DateTimeImmutable whose methods will throw exceptions instead of returning false.

PHPStan integration

Yeah... but I must explicitly think about importing the "safe" variant of the function, for each and every file of my application. I'm sure I will forget some "use function" statements!

Fear not! thecodingmachine/safe comes with a PHPStan rule.

Never heard of PHPStan before? Check it out, it's an amazing code analyzer for PHP.

Simply install the Safe rule in your PHPStan setup (explained in the "Installation" section) and PHPStan will let you know each time you are using an "unsafe" function.

The code below will trigger this warning:

$content = file_get_contents('foobar.json');

Function file_get_contents is unsafe to use. It can return FALSE instead of throwing an exception. Please add 'use function Safe\file_get_contents;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library.

Installation

Use composer to install Safe-PHP:

$ composer require thecodingmachine/safe

Highly recommended: install PHPStan and PHPStan extension:

$ composer require --dev thecodingmachine/phpstan-safe-rule

Now, edit your phpstan.neon file and add these rules:

includes:
    - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon

Automated refactoring

You have a large legacy codebase and want to use "Safe-PHP" functions throughout your project? PHPStan will help you find these functions but changing the namespace of the functions one function at a time might be a tedious task.

Fortunately, Safe comes bundled with a "Rector" configuration file. Rector is a command-line tool that performs instant refactoring of your application.

Run

$ composer require --dev rector/rector:^0.7

to install rector/rector.

Run

vendor/bin/rector process src/ --config vendor/thecodingmachine/safe/rector-migrate-0.7.php

to run rector/rector.

Note: do not forget to replace "src/" with the path to your source directory.

Important: the refactoring only performs a "dumb" replacement of functions. It will not modify the way "false" return values are handled. So if your code was already performing error handling, you will have to deal with it manually.

Especially, you should look for error handling that was already performed, like:

if (!mkdir($dirPath)) {
    // Do something on error
}

This code will be refactored by Rector to:

if (!\Safe\mkdir($dirPath)) {
    // Do something on error
}

You should then (manually) refactor it to:

try {
    \Safe\mkdir($dirPath));
} catch (\Safe\FilesystemException $e) {
    // Do something on error
}

Performance impact

Safe is loading 1000+ functions from ~85 files on each request. Yet, the performance impact of this loading is quite low.

In case you worry, using Safe will "cost" you ~700µs on each request. The performance section contains more information regarding the way we tested the performance impact of Safe.

Learn more

Read the release article on TheCodingMachine's blog if you want to learn more about what triggered the development of Safe-PHP.

Contributing

The files that contain all the functions are auto-generated from the PHP doc. Read the CONTRIBUTING.md file to learn how to regenerate these files and to contribute to this library.

Comments
  • Fatal error on DateTimeImmutable::createFromMutable

    Fatal error on DateTimeImmutable::createFromMutable

    root@19da4cbeb2d9:/app/src# php -v
    PHP 8.0.8 (cli) (built: Jul  1 2021 22:40:15) ( NTS )
    Copyright (c) The PHP Group
    Zend Engine v4.0.8, Copyright (c) Zend Technologies
        with Zend OPcache v8.0.8, Copyright (c), by Zend Technologies
        with Xdebug v3.1.0beta2, Copyright (c) 2002-2021, by Derick Rethans
    
    <?php
    require('../vendor/autoload.php');
    
    $date = new \DateTime();
    $immutable = \Safe\DateTimeImmutable::createFromMutable($date);
    

    results in:

    Fatal error: Uncaught Error: Call to a member function format() on null in /app/vendor/thecodingmachine/safe8/lib/DateTimeImmutable.php:75
    Stack trace:
    #0 /app/vendor/thecodingmachine/safe8/lib/DateTimeImmutable.php(38): Safe\DateTimeImmutable->format('Y-m-d H:i:s.u')
    #1 /app/vendor/thecodingmachine/safe8/lib/DateTimeImmutable.php(241): Safe\DateTimeImmutable::createFromRegular(Object(Safe\DateTimeImmutable))
    #2 /app/src/test.php(5): Safe\DateTimeImmutable::createFromMutable(Object(DateTime))
    #3 {main}
      thrown in /app/vendor/thecodingmachine/safe8/lib/DateTimeImmutable.php on line 75
    
    opened by tjveldhuizen 0
  • First attempt to mark pure functions

    First attempt to mark pure functions

    Hi!

    This is a first attempt to solve https://github.com/thecodingmachine/safe8/issues/11

    I got the list of impure functions from Psalm: https://github.com/vimeo/psalm/blob/9222b24ea17080ee3dcc1da21b65b9c4f794a4b6/src/Psalm/Internal/Codebase/Functions.php#L385

    It looks pretty complete. However, there might be some functions missing. I would like to have a more dynamic way to get them, but I think it is okay for now. New functions can be added easily.

    Thank you!

    opened by rubenrubiob 0
  • Add pure annotation to pure functions

    Add pure annotation to pure functions

    Hi!

    When using sprintf in a class with a @psalm-immutable I get the error:

     ImpureFunctionCall: Cannot call an impure function from a mutation-free context
    

    The thing is that, if I use core sprintf function, I do not get the error, as it is marked as pure. For example, in PHPStorm stubs the function is defined as follows:

    /**
     * Return a formatted string
     * @link https://php.net/manual/en/function.sprintf.php
     * @param string $format <p>
     * The format string is composed of zero or more directives:
     * ordinary characters (excluding %) that are
     * copied directly to the result, and conversion
     * specifications, each of which results in fetching its
     * own parameter. This applies to both sprintf
     * and printf.
     * </p>
     * <p>
     * Each conversion specification consists of a percent sign
     * (%), followed by one or more of these
     * elements, in order:
     * An optional sign specifier that forces a sign
     * (- or +) to be used on a number. By default, only the - sign is used
     * on a number if it's negative. This specifier forces positive numbers
     * to have the + sign attached as well, and was added in PHP 4.3.0.</p>
     * @param string|int|float ...$values [optional] <p>
     * </p>
     * @return string a string produced according to the formatting string
     * format.
     */
    #[Pure]
    function sprintf(string $format, ...$values): string {}
    

    I am opening this issue to ask if it would be valuable to add a #[Pure] annotation to all functions defined in Safe. This is something I could work on. As I do not know which functions are pure, I would base the work on PHPStorm stubs. We should be fine.

    Do you think that this is something that may be worth to work on?

    Thank you!

    Edit: I see that it is implemented in the 7.x branch: https://github.com/thecodingmachine/safe/pull/172 The work could be based on that, too.

    opened by rubenrubiob 2
  • Add PHPDoc return type to DateTimeImmutable::createFromFormat()

    Add PHPDoc return type to DateTimeImmutable::createFromFormat()

    When using PHPStan to analyze a project using Safe\DateTimeImmutable::createFromFormat(), the return type from the built-in DateTimeImmutable class is assumed.

    This change adds a return type PHPDoc comment to help PHPStan understand.

    opened by djcrock 0
  • Unresolvable conflict

    Unresolvable conflict

    Hello. Thank you for great library.

    I have installed https://github.com/infection/infection tools, which require safe lib. My project use php8, and i want to install safe8 for it. But i can't install both, because you fork lib without changing namespace and php crash on trying to redeclare functions.

    If we will have major release where v1 require php < 8.0 and v2 required php >= 8.0 then we can required v1 || v2 in infection and be happy without conflicts.

    So why just not made major release instead of forking library?

    opened by grachevko 2
Releases(v0.1)
  • v0.1(Apr 16, 2021)

Owner
TheCodingMachine
TheCodingMachine
PHP package to make your objects strict and throw exception when you try to access or set some undefined property in your objects.

?? Yell PHP package to make your objects strict and throw exception when you try to access or set some undefined property in your objects. Requirement

Zeeshan Ahmad 20 Dec 8, 2018
This is a plugin for pocketmine-mp, when locking a player's items helps players not to lose items or throw things around causing server lag.

[] LockedItem| v1.0.0 Player's item lock Features Player's item lock Players aren't afraid of losing items For Devolopers You can access to LockedItem

JeroGamingYT 3 Jan 4, 2022
🏆 Learn You PHP! - An introduction to PHP's core features: i/o, http, arrays, exceptions and so on.

Learn You PHP! The very first PHP School workshop. A revolutionary new way to learn PHP Bring your imagination to life in an open learning eco-system

PHP School 311 Dec 30, 2022
Here is the top 100 PHP functions: it is the list of the most often used PHP native functions

Here is the top 100 PHP functions: it is the list of the most often used PHP native functions. If you are a PHP developer, you must know the Top 100 PHP Functions deeply.

Max Base 16 Dec 11, 2022
A PocketMine-MP plugin for logging Exceptions to a Sentry server

Sentry A PocketMine-MP plugin for logging Exceptions to a Sentry server. Asynchronous logging If you want to log exceptions in a thread-safe way, you

null 4 Apr 9, 2022
DBML parser for PHP8. It's a PHP parser for DBML syntax.

DBML parser written on PHP8 DBML (database markup language) is a simple, readable DSL language designed to define database structures. This page outli

Pavel Buchnev 32 Dec 29, 2022
Magento-Functions - A Resource of Magento Functions

Magento-Functions A Resource of Magento Functions Table of Contents Category Product User Cart Checkout General Account [Working w/ URL's] (#urls) Cat

Bryan Littlefield 28 Apr 19, 2021
Validated properties in PHP8.1 and above using attribute rules

PHP Validated Properties Add Rule attributes to your model properties to make sure they are valid. Why this package? When validating external data com

null 23 Oct 18, 2022
JSON <=> PHP8+ objects serialization / deserialization library

A simple library for JSON to PHP Objects conversions Often times, we interact with an API, or data source that returns JSON. PHP only offers the possi

Square 90 Dec 20, 2022
Decimal handling as value object instead of plain strings.

Decimal Object Decimal value object for PHP. Background When working with monetary values, normal data types like int or float are not suitable for ex

Spryker 16 Oct 24, 2022
Unicode-url-for-Textpattern - Plugin for using unicode urls instead of transliterations to ASCII characters

Unicode-url-for-Textpattern Summary textpattern plugin wcz_utf8_url – uses UTF-8 permlinks instead of transliterated ones for SEO Features automatical

Andrij 4 Dec 16, 2017
Enhancement to Magento to allow simple product prices to be used instead of the default special-case configurable product prices

Simple Configurable Products Extension For Magento This documentation applies to SCP versions 0.7 onwards. The documentation for SCP v0.6 and earlier

Simon King 288 Nov 7, 2022
LaraNx Seo enables your Laravel app to store SEO and social media meta tag data in database instead of your code

LaraNx Seo enables your Laravel app to store SEO and social media meta tag data in database instead of your code. Moving marketing data out of your code base and into your database where it is easily modified.

srg 13 Dec 29, 2022
now you can save MentionedMessage in UI

General now you can save MentionedMessage in UI Example: You Chat Hi @MulqiGaming @Friends it will be save Message later to MulqiGaming and Friends Co

MulqiGaming64 3 Aug 9, 2022
A Laravel Wrapper for the Binance API. Now easily connect and consume the Binance Public & Private API in your Laravel apps without any hassle.

This package provides a Laravel Wrapper for the Binance API and allows you to easily communicate with it. Important Note This package is in early deve

Moinuddin S. Khaja 7 Dec 7, 2022
A now playing screen for the Raspberry Pi using the Last.fm API.

raspberry-pi-now-playing A now playing screen for the Raspberry Pi using the Last.fm API. This project is detailed, with photos of how I used it with

null 44 Dec 17, 2022
Helper script to aid upgrading magento 2 websites by detecting overrides. Now supports third party module detections

ampersand-magento2-upgrade-patch-helper Helper scripts to aid upgrading magento 2 websites, or when upgrading a magento module This tool looks for fil

Ampersand 242 Dec 18, 2022
Dubbox now means Dubbo eXtensions, and it adds features like RESTful remoting, Kyro/FST serialization, etc to the Dubbo service framework.

Dubbox now means Dubbo eXtensions. If you know java, javax and dubbo, you know what dubbox is :) Dubbox adds features like RESTful remoting, Kyro/FST

当当 4.9k Dec 27, 2022
This package is considered feature-complete, and is now in security-only maintenance mode

laminas-soap This package is considered feature-complete, and is now in security-only maintenance mode, following a decision by the Technical Steering

Laminas Project 46 Dec 18, 2022