TypeScript is to JavaScript what strict_types + PHPStan is to PHP – it adds a type system on top of a dynamically typed language and catches errors before runtime. For a PHP developer the concepts are familiar: interfaces, generics, union types. I show the most important TypeScript features from the perspective of someone who thinks in typed PHP daily.
Basic types – familiar territory
// TypeScript type annotations look like PHP type hints
function formatPrice(amount: number, currency: string = 'PLN'): string {
return amount.toFixed(2) + ' ' + currency;
}
// Nullable - same idea as PHP's ?string
function findProduct(id: number): Product | null {
return products.find(p => p.id === id) ?? null;
}
// Union types - like PHP 8.0's int|string
function processId(id: number | string): string {
return typeof id === 'number' ? `#${id}` : id;
}
// Readonly - like PHP's readonly properties
const config: { readonly apiUrl: string; timeout: number } = {
apiUrl: 'https://api.example.com',
timeout: 30,
};
// config.apiUrl = 'other'; // Error: Cannot assign to 'apiUrl' because it is read-only
Interfaces – the TypeScript contract
// TypeScript interface - structural typing (duck typing with checks)
interface Product {
id: number;
sku: string;
name: string;
price: number;
isActive: boolean;
category?: string; // optional property (like PHP's ?string with default null)
}
// Extending interfaces - like PHP implements + extends
interface PhysicalProduct extends Product {
weight: number;
dimensions: { width: number; height: number; depth: number };
}
interface DigitalProduct extends Product {
downloadUrl: string;
expiresAt: Date | null;
}
// Implementing interface in a class
class SimpleProduct implements Product {
constructor(
public id: number,
public sku: string,
public name: string,
public price: number,
public isActive: boolean = true
) {}
}
// Function accepting any Product implementation
function displayProduct(product: Product): void {
console.log(`${product.sku}: ${product.name} - ${product.price} PLN`);
}
Generics – like PHP templates
// Generic function - works with any type while preserving type info
function first<T>(items: T[]): T | undefined {
return items[0];
}
const firstProduct = first<Product>([product1, product2]); // Product | undefined
const firstNumber = first<number>([1, 2, 3]); // number | undefined
// TypeScript infers T from the argument if you omit <T>
// Generic class - like a typed PHP collection
class TypedCollection<T> {
private items: T[] = [];
add(item: T): void { this.items.push(item); }
get(index: number): T { return this.items[index]; }
all(): T[] { return [...this.items]; }
count(): number { return this.items.length; }
filter(predicate: (item: T) => boolean): TypedCollection<T> {
const result = new TypedCollection<T>();
this.items.filter(predicate).forEach(item => result.add(item));
return result;
}
}
const products = new TypedCollection<Product>();
products.add({ id: 1, sku: 'A', name: 'Test', price: 9.99, isActive: true });
const active = products.filter(p => p.isActive); // TypedCollection<Product>
async/await – familiar from PHP 8.1 Fibers context
// async/await is TypeScript's (and JS's) way to handle asynchronous operations
// The concept is similar to PHP's Fibers but built into the language
interface ApiResponse<T> {
data: T;
meta: { total: number; page: number };
}
async function fetchProducts(page: number = 1): Promise<ApiResponse<Product[]>> {
const response = await fetch(`/rest/V1/products?searchCriteria[currentPage]=${page}`);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
// Error handling with async/await
async function loadProductPage(categoryId: number): Promise<Product[]> {
try {
const { data, meta } = await fetchProducts(1);
console.log(`Loaded ${data.length} of ${meta.total} products`);
return data;
} catch (error) {
if (error instanceof Error) {
console.error('Failed to load products:', error.message);
}
return [];
}
}
// Parallel execution - equivalent to running multiple promises concurrently
async function loadDashboard(): Promise<void> {
const [products, categories, orders] = await Promise.all([
fetchProducts(),
fetchCategories(),
fetchRecentOrders(),
]);
// All three requests run in parallel
console.log(products, categories, orders);
}
Type utilities – practical patterns
// Partial<T> - all properties optional (useful for update payloads)
async function updateProduct(id: number, changes: Partial<Product>): Promise<Product> {
const response = await fetch(`/rest/V1/products/${id}`, {
method: 'PUT',
body: JSON.stringify(changes),
});
return response.json();
}
updateProduct(42, { price: 79.99 }); // only price, everything else optional
// Pick<T, K> - select specific properties
type ProductSummary = Pick<Product, 'id' | 'sku' | 'name' | 'price'>;
// Omit<T, K> - exclude specific properties
type ProductWithoutId = Omit<Product, 'id'>;
// Record<K, V> - typed object/dictionary
const productIndex: Record<string, Product> = {};
productIndex['SKU-001'] = product; // sku as key
// Enum in TypeScript
enum OrderStatus {
Pending = 'pending',
Processing = 'processing',
Complete = 'complete',
Cancelled = 'cancelled',
}
function getStatusLabel(status: OrderStatus): string {
const labels: Record<OrderStatus, string> = {
[OrderStatus.Pending]: 'Awaiting payment',
[OrderStatus.Processing]: 'In progress',
[OrderStatus.Complete]: 'Completed',
[OrderStatus.Cancelled]: 'Cancelled',
};
return labels[status];
}
TypeScript in Magento 2 context
// TypeScript compiles to JavaScript - Magento's RequireJS uses plain JS
// But you can write TypeScript in Hyvä Theme or custom Vue/React components
// Example: typed API client for Magento REST
interface MagentoProduct {
id: number;
sku: string;
name: string;
price: number;
status: 1 | 2; // 1=enabled, 2=disabled
}
interface SearchResult<T> {
items: T[];
search_criteria: object;
total_count: number;
}
async function getMagentoProducts(
storeCode: string = 'default'
): Promise<SearchResult<MagentoProduct>> {
const url = `/rest/${storeCode}/V1/products?searchCriteria[pageSize]=20`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${getToken()}` }
});
return res.json() as Promise<SearchResult<MagentoProduct>>;
}
Summary
TypeScript makes JavaScript feel much closer to typed PHP. Interfaces define contracts, generics allow type-safe collections, async/await makes asynchronous code readable. For a PHP developer the biggest mental shift is not the types – it is accepting that TypeScript types are erased at runtime (no runtime type checking by default). PHPStan equivalent for TypeScript would be running tsc – the TypeScript compiler – as a CI check.
