| 1 |
[](https://packagist.org/packages/thecodingmachine/safe](https://packagist.org/packages/thecodingmachine/safe](https://packagist.org/packages/thecodingmachine/safe) |
| 2 |
[](https://packagist.org/packages/thecodingmachine/safe](https://packagist.org/packages/thecodingmachine/safe](https://packagist.org/packages/thecodingmachine/safe) |
| 3 |
[](https://packagist.org/packages/thecodingmachine/safe](https://packagist.org/packages/thecodingmachine/safe](https://packagist.org/packages/thecodingmachine/safe) |
| 4 |
[](https://packagist.org/packages/thecodingmachine/safe](https://packagist.org/packages/thecodingmachine/safe](https://packagist.org/packages/thecodingmachine/safe) |
| 5 |
[](https://travis-ci.org/thecodingmachine/safe](https://travis-ci.org/thecodingmachine/safe](https://travis-ci.org/thecodingmachine/safe) |
| 6 |
[](https://github.com/thecodingmachine/safe/actions](https://github.com/thecodingmachine/safe/actions](https://github.com/thecodingmachine/safe/actions) |
| 7 |
[](https://codecov.io/gh/thecodingmachine/safe](https://codecov.io/gh/thecodingmachine/safe](https://codecov.io/gh/thecodingmachine/safe) |
| 8 |
|
| 9 |
Safe PHP |
| 10 |
======== |
| 11 |
|
| 12 |
**Work in progress** |
| 13 |
|
| 14 |
A set of core PHP functions rewritten to throw exceptions instead of returning `false` when an error is encountered. |
| 15 |
|
| 16 |
## The problem |
| 17 |
|
| 18 |
Most PHP core functions were written before exception handling was added to the language. Therefore, most PHP functions |
| 19 |
do not throw exceptions. Instead, they return `false` in case of error. |
| 20 |
|
| 21 |
But most of us are too lazy to check explicitly for every single return of every core PHP function. |
| 22 |
|
| 23 |
```php |
| 24 |
// This code is incorrect. Twice. |
| 25 |
// "file_get_contents" can return false if the file does not exists |
| 26 |
// "json_decode" can return false if the file content is not valid JSON |
| 27 |
$content = file_get_contents('foobar.json'); |
| 28 |
$foobar = json_decode($content); |
| 29 |
``` |
| 30 |
|
| 31 |
The correct version of this code would be: |
| 32 |
|
| 33 |
```php |
| 34 |
$content = file_get_contents('foobar.json'); |
| 35 |
if ($content === false) { |
| 36 |
throw new FileLoadingException('Could not load file foobar.json'); |
| 37 |
} |
| 38 |
$foobar = json_decode($content); |
| 39 |
if (json_last_error() !== JSON_ERROR_NONE) { |
| 40 |
throw new FileLoadingException('foobar.json does not contain valid JSON: '.json_last_error_msg()); |
| 41 |
} |
| 42 |
``` |
| 43 |
|
| 44 |
Obviously, while this snippet is correct, it is less easy to read. |
| 45 |
|
| 46 |
## The solution |
| 47 |
|
| 48 |
Enter *thecodingmachine/safe* aka Safe-PHP. |
| 49 |
|
| 50 |
Safe-PHP redeclares all core PHP functions. The new PHP functions act exactly as the old ones, except they |
| 51 |
throw exceptions properly when an error is encountered. The "safe" functions have the same name as the core PHP |
| 52 |
functions, except they are in the `Safe` namespace. |
| 53 |
|
| 54 |
```php |
| 55 |
use function Safe\file_get_contents; |
| 56 |
use function Safe\json_decode; |
| 57 |
|
| 58 |
// This code is both safe and simple! |
| 59 |
$content = file_get_contents('foobar.json'); |
| 60 |
$foobar = json_decode($content); |
| 61 |
``` |
| 62 |
|
| 63 |
All PHP functions that can return `false` on error are part of Safe. |
| 64 |
In addition, Safe also provide 2 'Safe' classes: `Safe\DateTime` and `Safe\DateTimeImmutable` whose methods will throw exceptions instead of returning false. |
| 65 |
|
| 66 |
## PHPStan integration |
| 67 |
|
| 68 |
> Yeah... but I must explicitly think about importing the "safe" variant of the function, for each and every file of my application. |
| 69 |
> I'm sure I will forget some "use function" statements! |
| 70 |
|
| 71 |
Fear not! thecodingmachine/safe comes with a PHPStan rule. |
| 72 |
|
| 73 |
Never heard of [](https://github.com/phpstan/phpstanPHPStan](https://github.com/phpstan/phpstan](https://github.com/phpstan/phpstan) before? |
| 74 |
Check it out, it's an amazing code analyzer for PHP. |
| 75 |
|
| 76 |
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. |
| 77 |
|
| 78 |
The code below will trigger this warning: |
| 79 |
|
| 80 |
```php |
| 81 |
$content = file_get_contents('foobar.json'); |
| 82 |
``` |
| 83 |
|
| 84 |
> 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. |
| 85 |
|
| 86 |
## Installation |
| 87 |
|
| 88 |
Use composer to install Safe-PHP: |
| 89 |
|
| 90 |
```bash |
| 91 |
$ composer require thecodingmachine/safe |
| 92 |
``` |
| 93 |
|
| 94 |
*Highly recommended*: install PHPStan and PHPStan extension: |
| 95 |
|
| 96 |
```bash |
| 97 |
$ composer require --dev thecodingmachine/phpstan-safe-rule |
| 98 |
``` |
| 99 |
|
| 100 |
Now, edit your `phpstan.neon` file and add these rules: |
| 101 |
|
| 102 |
```yml |
| 103 |
includes: |
| 104 |
- vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon |
| 105 |
``` |
| 106 |
|
| 107 |
## Automated refactoring |
| 108 |
|
| 109 |
You have a large legacy codebase and want to use "Safe-PHP" functions throughout your project? PHPStan will help you |
| 110 |
find these functions but changing the namespace of the functions one function at a time might be a tedious task. |
| 111 |
|
| 112 |
Fortunately, Safe comes bundled with a "Rector" configuration file. [](https://github.com/rectorphp/rectorRector](https://github.com/rectorphp/rector](https://github.com/rectorphp/rector) is a command-line |
| 113 |
tool that performs instant refactoring of your application. |
| 114 |
|
| 115 |
Run |
| 116 |
|
| 117 |
```bash |
| 118 |
$ composer require --dev rector/rector:^0.7 |
| 119 |
``` |
| 120 |
|
| 121 |
to install `rector/rector`. |
| 122 |
|
| 123 |
Run |
| 124 |
|
| 125 |
```bash |
| 126 |
vendor/bin/rector process src/ --config vendor/thecodingmachine/safe/rector-migrate-0.7.php |
| 127 |
``` |
| 128 |
|
| 129 |
to run `rector/rector`. |
| 130 |
|
| 131 |
*Note:* do not forget to replace "src/" with the path to your source directory. |
| 132 |
|
| 133 |
**Important:** the refactoring only performs a "dumb" replacement of functions. It will not modify the way |
| 134 |
"false" return values are handled. So if your code was already performing error handling, you will have to deal |
| 135 |
with it manually. |
| 136 |
|
| 137 |
Especially, you should look for error handling that was already performed, like: |
| 138 |
|
| 139 |
```php |
| 140 |
if (!mkdir($dirPath)) { |
| 141 |
// Do something on error |
| 142 |
} |
| 143 |
``` |
| 144 |
|
| 145 |
This code will be refactored by Rector to: |
| 146 |
|
| 147 |
```php |
| 148 |
if (!\Safe\mkdir($dirPath)) { |
| 149 |
// Do something on error |
| 150 |
} |
| 151 |
``` |
| 152 |
|
| 153 |
You should then (manually) refactor it to: |
| 154 |
|
| 155 |
```php |
| 156 |
try { |
| 157 |
\Safe\mkdir($dirPath)); |
| 158 |
} catch (\Safe\FilesystemException $e) { |
| 159 |
// Do something on error |
| 160 |
} |
| 161 |
``` |
| 162 |
|
| 163 |
## Performance impact |
| 164 |
|
| 165 |
Safe is loading 1000+ functions from ~85 files on each request. Yet, the performance impact of this loading is quite low. |
| 166 |
|
| 167 |
In case you worry, using Safe will "cost" you ~700µs on each request. The [](performance/README.mdperformance section](performance/README.md](performance/README.md) |
| 168 |
contains more information regarding the way we tested the performance impact of Safe. |
| 169 |
|
| 170 |
## Learn more |
| 171 |
|
| 172 |
Read [](https://thecodingmachine.io/introducing-safe-phpthe release article on TheCodingMachine's blog](https://thecodingmachine.io/introducing-safe-php](https://thecodingmachine.io/introducing-safe-php) if you want to |
| 173 |
learn more about what triggered the development of Safe-PHP. |
| 174 |
|
| 175 |
## Contributing |
| 176 |
|
| 177 |
The files that contain all the functions are auto-generated from the PHP doc. |
| 178 |
Read the [](CONTRIBUTING.mdCONTRIBUTING.md](CONTRIBUTING.md](CONTRIBUTING.md) file to learn how to regenerate these files and to contribute to this library. |
| 179 |
|