PHP has a reputation problem. For years, it’s been the language developers love to mock—the sloppy, inconsistent scripting language that powers half the internet despite itself. But something remarkable has happened over the past decade: PHP grew up. More specifically, it learned to type. The transformation from PHP 5’s dynamic free-for-all to PHP 8’s increasingly strict type system tells a fascinating story about evolution, backwards compatibility, and whether you can truly teach an old language new tricks.
This isn’t just a story about PHP. It’s about the broader question facing every dynamic language: can you add types after the fact without alienating your entire user base? Python, JavaScript, and Ruby all face similar challenges. PHP got there first, and its journey reveals both the promise and pain of gradual typing.
1. The Wild West Era: PHP 5 and Earlier
To appreciate how far PHP has come, you need to understand where it started. Early PHP was gloriously, terrifyingly loose with types. Type juggling was the name of the game, where the language would helpfully convert things for you—whether you wanted it to or not.
// Classic PHP type chaos $value = "10"; // string $result = $value + 5; // becomes 15 (int) $check = $value == 10; // true (loose comparison) // The infamous 0 == "string" problem 0 == "hello"; // true (!) "10" == "10.0"; // true "10.0" == 10.0; // true
This permissiveness made PHP easy to learn but painful to maintain. Functions accepted anything and did their best to make sense of it. A function expecting an integer would happily receive a string and convert it silently. Sometimes this worked beautifully. Often it introduced subtle bugs that surfaced only in production.
The Real Cost: Studies of PHP codebases before type hints found that roughly 20-30% of bugs were related to type confusion—passing the wrong type to functions, unexpected type coercion, or implicit conversions that produced surprising results.
2. The Long Road to Respectability
PHP’s type system evolved gradually, fighting backwards compatibility at every step. Each addition sparked intense debate within the community about whether PHP should remain dynamically typed or embrace stricter semantics.
3. The Backwards Compatibility Nightmare
Why did this take so long? Two words: backwards compatibility. PHP powers roughly 77% of websites with server-side programming. WordPress, Drupal, Magento, Laravel applications—millions of lines of code written in the old style.
The PHP team couldn’t just turn on strict typing overnight. Any breaking change would shatter the ecosystem. So they chose gradual typing: making type hints optional and defaulting to loose coercion for compatibility.
// Without strict_types, PHP coerces happily function add(int $a, int $b): int { return $a + $b; } add("5", "10"); // Works! Returns 15 add("5.9", 2); // Works! Returns 7 (truncates to int)
This solved the compatibility problem but created a new issue: PHP now had two type systems. The default coercive mode that worked like old PHP, and strict mode that actually enforced types properly. This bifurcation confused everyone.
4. The strict_types Declaration Controversy
PHP 7.0’s declare(strict_types=1) remains one of the language’s most debated features. It’s a per-file declaration that changes how type checking works in that specific file.
// With strict_types <?php declare(strict_types=1); function add(int $a, int $b): int { return $a + $b; } add("5", "10"); // TypeError! add(5, 10); // Works
The controversy centers on scope: strict_types affects the file where it’s declared, not the files being called. This leads to confusing behavior where whether type coercion happens depends on which file made the call, not where the function lives.
Arguments For strict_types
Catches bugs at the boundary. Forces explicit conversion. Makes code more predictable and closer to statically typed languages.
Arguments Against strict_types
Per-file scope is confusing. Fragments the ecosystem between strict and non-strict code. Should have been all-or-nothing.
The PHP community remains divided. Some developers use strict_types=1 in every file religiously. Others avoid it entirely, viewing it as fighting against PHP’s nature. Modern frameworks like Laravel increasingly default to strict typing, but legacy codebases often stick with coercive mode.
5. Comparing PHP to Python’s Type Hints
Python faced similar challenges when adding type hints in PEP 484. The approaches reveal different philosophies about gradual typing.
| Aspect | PHP Type Hints | Python Type Hints |
|---|---|---|
| Enforcement | Runtime enforcement by default | No runtime enforcement at all |
| Opt-in mechanism | Per-file strict_types declaration | Entirely optional annotations |
| Tooling requirement | Built into language | Requires external tools (mypy, etc.) |
| Migration path | Add types gradually, get immediate feedback | Add types for documentation, check with tools |
| Performance impact | Runtime checking has cost | Zero performance impact |
| Breaking changes | Can break code if strict | Never breaks existing code |
Python took the safer route: type hints are purely for static analysis. The Python interpreter ignores them completely. This means zero backwards compatibility issues—you can add types to any Python code without changing behavior. The downside? Types won’t catch errors at runtime unless you run a separate type checker.
PHP chose runtime enforcement, which catches errors immediately but risks breaking existing code. It’s more invasive but potentially more valuable. The question is whether developers actually use strict mode, or just add types to satisfy modern frameworks while leaving coercion enabled.
Does Adding Types to Dynamic Languages Actually Work?
This is the million-dollar question. After watching PHP, Python, JavaScript (via TypeScript), and Ruby (via Sorbet/RBS) all add types, we can draw some conclusions.
5.1 The Success Stories
TypeScript is the poster child for gradual typing done right. By being a separate language that compiles to JavaScript, it avoided the backwards compatibility trap entirely. TypeScript can be strict because you choose to write TypeScript. The migration path is clear: rename .js to .ts and add types gradually.
PHP 8.x is actually working. Modern PHP with full type coverage looks shockingly like a statically typed language. Laravel, Symfony, and other major frameworks have embraced types. New projects start with strict typing. The language feels fundamentally different than PHP 5.
// Modern PHP 8.2 code <?php declare(strict_types=1); readonly class UserProfile { public function __construct( public int $id, public string $email, public ?string $name = null, ) {} public function getDisplayName(): string { return $this->name ?? $this->email; } }
5.2 The Struggles
Python’s type hints remain underutilized. They’re excellent in projects that commit to them, but adoption is patchy. The lack of runtime enforcement means many developers view them as optional documentation rather than essential tooling. Industry surveys suggest less than 40% of Python projects use type checkers consistently.
Ruby’s typing experiments (Sorbet, RBS, TypeProf) haven’t achieved mainstream adoption. The community seems fundamentally resistant, viewing types as antithetical to Ruby’s philosophy. Multiple competing approaches fragment the ecosystem.
6. The Hidden Costs
Adding types to dynamic languages isn’t free. Beyond the technical challenges, there are subtler costs:
| Cost Category | Impact |
|---|---|
| Cognitive Load | Developers must understand two mental models: typed and untyped code |
| Ecosystem Fragmentation | Libraries vary in type coverage, creating compatibility headaches |
| Performance | Runtime type checking adds overhead (though usually negligible) |
| Migration Effort | Large codebases face years-long typing efforts |
| Community Division | Typed vs untyped camps create cultural friction |
PHP felt all of these. The strict_types division created real tension. Libraries maintaining PHP 5.6 compatibility couldn’t use type hints, while modern frameworks demanded them. The ecosystem took years to converge on best practices.
7. What PHP Teaches Us
PHP’s journey from dynamic chaos to gradual strictness offers lessons for any language considering similar evolution:
8. The Almost Respectable Present
Modern PHP is genuinely pleasant to work with. PHP 8.x with strict typing, property promotion, union types, and readonly classes feels like a different language. The type system is expressive enough for real work. Static analysis tools like PHPStan and Psalm provide IDE-like guarantees.
But “almost respectable” captures the reality. PHP still carries its legacy. The default coercive mode means plenty of code exists without proper types. The language still does surprising things if you don’t enable strict mode. And the reputation? That takes longer to fix than the actual code.
9. What We’ve Learned
PHP’s transformation from dynamically typed chaos to a language with a sophisticated gradual type system took over 15 years of careful evolution. The journey required balancing backwards compatibility with millions of existing sites against the desire for modern type safety. By making types optional and defaulting to coercive behavior, PHP preserved its ecosystem while enabling strict typing for those who wanted it.
The controversial strict_types declaration reveals the fundamental tension in gradual typing. Per-file scope created confusion but allowed incremental adoption. Runtime enforcement catches real bugs but adds complexity that Python avoided with its documentation-only type hints. PHP chose the harder path but arguably the more useful one.
Comparing PHP to Python’s optional annotations and TypeScript’s separate language approach shows there’s no single solution to adding types retroactively. PHP’s approach works because frameworks embraced it and runtime checking provides immediate value. Python’s works for projects committed to static analysis tools. TypeScript works because it’s a new language, not a retrofit.
The broader question—whether adding types to dynamic languages truly works—has a nuanced answer: yes, if you commit fully and accept a long transition period. PHP proves a dynamic language can acquire a respectable type system without breaking the world. But it also shows the costs: ecosystem fragmentation, migration effort, and community division. Whether it’s worth it depends on your priorities. For PHP, facing down its reputation and competing with modern languages, the investment paid off. The language that powered the web through sheer ubiquity now does so with actual engineering rigor.
Thank you!
We will contact you soon.
Eleftheria DrosopoulouJanuary 21st, 2026Last Updated: January 15th, 2026

This site uses Akismet to reduce spam. Learn how your comment data is processed.