Skip to content

Instantly share code, notes, and snippets.

@MrPunyapal
Last active November 14, 2024 11:14
Show Gist options
  • Save MrPunyapal/139779b743eec108a292de95b3f958f8 to your computer and use it in GitHub Desktop.
Save MrPunyapal/139779b743eec108a292de95b3f958f8 to your computer and use it in GitHub Desktop.
Types In PHP

Atomic Types (Built-in and Scalar)

// Built-in types
$variable = null;             // null type

// Scalar types
$boolVar = true;              // bool type
$intVar = 42;                 // int type
$floatVar = 3.14;             // float type
$stringVar = "Hello, World!"; // string type

// array type
$arrayVar = [1, 2, 3];

// object type
$objectVar = new stdClass();

// resource type
$resourceVar = fopen("file.txt", "r");

// Value types
$falseVar = false;  // false
$trueVar = true;    // true
// never type
function willNeverReturn(): never {
    exit();
}

// void type
function doSomething(): void {
    echo "Doing something";
}

// Relative class types
class MyClass {
    public function createSelf(): self {
        return new self();
    }
}

class ParentClass {}
class ChildClass extends ParentClass {
    public function getParent(): parent {
        return new ParentClass();
    }
}

class StaticClass {
    public static function createStatic(): static {
        return new static();
    }
}

User-defined Types

// Interfaces
interface MyInterface {
    public function doSomething();
}

// Classes
class MyClass {
    public function doSomething() {
        echo "Doing something";
    }
}

// Enumerations
enum Status {
    case PENDING;
    case APPROVED;
    case REJECTED;
}
// callable type
function myFunction(): void {
    echo "This is callable";
}
$callableVar = 'myFunction';
$callableVar();

class CallableClass {
    public function __invoke() {
        echo "This is callable from a class";
    }
}
$callableInstance = new CallableClass();
$callableInstance();

Composite Types and Type Aliases

// Intersection types
interface InterfaceA {}
interface InterfaceB {}
class MyClass implements InterfaceA, InterfaceB {}
function acceptsIntersection(InterfaceA&InterfaceB $obj) {
    // Function body
}
$instance = new MyClass();
acceptsIntersection($instance);
// Union types
function acceptsUnion(int|string $value) {
    // Function body
}
acceptsUnion(42);
acceptsUnion("Hello");

// Type Aliases
function acceptsMixed(mixed $value) {
    // Function body
}
acceptsMixed(42);
acceptsMixed("Hello");
acceptsMixed([1, 2, 3]);

function acceptsIterable(iterable $iterableVar) {
    // Function body
}
acceptsIterable([1, 2, 3]);
acceptsIterable(new ArrayObject([1, 2, 3]));

PHP has a "strict types" mode that ensures type declarations are strictly enforced. This mode is activated on a per-file basis and affects type declarations for parameters, return values, and class properties. To enable strict types, add declare(strict_types=1); at the top of the PHP file.

Enabling Strict Types

<?php
declare(strict_types=1);

Strict Types with Function Parameters and Return Types

<?php
declare(strict_types=1);

function add(int $a, int $b): int {
    return $a + $b;
}

// Usage
echo add(5, 3); // 8
echo add(5.5, 3.2); // TypeError: Argument 1 passed to add() must be of the type integer, float given

Strict Types with Class Properties

<?php
declare(strict_types=1);

class User {
    private string $name;
    private int $age;

    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }

    public function getName(): string {
        return $this->name;
    }

    public function getAge(): int {
        return $this->age;
    }
}

// Usage
$user = new User("Alice", 30);
echo $user->getName(); // Alice
echo $user->getAge();  // 30

$newUser = new User("Bob", "thirty"); // TypeError: Argument 2 passed to User::__construct() must be of the type integer, string given

Strict Types with Return Types in Methods

<?php
declare(strict_types=1);

class Calculator {
    public function multiply(float $a, float $b): float {
        return $a * $b;
    }
}

// Usage
$calculator = new Calculator();
echo $calculator->multiply(2.5, 4.0); // 10.0
echo $calculator->multiply(2, "3"); // TypeError: Argument 2 passed to Calculator::multiply() must be of the type float, string given

Typed Arrays OR Array Shape in PHP

Typed Array of Objects

<?php

class Book {
    private string $title;
    
    public function __construct(string $title) {
        $this->title = $title;
    }

    public function getTitle(): string {
        return $this->title;
    }
}

