Skip to Content
Course content

10: Type Juggling and Type Declarations

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the things that makes PHP feel "easy" when you first start is its flexibility. I call it the "PHP Magic." You can take a string, add it to an integer, and PHP won't throw a fit; it'll just shrug its shoulders and try its best to make it work. This is called type juggling. While it feels like a superpower during a quick prototype, it's often the primary source of those "how on earth did this value get here?" bugs that keep you up until 2 AM.

The Magic That Eventually Bites You

Imagine you're building a simple discount calculator for an e-commerce site. In a naive implementation, you might write a function that just takes two variables and returns a result. You aren't worrying about types because, well, PHP handles it for you.

<?php
function applyDiscount($price, $discount) {
    return $price - $discount;
}

// This works fine
echo applyDiscount(100, 20); // 80

// This also works because PHP juggles the string "10" into an integer
echo applyDiscount(100, "10"); // 90
?>

At first glance, this looks efficient. You didn't have to cast your input, and the code is concise. But here is the trade-off: you've just told PHP, "I don't care what comes in here, just try to make it a number." This becomes a nightmare when your data sources get messy. If a database query returns null or an empty string, or if a frontend API sends an array instead of a value, PHP will attempt to juggle those values. Depending on the version of PHP you're using, you'll either get a weird result (like 100 if the discount was null) or a TypeError that's hard to trace because it happened inside the subtraction, not at the function boundary.

Why Coercive Typing is a Dangerous Middle Ground

You might think the solution is simply adding type declarations to your function arguments. This is a huge step forward, but there's a catch. By default, PHP operates in "coercive mode." This means even if you tell PHP a function requires an int, it will still try to juggle a numeric string into an integer before passing it in.

<?php
function applyDiscount(float $price, float $discount): float {
    return $price - $discount;
}

// PHP sees the string "10.5", sees the function wants a float, 
// and silently converts it. No error is thrown.
echo applyDiscount(100.0, "10.5"); // 89.5
?>

I’ve seen this cause massive headaches in financial applications. When you allow coercive typing, you're essentially saying, "I prefer this type, but I'll accept anything that looks remotely like it." This hides the fact that your data is "dirty." If your discount is coming in as a string, it means some other part of your pipeline is failing to sanitize or cast that data. By letting it slide here, you're just pushing the bug further down the road.

Locking it Down with Strict Types

If you want to write professional, predictable code, you need to kill the magic. You do this by adding declare(strict_types=1); to the very top of your file. This tells PHP: "Stop guessing. If I asked for a float and you got a string, throw an error immediately."

<?php
declare(strict_types=1);

function applyDiscount(float $price, float $discount): float {
    return $price - $discount;
}

// This will now throw a TypeError immediately.
echo applyDiscount(100.0, "10.5"); 
?>

Now, the trade-off is that your code will break more often during development. You'll find yourself manually casting values—using (float)$value—before passing them into functions. It feels like more boilerplate, but it's actually a gift. It forces you to be explicit about how data transforms as it moves through your app. I'd rather have a TypeError the moment a value enters a function than a silent calculation error that corrupts a customer's invoice.




📋 Practical Task

Refactoring the Order Total Validator

You have been handed a legacy piece of code for a checkout system. The current calculateFinalTotal function is causing "ghost bugs" because it relies on type juggling. The system occasionally passes strings from the $_POST superglobal directly into the function, and sometimes it passes null when a coupon is missing.

Your Goal: Refactor the code below to use strict typing and explicit type declarations. Ensure that the function only accepts floats and returns a float.

<?php
// TODO: Enable strict types here

function calculateFinalTotal($subtotal, $tax, $shipping) {
    return $subtotal + $tax + $shipping;
}

// Test cases that should now fail (throw TypeErrors) if strict_types is on:
$order1 = calculateFinalTotal(100.00, "5.50", 10.00); 
$order2 = calculateFinalTotal(100.00, 5.50, null);
?>

Requirements:

  • Add the strict types declaration at the top of the file.
  • Add type hints for all three parameters of calculateFinalTotal.
  • Add a return type declaration to the function.
  • Identify which of the two test cases will trigger the error first.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.