PHP Articles
PHP Object __invoke()
What is the __invoke() method used for in objects? The __invoke() method is a PHP magic method that allows an object to be called as if it were a function. It’s great for maintaining state and focusing on a single calculation.
Let’s take a look a more practical use. In this example we will use it for issuing API requests:
class HttpRequest {
public function __construct(private string $url)
{
}
public function __invoke($data) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
$amazon = new HttpRequest('https://api.example.com/amazon/store');
$temu = new HttpRequest('https://api.example.com/temu/store');
$amazonResponse = $amazon(['item_id'=>12333]);
$temuResponse = $amazon(['item_id'=>12333]);
PHP Null Coalescing Assignment (??=)
PHP Null-Coalescing Assignment (??=)
The PHP Null-Coalescing Assignment operator ??= is a shorthand that combines the null coalescing operator ?? with an assignment = operator.
Some benefits of the null coalescing assignment operator is it reduces code verbosity significantly. It’s readable with clear intent for setting defaults. It’s safe and avoids undefined variable notices. It’s efficient with a single operation instead of multiple lines.
Below is 3 different ways of accomplishing the same objective. The value of $variable will be 30. The first example uses the ??= (null-coalescing assignment) operator. Notice it's a shorthand for the other 2 similar methods:
// Null-Coalescing Assignment (all-in-one)
$variable ??= 30;
// Null Coalescing combined with Assignment
$variable = $variable ?? 30;
// Ternary Operator combined with Assignment
$variable = $variable ? $variable : 30;
Here are a few different ways of using the Null-Coalescing Assignment operator ??=
// Variable is null
$name = null;
$name ??= 'Peter';
echo $name; // Output: 'Peter'
// Variable already has a value
$count = 25;
$count ??= 30;
echo $count; // Output: 25 (unchanged)
// Variable doesn't exist
$location ??= 'Chicago';
echo $location; // Output: 'Chicago'
What is the PHP Spread Operator (...)
Spread Operator (...)
The PHP Spread Operator (...) is a powerful feature introduced in PHP 5.6 that allows you to "unpack" or "spread" elements from arrays and traversable objects. It's also known as the splat operator or unpacking operator.
The spread operator is particularly useful in modern PHP development for creating more expressive and efficient code when working with arrays and variable function arguments.
Key Benefits of the PHP Spread Operator
-
Cleaner syntax for array merging and function calls
-
Better performance in some cases compared to array_merge()
-
More readable code when dealing with multiple arrays
-
Flexible for both array operations and function arguments
Here are a few common use cases for the operator:
Unpack and Merge Multiple Arrays
Here are a couple of examples for unpacking and merging arrays.
$array1 = [1, 2, 3];
$array2 = [4, 5, 6];
$array3 = [7, 8, 9];
$merged = [...$array1, ...$array2, ...$array3];
// Array (
// [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5
// [5] => 6 [6] => 7 [7] => 8 [8] => 9
// )
$original = ['b', 'c', 'd'];
$newArray = ['a', ...$original, 'e'];
// Array (
// [0] => a [1] => b [2] => c [3] => d [4] => e
// )
Function Arguments
// FUNCTION ARGUMENTS
function addNumbers($a, $b, $c) {
return $a + $b + $c;
}
$numbers = [2, 4, 6];
$result = addNumbers(...$numbers);
echo $result; // Output: 12
function sum(...$numbers) {
return array_sum($numbers);
}
$values = [1, 2, 3, 4, 5];
echo sum(...$values); // Output: 15
Combining With Regular Elements
// Combining with Regular Elements
$first = [1, 2];
$last = [5, 6];
$combined = [...$first, 3, 4, ...$last];
print_r($combined);
// Output: Array ([0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 [5] => 6)
Usage With Associative Arrays
Notice that array keys are also considered making it perfect for merging associative arrays as well.
//With Associative Arrays
$config1 = ['host' => 'localhost', 'port' => 3306];
$config2 = ['username' => 'root', 'password' => 'secret'];
$finalConfig = [...$config1, ...$config2];
print_r($finalConfig);
// Output: Array ([host] => localhost [port] => 3306 [username] => root [password] => secret)
String To Array Conversion
// String To Array Conversion
$string = "hello";
$chars = [...$string];
print_r($chars);
// Output: Array ([0] => h [1] => e [2] => l [3] => l [4] => o)
Practical Example of Merging 2 Configuration Arrays
Remember that when merging 2 associative arrays using the spread operator and brackets syntax, the second array in the will overwrite values with matching keys. Take a look at the ‘debug’ key below:
// Configuration Merging
$defaultConfig = [
'debug' => false,
'cache' => true,
'timeout' => 30
];
$userConfig = [
'debug' => true,
'database' => 'mysql'
];
$finalConfig = [...$defaultConfig, ...$userConfig];
print_r($finalConfig);
// Output: Array ([debug] => true [cache] => true [timeout] => 30 [database] => mysql)
Laravel Eloquent ORM Polymorphism Examples
Laravel Eloquent ORM Polymorphism
In Laravel's Eloquent ORM, polymorphism refers to polymorphic relationships, which allow a model to belong to more than one type of model using a single association.
This is useful when you have a relationship that can apply to multiple different models without having to create separate foreign keys or pivot tables for each type. Examples would be Users and Products that each have one or more images.
Let’s take a look at different types of polymorphic relationships commonly used in Laravel Eloquent ORM.
One-to-One (Polymorphic)
Example: An Image model could belong to either a User or a Product.
// Image model
public function imageable()
{
return $this->morphTo();
}
// User model
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
// Product model
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
Table Definitions: User, Product models can both have an Image. Notice Image.imageable_id and Image.imageable_type. This can join on both User.id and Product.id tables.
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255) UNIQUE
);
CREATE TABLE products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
price DECIMAL(10,2)
);
CREATE TABLE images (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
url VARCHAR(255),
imageable_id BIGINT UNSIGNED, -- FK ID (user.id or product.id)
imageable_type VARCHAR(255), -- Model class (App\Models\User/App\Models\Product)
INDEX idx_imageable (imageable_id, imageable_type)
);
One-to-Many (Polymorphic)
Example: A Comment model could belong to both a Post and a Video.
// Comment model
public function commentable()
{
return $this->morphTo();
}
// Post model
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
// Video model
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
Table Definitions: Post, Video models can both have a Comment. Notice Comment.commentable_id and Comment.commentable_type. This can join on both Post.id and Video.id tables.
CREATE TABLE posts (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
body TEXT
);
CREATE TABLE videos (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
url VARCHAR(255)
);
CREATE TABLE comments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
body TEXT,
commentable_id BIGINT UNSIGNED, -- FK ID (post.id|video.id)
commentable_type VARCHAR(255), -- Model class (App\Models\Post|App\Models\Video)
INDEX idx_commentable (commentable_id, commentable_type)
);
Many-to-Many (Polymorphic)
Example: A Tag model can be applied to both Post and Video.
// Tag model
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
// Post model
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
// Video model
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
Table Definitions: Both Post, Video models can have multiple Tags. Also, a Tag can belong to multiple Post, Video models. Notice the pivot table taggables…
CREATE TABLE tags (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) UNIQUE
);
CREATE TABLE posts (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
body TEXT
);
CREATE TABLE videos (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
url VARCHAR(255)
);
CREATE TABLE taggables (
tag_id BIGINT UNSIGNED, -- FK to tags.id
taggable_id BIGINT UNSIGNED, -- FK ID (post.id or video.id)
taggable_type VARCHAR(255), -- Model class (App\Models\Post / App\Models\Video)
PRIMARY KEY (tag_id, taggable_id, taggable_type),
INDEX idx_taggable (taggable_id, taggable_type)
);
PHP Spaceship Operator (<=>)
PHP Spaceship Operator (<=>)
The PHP Spaceship Operator (<=>) is a comparison operator that provides a three-way comparison between two values. It's also known as the "combined comparison operator."
How it works:
The operator returns -1, 0, or 1 depending on the condition.
-1 => if the left operand is less than the right operand
0 => if both operands are equal
1 => if the left operand is greater than the right operand
Sample Usage:
$a = 1;
$b = 1;
$result = $a <=> $b; // result is 0
$b = 2;
$result = $a <=> $b; // result is -1
$b = 0;
$result = $a <=> $b; // $result is 1
Spaceship Operator vs using if-else:
// using if/else
if ($a < $b) {
return -1;
} elseif ($a > $b) {
return 1;
} else {
return 0;
}
// using spaceship operator
return $a <=> $b;
Benefits:
- Concise: Replaces complex if-else chains
- Readable: Clear intention for comparison
- Consistent: Always returns -1, 0, or 1
- Type-safe: Handles different data types predictably
PHP Match Expression (match)
PHP Match Expression (match)
The PHP match expression is a powerful feature introduced in PHP 8.0 that provides a more concise and flexible alternative to switch statements.
Basic Match Syntax
$result = match ($value) {
pattern1 => expression1,
pattern2 => expression2,
// ...
default => default_expression,
};
Comparison switch vs match
// switch statement
switch ($statusCode) {
case 200:
$message = 'OK';
break;
case 404:
$message = 'Not Found';
break;
case 500:
$message = 'Server Error';
break;
default:
$message = 'Unknown';
}
// match equivalent
$message = match ($statusCode) {
200 => 'OK',
404 => 'Not Found',
500 => 'Server Error',
default => 'Unknown',
};
Various Usage Examples:
// multiple conditions
$result = match ($httpCode) {
200, 201, 202 => 'Success',
400, 401, 403 => 'Client Error',
500, 501, 502 => 'Server Error',
default => 'Unknown',
};
// Match uses strict comparison (===)
$result = match ($value) {
0 => 'Integer zero',
'0' => 'String zero',
false => 'Boolean false',
default => 'Other',
};
// Complex Expressions
$age = 25;
$category = match (true) {
$age < 13 => 'Child',
$age < 20 => 'Teenager',
$age < 65 => 'Adult',
default => 'Senior',
};
// returning different types
function processValue($value) {
return match ($value) {
'int' => 42,
'string' => 'Hello World',
'array' => [1, 2, 3],
'bool' => true,
default => null,
};
}
// Using with arrays
$user = [
'role' => 'admin',
'status' => 'active'
];
$permissions = match ($user['role']) {
'admin' => ['read', 'write', 'delete'],
'editor' => ['read', 'write'],
'viewer' => ['read'],
default => [],
};
// nested match expressions
$result = match ($type) {
'number' => match ($value) {
$value > 0 => 'Positive',
$value < 0 => 'Negative',
default => 'Zero',
},
'string' => 'String type',
default => 'Unknown type',
};
// Conditional Logic in Patterns
$score = 85;
$grade = match (true) {
$score >= 90 => 'A',
$score >= 80 => 'B',
$score >= 70 => 'C',
$score >= 60 => 'D',
default => 'F',
};
Advantages Over Switch
- Returns a value - Can be assigned directly to variables
- No fall-through - Prevents accidental bugs
- Strict comparisons - More predictable behavior
- More concise - Less boilerplate code
- Better error handling - Throws UnhandledMatchError for unhandled cases
Important Notes
- Match expressions must be exhaustive or include a default case
- Throws UnhandledMatchError if no pattern matches and no default is provided
- Each arm must be a single expression (use anonymous functions for complex logic)
- Patterns are evaluated in order, first match wins
The match expression is a significant improvement that makes conditional logic more readable, safer, and more expressive in modern PHP code.
PHP Null Coalescing Nesting AND Chaining
PHP Null Coalescing Nesting AND Chaining
The null coalescing operator is essential for writing clean, safe PHP code that gracefully handles missing data without generating warnings or errors.
Null Coalescing Assignment Operator (??)
// ternary operator usage
$value = isset($a) ? $a : (isset($b) ? $b : $c);
// null-coalescing shorthand for above solution
$value = $a ?? $b ?? $c;
Null Coalescing Assignment Operator (??=)
// Only assign if variable is null or doesn't exist
$array['key'] ??= 'default value';
$user->name ??= 'Anonymous';
// Equivalent to:
if (!isset($array['key'])) {
$array['key'] = 'default value';
}
Use with error suppression
$value = $object->property->nestedProperty ?: 'default';
echo "Value: ".$value."\r\n"; //Warning is thrown
$value = @$object->property->nestedProperty ?? 'default';
echo "Value: ".$value."\r\n"; // Value: default
PHP Null Coalescing (??) vs Elvis Operator (?:)
PHP Null Coalescing (??) vs Elvis Operator (?:)
Both operators handle null/empty values but with important differences in behavior.
Null Coalescing (??)
The null coalescing operator was introduced in PHP 7. It returns the first operand if it exists and is not NULL, otherwise it returns the second operand.
- Checks if left operand is set and not NULL
- Returns left operand if it exists and is not NULL
- Returns right operand otherwise
- Does NOT consider empty strings, 0, false as "empty"
// Sample usage below. If is $beast IS NOT set a warning will be thrown.
$pet = "puppy";
$beast = null;
echo "Result: " . ($beast ?? $pet) ."\r\n"; // Result: puppy
Elvis Operator (?:)
The ternary operator shorthand checks for truthy values (not just NULL).
- Checks if left operand is truthy (not empty, not zero, not false, not NULL)
- Returns left operand if it's truthy
- Returns right operand otherwise
- Considers empty strings, 0, false as "empty"
// Ternary Operator sample usage
$pet = "kitty";
$beast = "";
echo "Result: " . ($beast ?: $pet) ."\r\n"; // Result: kitty
$beast = "tiger";
echo "Result: " . ($beast ?: $pet) ."\r\n"; // Result: tiger
Choose an operator to use based on whether you need to distinguish between NULL and other "empty" values in your specific use case.
- Use Null Coalescing when you only want to handle NULL values specifically
- Use Elvis Operator when you want to handle all "empty" values
- Null Coalescing is safer for undefined variables (no warnings)
- Consider using null coalescing with arrays and object properties that might not exist
PHP Null-Safe(Null-Conditional) Operator (?->)
Null-Safe(Null-Conditional) Operator (?->)
The null-safe operator (?->), also known as the null-conditional operator, is a feature in several programming languages that allows you to safely access members of an object without explicitly checking for null references. If the object is null, the expression returns null instead of throwing a NullPointerException.
Basic Property Access
class User {
public ?Profile $profile = null;
}
class Profile {
public string $name = "John Doe";
public ?Address $address = null;
public function getName(): string {
return $this->name;
}
}
class Address {
public string $street = "123 Main St";
}
$user = new User();
// Safe property access
$name = $user?->profile?->name; // Returns null instead of error
var_dump($name); // NULL
// With actual data
$user->profile = new Profile();
$name = $user?->profile?->name;
var_dump($name); // string(8) "John Doe"
Method Calls
$user = new User();
// Safe method call
$result = $user?->profile?->getName(); // Returns null
var_dump($result); // NULL
$user->profile = new Profile();
$result = $user?->profile?->getName();
var_dump($result); // string(8) "John Doe"
Array Access with Null-Safe
class DataContainer {
public ?array $items = null;
public function getItems(): ?array {
return $this->items;
}
}
$container = new DataContainer();
// Safe array access
$firstItem = $container?->items[0] ?? 'default';
var_dump($firstItem); // string(7) "default"
$container->items = ['apple', 'banana'];
$firstItem = $container?->items[0] ?? 'default';
var_dump($firstItem); // string(5) "apple"
Chaining Multiple Levels
$user = new User();
$user->profile = new Profile();
$user->profile->address = new Address();
// Deep chaining
$street = $user?->profile?->address?->street;
var_dump($street); // string(11) "123 Main St"
// With null in chain
$user->profile->address = null;
$street = $user?->profile?->address?->street;
var_dump($street); // NULL
Used with Null-Coalescing Operator
$user = null;
// Null-safe + null coalescing
$userName = $user?->profile?->name ?? 'Guest';
var_dump($userName); // string(5) "Guest"
$user = new User();
$userName = $user?->profile?->name ?? 'Guest';
var_dump($userName); // string(5) "Guest"
$user->profile = new Profile();
$userName = $user?->profile?->name ?? 'Guest';
var_dump($userName); // string(8) "John Doe"
Used with Ternary Operator
$user = null;
$displayName = $user?->profile?->name ?: 'Anonymous User';
var_dump($displayName); // string(15) "Anonymous User"
Key Benefits
1. Reduces Boilerplate: Eliminates repetitive null checks
2. Prevents NullPointerExceptions: Safe access to nested properties
3. Cleaner Code: More readable and concise syntax
4. Short-Circuiting: Stops evaluation when null is encountered
5. Method Safety: Safe method calls on potentially null objects
Limitations
1. Read-Only: Cannot be used for assignment
2. Context-Specific: Only works in expressions, not statements
3. Language Support: Syntax varies between languages
4. Debugging: Can make null-related bugs harder to trace
The null-safe operator is particularly useful in scenarios with deep object graphs, API responses, configuration objects, and any situation where multiple nested objects might be null.
Using PHP Class Interface
In PHP, an interface defines a contract or blueprint that any class implementing it must follow.
It specifies method signatures (the names, parameters, and visibility of methods), however does not implement the methods.
A class that implements an interface must define all of the methods declared in the interface.
Interfaces help achieve abstraction and multiple inheritance (since a class can implement multiple interfaces).
Example:
// Define an interface
interface Employee {
public function clockIn(string $message);
}
// Implement the interface in a class
class Engineer implements Employee {
public function clockIn(string $message) {
echo "Engineer Clock In: " . PHP_EOL;
}
}
// Another class implementing the same interface
class Mechanic implements Employee {
public function clockIn(string $message) {
echo "Mechanic Clock In: " . PHP_EOL;
}
}
// Usage
function processTask(Employee $employee) {
$employee->clockIn("Task has been processed!");
}
// You can swap implementations easily
$engineer = new Engineer();
$mechanic = new Mechanic();
processTask($engineer); // Clock In Engineer
processTask($mechanic); // Clock In Mechanic
What interfaces can contain:
- Method declarations (no body/implementation)
- Constants (e.g. const MAX_LIMIT = 100;)
What interfaces cannot contain:
- Properties/variables
- Constructors with implementation
- Method bodies
Example:
interface ExampleInterface {
// Allowed
public function doSomething();
// Allowed
const VERSION = "1.0";
// Not allowed: properties inside interfaces
// public $name; // This will cause an error
}