Web Development Articles
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
self:: vs static:: in PHP
self:: vs static:: in PHP is all about inheritance and late static binding. It’s used when you are dealing with overrides and methods coming from the parent class.
self::
- Refers to the class where the method is defined.
- Does not consider inheritance overrides.
static::
- Refers to the class that is actually called at runtime.
- This is called late static binding.
- Allows child classes to override static properties/methods and still be respected.
class Automobile {
public static $type = "Automobile";
public static function getTypeUsingSelf() {
return self::$type; // bound to Automobile
}
public static function getTypeUsingStatic() {
return static::$type; // late static binding
}
}
class Car extends Automobile {
public static $type = "Car";
}
class Truck extends Automobile {
public static $type = "Truck";
}
class Van extends Automobile {
public static $type = "Van";
}
// ------------------- USAGE -------------------
echo Car::getTypeUsingSelf(); // Output: Automobile
echo "<br>";
echo Car::getTypeUsingStatic(); // Output: Car
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.
JavaScript Promises and Asynchronous Handling Explained
Have you ever made an HTTP API request in JavaScript, only to find the data you need is mysteriously unavailable? You're confident the server-side API works, as you've tested it repeatedly. The issue often lies in JavaScript's asynchronous nature.
JavaScript doesn't pause execution to wait for slow operations, like API calls, to complete. Instead, it triggers the request and immediately moves on to the next line of code. By the time your script tries to use the response data, the request may not have finished.
This is where the Promise object becomes essential. A Promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Let's explore how to use them effectively.
First let’s take a look at the most commonly used methods available on a JavaScript Promise object (Promise.then()):
1. Basic Promise Handling with .then()
The .then() method is the primary way to interact with a Promise. You can pass it two functions: one to handle a successful resolution and another to handle a rejection.
let name = "Mary";
const promise = new Promise((resolve, reject) => {
name == "Mary" ? resolve(name) : reject(name);
});
// promise.then(onFulfilled, onRejected)
promise.then(
x => console.log(`Name resolved: ${x}`), // Called if resolved
x => console.log(`Name rejected: ${x}`) // Called if rejected
);
// Expected output: "Name resolved: Mary"
2. Chaining Multiple .then() Methods
Promises are powerful because they can be chained, allowing you to define a sequence of asynchronous steps. Each .then() in the chain receives the result from the previous one.
let name = "Mary";
// Function passed to the Promise constructor
const analyzeName = (resolve, reject) => {
name == "Mary" ? resolve(name) : reject(name);
};
// Handler for a resolved promise
const nameResolved = (x) => {
console.log(`Name resolved: ${x}`);
return x; // Pass the value to the next .then()
};
// Handler for a rejected promise
const nameRejected = (x) => {
console.log(`Name rejected: ${x}`);
return x; // Even on rejection, we can pass the value down the chain
};
// Subsequent steps in the process
const step2 = (x) => {
console.log(`Step 2: Processing ${x}`);
return x;
};
const step3 = (x) => {
console.log(`Final Step: ${x}`);
return x;
};
const namePromise = new Promise(analyzeName);
namePromise
.then(nameResolved, nameRejected) // Handles the initial result
.then(step2) // Receives the value from nameResolved/nameRejected
.then(step3); // Receives the value from step2
// Console Output:
// Name resolved: Mary
// Step 2: Processing Mary
// Final Step: Mary
By using Promises and its .then() method, you gain precise control over the flow of your asynchronous code. This ensures that each step waits for the previous one to complete before executing, which is the fundamental solution to the "missing data" problem in async operations like API calls. For modern, cleaner syntax, consider using async/await, which is built on top of Promises.
Here is a comprehensive example that builds on the previous explanation by adding .catch() and .finally() methods, which are crucial for robust promise handling.
While .then() handles successful outcomes, a complete promise chain needs ways to handle errors and cleanup operations. This is where .catch() and .finally() come in.
- catch() - Handles any rejection that occurs in the chain
- finally() - Executes regardless of success or failure, perfect for cleanup
Below I’ve included a real-world example with HTTP request and a more practical example of its use with proper error handling.
Practical Example: User Data Fetch with Complete Error Handling
// Simulate fetching user data from an API
const fetchUserData = (userId) => {
return new Promise((resolve, reject) => {
console.log(`Fetching data for user ${userId}...`);
// Simulate API call delay
setTimeout(() => {
const users = {
1: { id: 1, name: "Alice", role: "admin" },
2: { id: 2, name: "Bob", role: "user" }
};
const user = users[userId];
if (user) {
resolve(user); // Success case
} else {
reject(new Error(`User ${userId} not found`)); // Error case
}
}, 1000);
});
};
// Processing functions for our chain
const validateUserRole = (user) => {
console.log(`Validating role for: ${user.name}`);
if (user.role !== 'admin') {
throw new Error('Insufficient permissions'); // This will trigger .catch()
}
return user; // Pass to next .then()
};
const logAccess = (user) => {
console.log(`Access granted to ${user.name} (${user.role})`);
return user;
};
// Example 1: Successful chain
console.log('=== SUCCESSFUL REQUEST ===');
fetchUserData(1) // Returns Alice (admin)
.then(validateUserRole)
.then(logAccess)
.then(user => {
console.log(`Final success: ${user.name} is logged in`);
return user;
})
.catch(error => {
console.error('Error:', error.message);
return { error: true, message: error.message }; // Recover from error
})
.finally(() => {
console.log('Request completed - cleaning up resources\n');
});
// After 1 second, this will output:
// Fetching data for user 1...
// Validating role for: Alice
// Access granted to Alice (admin)
// Final success: Alice is logged in
// Request completed - cleaning up resources
Real-Word HTTP Request Example
// Real-world example with fetch API
const loadUserProfile = (userId) => {
console.log(`Starting profile load for user ${userId}`);
fetch(`/api/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // Parse JSON response
})
.then(userData => {
console.log('User data received:', userData);
// Continue with data process in .then()
return processUserData(userData);
})
.then(processedData => {
updateUI(processedData);
})
.catch(error => {
console.error('Failed to load profile:', error);
showErrorMessage('Failed to load user profile');
})
.finally(() => {
hideLoadingSpinner(); // Always hide spinner, success or failure
console.log('Profile load operation completed');
});
};
// Mock functions for the example
const processUserData = (data) => {
console.log('Processing user data...');
return { ...data, processed: true };
};
const updateUI = (data) => {
console.log('Updating UI with:', data);
};
const showErrorMessage = (message) => {
console.log('Showing error:', message);
};
const hideLoadingSpinner = () => {
console.log('Loading spinner hidden');
};
// Simulate calling the function
// loadUserProfile(123);
As you can see from the last 2 examples, this pattern ensures your asynchronous code is robust, maintainable, and properly handles both success and failure scenarios.
Below is a comprehensive list of the static methods and instance methods available on a Promise:
Static Methods (Called on Promise class)
Promise.all()
- Returns a single Promise that resolves when all promises in the iterable have resolved
- Rejects immediately if any promise in the iterable rejects
- Results are in the same order as input promises
Promise.allSettled()
- Returns a single Promise that resolves when all promises in the iterable have settled (either fulfilled or rejected)
- Never rejects - always resolves with an array of outcome objects showing each promise's status and value/reason
Promise.any()
- Returns a single Promise that resolves when any promise in the iterable fulfills
- Only rejects if all promises are rejected (with an AggregateError)
Promise.race()
- Returns a single Promise that settles based on the first promise in the iterable to settle (whether fulfilled or rejected)
- Adopts the state and value/reason of the first settling promise
Promise.resolve()
-
Returns a Promise object that is resolved with the given value
-
Creates an immediately fulfilled promise
Promise.reject()
- Returns a Promise object that is rejected with the given reason
- Creates an immediately rejected promise
Instance Methods (Called on Promise instances)
.then()
- Attaches callbacks for when the promise is fulfilled or rejected
- Returns a new promise allowing for method chaining
- Takes two optional arguments: onFulfilled and onRejected handlers
.catch()
- Attaches a callback for when the promise is rejected
- Returns a new promise (sugar syntax for .then(null, onRejected))
.finally()
- Attaches a callback that executes regardless of fulfillment or rejection
- Useful for cleanup operations that should always run
- Returns a new promise that preserves the original settlement state
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'
Javascript Null Coalescing Operator ??
The double question mark ?? in javascript is referred to as the null coalescing operator. It was introduced in JavaScript ES2020. It allows you to check for null or undefined variable. In most cases the order of operation for this operator follows the math and comparison operators. Below are a few examples:
Example 1:let myval = null;console.log(myval ?? 20); // output is 20 because myval is null
Example 2:console.log(myval ?? 20); // output is 20 because myval is undefined
Example 3:let myval = [];console.log(myval ?? [1,2,3]); // output is [] because it isn't null and it's not undefined
If the myval is anything other than null or undefined, the output will be the value of the right-hand side of the equation.
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)
);