/**
 * @param Book[] $books
 * @return string[]
 */
function getBookTitles(array $books): array {
    return array_map(fn(Book $book) => $book->getTitle(), $books);
}

// Usage
$books = [new Book('1984'), new Book('Brave New World')];
print_r(getBookTitles($books)); // Output: Array ( [0] => 1984 [1] => Brave New World )

List Type for Integer Operations

<?php
/**
 * @param list<int> $numbers
 * @return int
 */
function sumNumbers(array $numbers): int {
    return array_sum($numbers);
}

// Usage
$numbers = [1, 2, 3, 4];
echo sumNumbers($numbers); // Output: 10

Associative Arrays with Mixed Types

<?php
/**
 * @param array<string, mixed> $data
 * @return string
 */
function stringifyAssociativeArray(array $data): string {
    return json_encode($data);
}

// Usage
$data = ['name' => 'Alice', 'age' => 30, 'isMember' => true];
echo stringifyAssociativeArray($data); // Output: {"name":"Alice","age":30,"isMember":true}
### defined keys
<?php

/**
 * @param array{key1: int, key2: int} $data
 * 
 * @return int 
 */
function definedValues(array $data): int {
  return $data['key1'] + $data['key2'];
}

Non-Empty Arrays of Strings

<?php
/**
 * @param non-empty-array<string> $messages
 * @return string
 */
function getFirstMessage(array $messages): string {
    return $messages[0];
}

// Usage
$messages = ["Hello", "World", "PHP"];
echo getFirstMessage($messages); // Output: Hello

Typed Array of Callable Functions

<?php
/**
 * @param array<callable> $callbacks
 */
function executeCallbacks(array $callbacks): void {
    foreach ($callbacks as $callback) {
        $callback();
    }
}

// Usage
$callbacks = [
    fn() => print("First\n"),
    fn() => print("Second\n")
];
executeCallbacks($callbacks); // Output: First\nSecond\n

In PHP, generics are not natively supported in the same way they are in some other languages like Java or TypeScript. However, with the release of PHP 8, some limited support for generics can be achieved using a combination of docblock annotations and runtime checks. Here are some examples of how you might use generic types in PHP along with explanations.

Generic Type Example with Collections

/**
 * @template T
 */
class Collection {
    /** @var T[] */
    private array $items = [];

    /**
     * @param T $item
     */
    public function add($item): void {
        $this->items[] = $item;
    }

    /**
     * @return T[]
     */
    public function getAll(): array {
        return $this->items;
    }
}

// Usage
$intCollection = new Collection();
$intCollection->add(1);
$intCollection->add(2);
print_r($intCollection->getAll()); // [1, 2]

$stringCollection = new Collection();
$stringCollection->add("Hello");
$stringCollection->add("World");
print_r($stringCollection->getAll()); // ["Hello", "World"]

Generic Type Example with Constraints

/**
 * @template T of Animal
 */
class AnimalShelter {
    /** @var T[] */
    private array $animals = [];

    /**
     * @param T $animal
     */
    public function addAnimal($animal): void {
        $this->animals[] = $animal;
    }

    /**
     * @return T[]
     */
    public function getAnimals(): array {
        return $this->animals;
    }
}

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

// Usage
$dogShelter = new AnimalShelter();
$dogShelter->addAnimal(new Dog());
print_r($dogShelter->getAnimals()); // [Dog]

$catShelter = new AnimalShelter();
$catShelter->addAnimal(new Cat());
print_r($catShelter->getAnimals()); // [Cat]

Generic Type Example with Functions

/**
 * @template T
 * @param T[] $items
 * @return T
 */
function getFirst(array $items) {
    return $items[0];
}

// Usage
echo getFirst([1, 2, 3]); // 1
echo getFirst(["apple", "banana", "cherry"]); // apple

Generic Methods in Classes

class MathUtil {
    /**
     * @template T of int|float
     * @param T $a
     * @param T $b
     * @return T
     */
    public function add($a, $b) {
        return $a + $b;
    }
}

// Usage
$math = new MathUtil();
echo $math->add(1, 2); // 3
echo $math->add(1.5, 2.5); // 4.0

What is PHPStan?

PHPStan is a static analysis tool for PHP that helps detect errors in your code without executing it. It ensures type safety, catches potential bugs, and enforces best practices, making your code more robust and maintainable.

