I spent eight years writing PHP before I wrote my first serious TypeScript. The mental model transfer is smoother than you'd expect — but there are enough sharp edges that a PHP developer coming to TypeScript fresh will waste time on avoidable mistakes. Here's the translation guide I wish I'd had.
The Basics Are Familiar
PHP's type system:
function greet(string $name, int $age): string
{
return "Hello {$name}, you are {$age} years old.";
}
TypeScript:
function greet(name: string, age: number): string {
return `Hello ${name}, you are ${age} years old.`;
}
The syntax differs; the intent is identical.
null vs undefined — The Key Gotcha
PHP has null. TypeScript has both null and undefined — and they're different.
undefined— variable declared but never assignednull— explicitly assigned "nothing"
In PHP, unset properties throw. In TypeScript, accessing an unset property returns undefined (no error). This burns every PHP developer at least once.
const user = { name: 'Al Amin' };
console.log(user.email); // undefined — no error in JS!
Turn on strictNullChecks in tsconfig.json and TypeScript will catch this at compile time.
Optional Chaining
PHP's null-safe operator: $user?->address?->city
TypeScript's equivalent: user?.address?.city
They're syntactically identical in purpose.
Interfaces vs Types
PHP has interfaces as contracts for classes. TypeScript interfaces shape objects (data):
interface Post {
id: number;
title: string;
publishedAt: Date | null;
}
// type aliases work too — use interface for objects, type for unions
type Status = 'draft' | 'published' | 'archived';
PHP has no equivalent of union types in data (Status above). TypeScript's union types replace the PHP pattern of using string constants.
Generics
PHP:
/**
* @template T
* @param array<T> $items
* @param callable(T): bool $fn
* @return array<T>
*/
function filter(array $items, callable $fn): array
{
return array_values(array_filter($items, $fn));
}
TypeScript:
function filter<T>(items: T[], fn: (item: T) => boolean): T[] {
return items.filter(fn);
}
TypeScript generics are first-class. You don't need PHPDoc workarounds.
Async / Await
PHP's synchronous model has no direct equivalent of promises. The closest mental model: think of async/await as Laravel's job queues, but inline in your code.
async function fetchPost(id: number): Promise<Post> {
const response = await fetch(`/api/posts/${id}`);
return response.json();
}
// Calling it
const post = await fetchPost(42);
The await pauses execution of the current function until the promise resolves — other code continues running in the meantime. It's non-blocking I/O, not concurrency.
tsconfig.json Essentials
{
"compilerOptions": {
"strict": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"baseUrl": ".",
"paths": { "@/*": ["resources/js/*"] }
}
}
"strict": true enables all the checks PHP developers expect from a type system. Without it, TypeScript is optional and mostly useless.
Al Amin Ahamed
Senior software engineer & AI practitioner. Building things in Laravel, PHP, and TypeScript.
About me →More from the blog
← Older
Scaling WordPress: Redis Object Cache, Query Optimization, and Page Caching
Newer →
Semantic Search with pgvector and Laravel Scout
One email a month. No noise.
What I shipped, what I read, occasional deep dive. Unsubscribe anytime.
Check your inbox — confirmation link sent.