Stars: 151
Forks: 7
Pull Requests: 21
Issues: 1
Watchers: 3
Last Updated: 2023-06-28 12:28:06
Strict Typing on inline variables for PHP
License:
Languages: PHP, Makefile, Dockerfile
Strictus brings strict typing for local variables into PHP.
With Strictus, you can control the types of local variables using different patterns.
💣
The problem:
PHP has no support for strongly typed Local Variables.
Here is an illustrative example of a basic mistake:
<?php
//Rule: Active discount of 10% or 25% for orders from $50
$total = 82.50;
$discount = 0.10; //float
if ($total >= 50) {
$discount = '25%'; //replacing a float value with string value 🤦🏻♂️
}
$total = $total - ($total * $discount); //💥 Error: A float cannot be multiplied by a string
In the code above, nothing prevents overriding the float
value of $discount
a string
value, causing a bug.
👍
The solution:
Let's rewrite the previous example using Strictus and strongly typed variables:
<?php
//Rule: Active discount of 10% or 25% for orders from $50
use Strictus\Strictus;
$total = Strictus::float(82.50);
$discount = Strictus::float(0.10);
if ($total() >= 50) {
$discount(0.25); //updates the $discount value
}
$total($total() - ($total() * $discount()));
echo $total(); //61.875
In the code above, the variable $discount
is an instance of StrictusFloat::class
and it only accepts float
values.
An Exception StrictusTypeException
is thrown when we try to assign anything that is not of type float
, like a string
for example.
See this example:
<?php
use Strictus\Strictus;
$discount = Strictus::float(0.10);
$discount('25%'); //A StrictusTypeException stops the code execution
You can install the package via composer:
composer require strictus/strictus
Requires: PHP 8.1+
There are a few different patterns you can use to work Strictus
.
To create a variable, simply call Strictus::*method*()
, replacing the *method*
with the type you want to enforce.
For example:
<?php
use Strictus\Strictus;
//creates a string
$name = Strictus::string('Wendell');
//creates a nullable string
$comment = Strictus::nullableString(null);
//creates an int
$score = Strictus::int(100);
//creates a boolean
$isActive = Strictus::boolean(true);
//creates an array
$authors = Strictus::array(['Wendell', 'Christopher']);
//creates an object
$person = Strictus::object((object) ['name' => 'Wendell', 'country' => 'BR']);
//instantiates a class
$calculator = Strictus::instance(CalculatorClass::class, new CalculatorClass());
//instantiates an enum
$role = Strictus::enum(Role::class, Role::CONTRIBUTOR);
class CalculatorClass
{
//...
}
enum Role
{
case CONTRIBUTOR;
}
💡 Check out all the available variable methods.
To retrieve the variable value, just call it like a function:
echo $name(); //Wendell
echo $score() - 10; //90
if ($isActive()) {
//do your logic here
}
echo implode($authors(), ';'); //Wendell;Christopher
Alternatively, you can use the $variable
like a Value Object:
$name = Strictus::string('Christopher'); //creates a string
echo $name->value; //Christopher
To update the variable value, call it like a function passing the new value as the argument:
$score = Strictus::int(100);
$score($score() - 20); //updates $score
echo $score(); //80
Alternatively, you can use the $variable
like a Value Object:
$score = Strictus::int(100);
$score->value = 0;
echo $score(); //0
You can use the following methods to create single type variables:
Type | Nullable | Method |
---|---|---|
String | No | Strictus::string($value) |
String | Yes | Strictus::string($value, true) |
String | Yes | Strictus::nullableString($value) |
Integer | No | Strictus::int($value) |
Integer | Yes | Strictus::int($value, true) |
Integer | Yes | Strictus::nullableInt($value) |
Float | No | Strictus::float($value) |
Float | Yes | Strictus::float($value, true) |
Float | Yes | Strictus::nullableFloat($value, true) |
Boolean | No | Strictus::boolean($value) |
Boolean | Yes | Strictus::boolean($value, true) |
Boolean | Yes | Strictus::nullableBoolean($value) |
Array | No | Strictus::array($value) |
Array | Yes | Strictus::array($value, true) |
Array | Yes | Strictus::nullableArray($value) |
Object | No | Strictus::object($value) |
Object | Yes | Strictus::object($value, true) |
Object | Yes | Strictus::nullableObject($value) |
Class Type | No | Strictus::instance($instanceType, $value) |
Class Type | Yes | Strictus::instance($instanceType, $value, true) |
Class Type | Yes | Strictus::nullableInstance($instanceType, $value) |
Enum Type | No | Strictus::enum($enumType, $value) |
Enum Type | Yes | Strictus::enum($enumType, $value, true) |
Enum Type | Yes | Strictus::nullableEnum($enumType, $value) |
Strictus
also supports union types:
use Strictus\Enums\Type;
$unionTypesVariable = Strictus::union([Type::INT, Type::STRING], 'foo');
echo $unionTypesVariable->value; //foo
echo $unionTypesVariable(); //foo
// Update variable
$unionTypesVariable->value = 100;
echo $unionTypesVariable->value; //100
// Thrown an exception if the value is wrong union types
$unionTypesVariable->value = false; //StrictusTypeException
If you want to create immutable variables, you can use the ->immutable()
method. If you try to assign a new value
to an Immutable Variable, an Strictus\Exceptions\ImmutableStrictusException
exception will be thrown:
$immutableScore = Strictus::int(100)->immutable();
$immutableScore(50); //ImmutableStrictusException
$immutableScore->value = 50; //ImmutableStrictusException
If you try to assign a value that doesn't match the type of the created variable, an
Strictus\Exceptions\StrictusTypeException
exception will be thrown:
$score = Strictus::int(100);
$score('one hundred'); //StrictusTypeException
$score->value = false; //StrictusTypeException
If you try to assign a new value to an Immutable Variable, an
Strictus\Exceptions\ImmutableStrictusException
exception will be thrown:
$immutableScore = Strictus::int(100)->immutable();
$immutableScore(50); //ImmutableStrictusException
$immutableScore->value = 50; //ImmutableStrictusException
Following a discussion on Twitter between Christopher Miller and Wendell Adriel around the lack of strongly typed local variables for PHP we quickly decided a package was the right approach whilst we could get an RFC into the core.
We welcome contributions!
Please visit the Contributing Guide to learn more about contributing to Strictus.