Key Features of PHPStan

  • Detects Type Errors: Ensures type correctness across your codebase.
  • Catches Potential Bugs: Finds issues that could lead to runtime errors.
  • Supports Generics: Provides support for generic types through annotations.
  • Integrates with CI/CD: Seamlessly integrates with continuous integration pipelines.

Installation via Composer

To install PHPStan, add it as a development dependency using Composer:

composer require --dev phpstan/phpstan

Basic Configuration

Create a phpstan.neon configuration file in your project root:

includes:
    - vendor/larastan/larastan/extension.neon

parameters:
    checkMissingIterableValueType: true

    level: max # 0-9

    paths:
        - app
        - config
        - bootstrap
        - database/factories
        - routes

Command to Run PHPStan

Execute PHPStan using the following command:

vendor/bin/phpstan analyse

Code Example with Type Issues

<?php
declare(strict_types=1);

function add(int $a, int $b): int {
    return $a + $b;
}

echo add(5, 3);       // Valid
echo add(5.5, 3.2);   // TypeError in strict mode
echo add("5", "3");   // TypeError in strict mode

Running PHPStan on the Code

vendor/bin/phpstan analyse src

Expected Output:

  Line   src/file.php
 ------ -----------------------------------------------------------------
  9      Parameter #1 $a of function add expects int, float given.
  9      Parameter #2 $b of function add expects int, float given.
 10      Parameter #1 $a of function add expects int, string given.
 10      Parameter #2 $b of function add expects int, string given.
 ------ -----------------------------------------------------------------

 [ERROR] Found 4 errors

Go to https://phpstan.org/try and check for all examples with differnt levels.

Level 0: Basic Checks

<?php
class Example {
    public function run() {
        $this->unknownMethod(); 
        // Fails because unknownMethod() does not exist
    }
}
function add($a, $b) {
    return $a + $b;
}
echo add(1); 
// Fails because add() is called with the wrong number of arguments

Level 1: Possibly Undefined Variables

<?php
function maybeUndefined() {
    if (rand(0, 1)) {
        $a = 1;
    }
    echo $a; 
    // Fails because $a might be undefined
}

echo $undefinedVariable; 
// Fails because $undefinedVariable is not defined

Level 2: Unknown Methods Checked on All Expressions

<?php
class AnotherClass {
    public function existingMethod() {}
}

$object = new AnotherClass();
$object->nonExistentMethod(); 
// Fails because nonExistentMethod() does not exist

Level 3: Return Types, Types Assigned to Properties

<?php
class ReturnType {
    public function getString(): string {
        return 123; 
        // Fails because the return type should be a string
    }
}

class PropertyType {
    /** @var int */
    private $value;

    public function setValue(string $value) {
        $this->value = $value; 
        // Fails because $value should be an integer
    }
}

Level 4: Basic Dead Code Checking

<?php
if ($object instanceof NonExistentClass) {
    echo 'This will never be true'; 
    // Fails because NonExistentClass does not exist
} else {
    echo 'This will always execute';
}

function test() {
    return;
    echo 'This will never be executed'; 
    // Fails because this is unreachable code
}

Level 5: Checking Types of Arguments Passed to Methods and Functions

<?php
function addNumbers(int $a, int $b) {
    return $a + $b;
}
echo addNumbers(1, 'two'); 
// Fails because 'two' is not an integer

Level 6: Report Missing Typehints

<?php
class TypehintExample {
    private $value; 
    // Fails because the typehint is missing

    public function setValue($value) {
        $this->value = $value; 
        // Fails because the typehint is missing
    }

    public function getValue() {
        return $this->value; 
        // Fails because the typehint is missing
    }
}

Level 7: Report Partially Wrong Union Types

<?php
class A {
    public function methodA():void {}
}

class B {
    public function methodB():void {}
}

function process(A|B|null $object): void {
    if ($object instanceof A || $object instanceof B) {
        $object->methodA(); 
        // Fails because $object might be an instance of B, which does not have methodA
    }
}

Level 8: Report Calling Methods and Accessing Properties on Nullable Types

<?php
function getLength(?string $str): int {
    return strlen($str); 
    // Fails because $str can be null, and strlen() does not accept null
}

Level 9: Be Strict About the mixed Type

<?php
function handleMixed(mixed $value):void {
    echo $value; 
    // Fails because operations on mixed are not allowed
}

These examples illustrate specific issues that PHPStan detects at each level, aligned with the detailed checks you provided.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment