Hack Interview Questions with Answers
Most Asked Hack Interview Questions for Software Engineer Roles
Introduction
This page provides a complete collection of Hack Interview Questions and Answers designed for web developers, backend engineers, software architects, and candidates preparing for technical interviews with a focus on modern, large‑scale web development. Hack is an open‑source programming language developed by Meta (formerly Facebook) as a dialect of PHP. It is designed to support fast, type‑safe, and scalable application development. Hack runs on the HHVM (HipHop Virtual Machine) runtime and introduces features like static typing, generics, and advanced type inference, making it ideal for building and maintaining large‑scale web applications. This interview guide covers beginner, intermediate, and advanced Hack concepts including Hack syntax, types, generics, collections, async programming, XHP, HHVM, frameworks, and real‑world development scenarios for high‑traffic systems.
Why Hack?
- Gradual typing – introduces static types to PHP without sacrificing flexibility
- Performance – runs on HHVM, designed for high‑throughput, large‑scale applications
- Modern language features – generics, null safety, and async/await for concurrent code
- Deep integration with PHP ecosystem – reuse existing PHP libraries and frameworks
- Used in production at Meta – powers Facebook, Instagram, and other massive platforms
- Growing adoption – companies like Slack, Etsy, and others are using Hack
- Strong tooling – with type checker, IDE support, and debugging capabilities
Most Asked Hack Interview Questions
Hack is a programming language developed by Facebook (now Meta) as a dialect of PHP. It combines the rapid development of PHP with the safety of static typing and modern features like generics, async/await, and XHP.
- Static typing: Type annotations for safety
- Async/await: Built-in asynchronous programming
- Generics: Type-safe collections and classes
- XHP: XML-like syntax for UI components
- Performance: Runs on HHVM or PHP 8+ with the Hack compiler
// Hello World in Hack
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "Hello, World!\n";
}Hack provides a rich set of data types including scalar types, collections (Vector, Map, Set, Pair), and user-defined types.
- Scalar: int, float, string, bool, null
- Collections: Vector, Map, Set, Pair
- Compound: arrays, objects, shapes, tuples
- Nullable: ?type
- Type aliases: define custom types
// Data Types in Hack
function dataTypes(): void {
// Scalar types
$age = 25; // int
$salary = 50000.50; // float
$pi = 3.14159265358979; // float
$grade = 'A'; // string
$isActive = true; // bool
$name = "Alice"; // string
$price = 99.99; // float
// Collections
$list = Vector {1, 2, 3, 4, 5};
$map = Map {'name' => 'Alice', 'age' => 25};
$set = Set {1, 2, 3, 4, 5};
$pair = Pair {'key', 'value'};
echo "Age: $age\n";
echo "Salary: $salary\n";
echo "Pi: $pi\n";
echo "Grade: $grade\n";
echo "Active: " . ($isActive ? 'true' : 'false') . "\n";
echo "Name: $name\n";
echo "Price: $price\n";
}Variables in Hack are declared with $ and can have type annotations. Constants are defined with const inside classes or define globally.
- Variables:
$x = 10;or$x: int = 10; - Constants:
const PI = 3.14; - Type inference:
$x = 10;(int inferred) - Scope: Variables are function-scoped; constants are global or class-scoped
// Variables and Constants in Hack
function variablesConstants(): void {
// Variables
$x = 10;
$val = 3.14;
$str = "Hello";
$counter = 0;
// Constants (class level)
// const float PI = 3.14159;
// Type annotations
$explicit: int = 42;
$name: string = "Alice";
echo "x = $x\n";
echo "val = $val\n";
echo "str = $str\n";
echo "counter = $counter\n";
echo "explicit = $explicit\n";
echo "name = $name\n";
}Hack provides built‑in collection types for efficient data manipulation. Vector is an ordered list, Map is key‑value, Set is unique values, and Pair is a fixed‑size tuple of two.
- Vector:
$v = Vector {1, 2, 3}; - Map:
$m = Map {'a' => 1, 'b' => 2}; - Set:
$s = Set {1, 2, 3}; - Pair:
$p = Pair {'key', 'value'};
// Vector and Collections in Hack
function collectionsExamples(): void {
// Vector (ordered collection)
$vector = Vector {1, 2, 3, 4, 5};
echo "vector[0] = {$vector[0]}\n";
echo "vector[2] = {$vector[2]}\n";
// Add elements
$vector[] = 6;
$vector[] = 7;
echo "After add: " . json_encode($vector) . "\n";
// Remove elements
unset($vector[2]);
echo "After remove: " . json_encode($vector) . "\n";
// Iteration
foreach ($vector as $item) {
echo "Item: $item\n";
}
// 2D Vector
$matrix = Vector {
Vector {1, 2, 3},
Vector {4, 5, 6},
Vector {7, 8, 9}
};
echo "matrix[1][1] = {$matrix[1][1]}\n";
}Functions in Hack can have type annotations for parameters and return values, default parameters, variadic parameters, and lambda expressions.
- Basic:
function add(int $a, int $b): int { return $a + $b; } - Default:
function greet(string $name = "Guest"): string - Lambda:
$square = (int $x): int ==> $x * $x; - Variadic:
function sum(int ...$nums): int - Multiple returns: using tuples
// Functions in Hack
// Basic function with types
function add(int $a, int $b): int {
return $a + $b;
}
// Function with default parameters
function greet(string $name = "Guest"): string {
return "Hello, $name!";
}
// Lambda function
$multiply = (int $a, int $b): int ==> $a * $b;
// Variadic function
function sum(int ...$nums): int {
$total = 0;
foreach ($nums as $num) {
$total += $num;
}
return $total;
}
// Function with multiple returns using tuple
function getMinMax(Vector<int> $list): (int, int) {
$min = $list[0];
$max = $list[0];
foreach ($list as $value) {
if ($value < $min) $min = $value;
if ($value > $max) $max = $value;
}
return tuple($min, $max);
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "Add: " . add(10, 20) . "\n";
echo "Greet: " . greet("Alice") . "\n";
echo "Multiply: " . $multiply(5, 4) . "\n";
echo "Sum: " . sum(1, 2, 3, 4, 5) . "\n";
list($min, $max) = getMinMax(Vector {5, 2, 8, 1, 9});
echo "Min: $min, Max: $max\n";
}Recursion in Hack is similar to other languages. Functions can call themselves with a base case to terminate.
- Base case: Stops recursion
- Recursive case: Calls itself with modified arguments
- Stack management: Be mindful of depth
- Tail recursion: Not optimized, but can be simulated with loops
// Recursion in Hack
// Factorial
function factorial(int $n): int {
if ($n <= 1) {
return 1;
}
return $n * factorial($n - 1);
}
// Fibonacci
function fibonacci(int $n): int {
if ($n <= 1) {
return $n;
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
// Sum of array
function sumArray(Vector<int> $arr, int $n): int {
if ($n <= 0) {
return 0;
}
return $arr[$n - 1] + sumArray($arr, $n - 1);
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "Factorial 5: " . factorial(5) . "\n";
echo "Fibonacci 8: " . fibonacci(8) . "\n";
echo "Sum [1,2,3,4,5]: " . sumArray(Vector {1, 2, 3, 4, 5}, 5) . "\n";
}Maps are key‑value pairs. You can add, remove, and access values using array‑like syntax or methods.
- Creation:
Map {'key' => 'value'} - Access:
$map['key'] - Check existence:
$map->containsKey('key') - Iteration:
foreach ($map as $key => $value)
// Map in Hack
function mapExamples(): void {
// Map declaration
$scores = Map {
'Alice' => 95,
'Bob' => 87,
'Carol' => 92
};
// Access values
echo "Alice: " . $scores['Alice'] . "\n";
echo "Bob: " . $scores['Bob'] . "\n";
// Add new key-value
$scores['Dave'] = 88;
// Check if key exists
if ($scores->containsKey('Eve')) {
echo "Eve: " . $scores['Eve'] . "\n";
} else {
echo "Eve not found\n";
}
// Iterate map
foreach ($scores as $key => $value) {
echo "$key: $value\n";
}
// Delete key
unset($scores['Bob']);
echo "After delete: " . json_encode($scores) . "\n";
}Classes in Hack support properties, methods, constructors, visibility (public, private, protected), and type annotations.
- Properties: typed fields
- Constructor:
public function __construct(...) - Methods: can have return types
- Visibility: public, private, protected
// Classes in Hack
// Class definition
class Person {
public string $name;
public int $age;
public ?string $email;
// Constructor
public function __construct(string $name, int $age, ?string $email = null) {
$this->name = $name;
$this->age = $age;
$this->email = $email;
}
// Methods
public function greet(): string {
return "Hello, I'm " . $this->name;
}
public function getInfo(): string {
return "Name: " . $this->name . ", Age: " . $this->age;
}
}
// Class with properties
class Rectangle {
public float $width;
public float $height;
public function __construct(float $width, float $height) {
$this->width = $width;
$this->height = $height;
}
public function area(): float {
return $this->width * $this->height;
}
public function perimeter(): float {
return 2 * ($this->width + $this->height);
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$person1 = new Person("Alice", 25, "alice@email.com");
$person2 = new Person("Bob", 30);
echo $person1->greet() . "\n";
echo $person1->getInfo() . "\n";
echo $person2->getInfo() . "\n";
$rect = new Rectangle(4.0, 6.0);
echo "Area: " . $rect->area() . "\n";
echo "Perimeter: " . $rect->perimeter() . "\n";
}Interfaces define contracts for classes. They can contain method signatures and constants. Classes implement multiple interfaces.
- Declaration:
interface Shape { public function area(): float; } - Implementation:
class Circle implements Shape { ... } - Multiple:
class A implements I1, I2 { ... }
// Interfaces in Hack
// Interface definition
interface Shape {
public function area(): float;
public function perimeter(): float;
}
// Implementation
class Circle implements Shape {
private float $radius;
public function __construct(float $radius) {
$this->radius = $radius;
}
public function area(): float {
return M_PI * $this->radius * $this->radius;
}
public function perimeter(): float {
return 2 * M_PI * $this->radius;
}
}
class Square implements Shape {
private float $side;
public function __construct(float $side) {
$this->side = $side;
}
public function area(): float {
return $this->side * $this->side;
}
public function perimeter(): float {
return 4 * $this->side;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$shapes = Vector {
new Circle(5.0),
new Square(4.0)
};
foreach ($shapes as $shape) {
echo "Area: " . $shape->area() . ", Perimeter: " . $shape->perimeter() . "\n";
}
}Lambdas are anonymous functions. They can capture variables and be assigned to variables or passed as arguments.
- Syntax:
(params) ==> expressionor(params) ==> { statements } - Type annotations:
(int $x): int ==> $x * 2 - Capture:
$x = 10; $addX = (int $y): int ==> $y + $x;
// Lambda Functions in Hack
function lambdaExamples(): void {
// Basic lambda
$greet = (string $name): string ==> {
return "Hello, $name!";
};
echo $greet("Alice") . "\n";
// Lambda with implicit return
$square = (int $x): int ==> $x * $x;
echo "Square of 5: " . $square(5) . "\n";
// Lambda with multiple parameters
$add = (int $a, int $b): int ==> $a + $b;
echo "Add: " . $add(5, 3) . "\n";
// Lambda capturing variables
$x = 10;
$addX = (int $y): int ==> $y + $x;
echo "addX: " . $addX(5) . "\n";
// Lambda as parameter
$processList = (Vector<int> $list, (function(int): void) $callback): void ==> {
foreach ($list as $item) {
$callback($item);
}
};
$processList(Vector {1, 2, 3, 4, 5}, (int $item): void ==> {
echo "Item: $item\n";
});
}Collections support functional operations like map, filter, fold, any, every, and groupBy.
- map: transform each element
- filter: keep elements satisfying a condition
- fold: reduce to a single value
- any/every: check conditions
- groupBy: group by a key
// Collection Operations in Hack
function collectionOperations(): void {
$list = Vector {1, 2, 3, 4, 5};
// Map operations
$doubled = $list->map((int $x): int ==> $x * 2);
echo "Doubled: " . json_encode($doubled) . "\n";
// Filter
$evens = $list->filter((int $x): bool ==> $x % 2 === 0);
echo "Evens: " . json_encode($evens) . "\n";
// Reduce
$sum = $list->fold(0, (int $acc, int $x): int ==> $acc + $x);
echo "Sum: $sum\n";
// Any/Every
echo "Has even: " . ($list->any((int $x): bool ==> $x % 2 === 0) ? 'true' : 'false') . "\n";
echo "All even: " . ($list->every((int $x): bool ==> $x % 2 === 0) ? 'true' : 'false') . "\n";
// Group by
$grouped = $list->groupBy((int $x): string ==> $x % 2 === 0 ? "Even" : "Odd");
echo "Grouped: " . json_encode($grouped) . "\n";
}Strings in Hack support concatenation, interpolation, and various helper functions.
- Concatenation:
$a . $b - Interpolation:
"Hello $name" - Length:
strlen() - Substring:
substr() - Split/join:
explode()/implode()
// Strings in Hack
function stringExamples(): void {
$str = "Hello, World!";
// Basic operations
echo "Length: " . strlen($str) . "\n";
echo "Substring: " . substr($str, 7, 5) . "\n";
echo "Contains: " . (strpos($str, "World") !== false ? 'true' : 'false') . "\n";
echo "Index of: " . strpos($str, "World") . "\n";
// String interpolation
$name = "Alice";
$age = 25;
echo "$name is $age years old\n";
// Multi-line strings
$multiLine = "
This is a
multi-line
string
";
echo $multiLine;
// String methods
echo "Upper: " . strtoupper($str) . "\n";
echo "Lower: " . strtolower($str) . "\n";
echo "Trim: '" . trim(" Hello ") . "'\n";
// Split and join
$csv = "Alice,Bob,Carol,Dave";
$tokens = explode(",", $csv);
echo implode(" - ", $tokens) . "\n";
}Hack uses try/catch/finally blocks and custom exception classes that extend Exception.
- Try/catch:
try { ... } catch (Exception $e) { ... } - Finally:
finally { ... } - Custom exceptions:
class MyError extends Exception { ... }
// Exception Handling in Hack
// Custom exception
class ValidationError extends Exception {
public int $code;
public function __construct(string $message, int $code) {
parent::__construct($message);
$this->code = $code;
}
public function getCode(): int {
return $this->code;
}
}
function divide(float $a, float $b): float {
if ($b === 0.0) {
throw new Exception("Division by zero");
}
return $a / $b;
}
function validateAge(int $age): int {
if ($age < 0 || $age > 150) {
throw new ValidationError("Invalid age: $age", 400);
}
return $age;
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
// Try-catch-finally
try {
$result = divide(10, 2);
echo "Result: $result\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
} finally {
echo "Finally block executed\n";
}
// Custom exception
try {
validateAge(200);
} catch (ValidationError $e) {
echo "Error [" . $e->getCode() . "]: " . $e->getMessage() . "\n";
}
}File operations use PHP functions like fopen, fwrite, fread, and file_get_contents.
- Write:
file_put_contents('file.txt', 'data') - Read:
file_get_contents('file.txt') - Lines:
file('file.txt') - Check existence:
file_exists()
// File I/O in Hack
async function fileOperations(): Awaitable<void> {
// Write to file
$file = fopen("example.txt", "w");
fwrite($file, "Hello, World!\n");
fwrite($file, "Line 2\n");
fclose($file);
// Read file
$content = file_get_contents("example.txt");
echo "Content: $content\n";
// Read lines
$lines = file("example.txt");
foreach ($lines as $line) {
echo "Line: $line";
}
// Append to file
file_put_contents("example.txt", "Line 3\n", FILE_APPEND);
// Check if file exists
if (file_exists("example.txt")) {
echo "File exists\n";
echo "Size: " . filesize("example.txt") . " bytes\n";
}
// Directory operations
if (!is_dir("testdir")) {
mkdir("testdir");
}
rmdir("testdir");
}Regular expressions use the PCRE functions: preg_match, preg_match_all, preg_replace.
- Match:
preg_match('/pattern/', $subject, $matches) - All matches:
preg_match_all() - Replace:
preg_replace('/pattern/', 'replacement', $subject)
// Regular Expressions in Hack
function regexExamples(): void {
// Pattern matching
$pattern = '/d+/';
$text = "Price: $100, Discount: $20";
preg_match_all($pattern, $text, $matches);
echo "Found: " . implode(", ", $matches[0]) . "\n";
// Find all matches
$numbers = "Price: 100, Discount: 20, Total: 80";
preg_match_all('/d+/', $numbers, $matches);
echo "Numbers: " . implode(", ", $matches[0]) . "\n";
// Replace
$text = "Hello World";
$replaced = preg_replace('/World/', 'Hack', $text);
echo "Replaced: $replaced\n";
// Email validation
$emailPattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/';
$emails = Vector {"user@example.com", "invalid-email", "hello@world.org"};
foreach ($emails as $email) {
if (preg_match($emailPattern, $email)) {
echo "$email is valid\n";
} else {
echo "$email is invalid\n";
}
}
}JSON serialization and deserialization use json_encode and json_decode.
- Encode:
json_encode($data) - Decode:
json_decode($json, true) - Pretty print:
json_encode($data, JSON_PRETTY_PRINT) - Error handling:
json_last_error()
// JSON in Hack
use namespace HHLibDict;
use namespace HHLibVec;
function jsonExamples(): void {
// JSON parsing
$jsonString = '{"name":"Alice","age":25,"email":"alice@email.com"}';
$person = json_decode($jsonString, true);
echo "Name: " . $person['name'] . "\n";
echo "Age: " . $person['age'] . "\n";
echo "Email: " . $person['email'] . "\n";
// JSON generation
$data = Map {
'name' => 'Bob',
'age' => 30,
'email' => 'bob@email.com',
'hobbies' => Vector {'reading', 'coding'}
};
$jsonOutput = json_encode($data);
echo "JSON: $jsonOutput\n";
// Pretty print
$prettyJson = json_encode($data, JSON_PRETTY_PRINT);
echo "Pretty JSON:\n$prettyJson\n";
// JSON from file
if (file_exists("data.json")) {
$fileJson = file_get_contents("data.json");
$fileData = json_decode($fileJson, true);
echo "File data: " . json_encode($fileData) . "\n";
}
}Hack provides built‑in asynchronous programming with async functions and await.
- Async function:
async function fetch(): Awaitable<string> - Await:
$result = await fetch(); - Parallel:
await Vec\map_async($items, async ($x) ==> ...) - Concurrency:
\HH\Asio\va()
// Async/Await in Hack
async function fetchData(string $url, int $delay): Awaitable<string> {
await SleepWaitHandle::create($delay);
return "Data from $url";
}
async function computeAsync(int $a, int $b): Awaitable<int> {
await SleepWaitHandle::create(100);
return $a + $b;
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
// Basic async
$result = await fetchData("api.example.com", 500);
echo "$result\n";
// Parallel async tasks
$tasks = Vector {
fetchData("source1", 300),
fetchData("source2", 200),
fetchData("source3", 400)
};
$results = await Vecmap_async($tasks, async ($task) ==> await $task);
echo "All results: " . implode(", ", $results) . "\n";
// Parallel processing
$numbers = Vec
ange(1, 10);
$parallelResults = await Vecmap_async($numbers, async ($n) ==> {
await SleepWaitHandle::create(50);
return $n * $n;
});
echo "Squares: " . implode(", ", $parallelResults) . "\n";
}Generics allow classes, functions, and methods to work with any type while maintaining type safety.
- Generic class:
class Stack<T> { ... } - Generic function:
function findMax<T as num>(T $a, T $b): T - Constraints:
<T as MyInterface> - Multiple parameters:
class Pair<K, V>
// Generics in Hack
// Generic class
class Stack<T> {
private Vector<T> $items = Vector {};
public function push(T $item): void {
$this->items[] = $item;
}
public function pop(): T {
if ($this->items->count() === 0) {
throw new Exception("Stack is empty");
}
return $this->items->pop();
}
public function peek(): T {
if ($this->items->count() === 0) {
throw new Exception("Stack is empty");
}
return $this->items->last();
}
public function isEmpty(): bool {
return $this->items->count() === 0;
}
}
// Generic function
function findMax<T as num>(T $a, T $b): T {
return $a > $b ? $a : $b;
}
// Multiple type parameters
class Pair<K, V> {
public K $key;
public V $value;
public function __construct(K $key, V $value) {
$this->key = $key;
$this->value = $value;
}
public function print(): void {
echo $this->key . " -> " . $this->value . "\n";
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$intStack = new Stack<int>();
$intStack->push(10);
$intStack->push(20);
echo "Pop: " . $intStack->pop() . "\n";
echo "Max int: " . findMax(5, 3) . "\n";
echo "Max float: " . findMax(3.14, 2.5) . "\n";
$pair = new Pair<string, int>("age", 25);
$pair->print();
}XHP is an XML-like syntax for building UI components in Hack. It allows you to create reusable, type‑safe elements.
- Element:
class :ui:button extends :x:element { ... } - Attributes:
attribute string label @required; - Render:
protected function render(): :xhp { return <button>{...}</button>; } - Usage:
<ui:button label="Click" />
// XHP in Hack
// XHP class for reusable components
class :ui:button extends :x:element {
attribute string label @required;
attribute string type = "button";
protected function render(): :xhp {
return <button type={$this->:type} class="btn btn-primary">
{$this->:label}
</button>;
}
}
class :ui:card extends :x:element {
attribute string title @required;
attribute string subtitle;
protected function render(): :xhp {
return <div class="card">
<div class="card-header">
<h3>{$this->:title}</h3>
{ $this->:subtitle !== null ? <p class="subtitle">{$this->:subtitle}</p> : null }
</div>
<div class="card-body">
{$this->getChildren()}
</div>
</div>;
}
}
// Usage
<<__EntryPoint>>
async function main(): Awaitable<void> {
$button = <ui:button label="Click Me" />;
$card = <ui:card title="User Profile" subtitle="Details">
<p>Name: Alice</p>
<p>Email: alice@email.com</p>
<ui:button label="Edit" type="submit" />
</ui:card>;
echo $button->toString();
echo $card->toString();
}Type aliases give descriptive names to complex types. Shapes are structure‑like types with named fields.
- Type alias:
type UserID = int; - Shape:
type Person = shape('name' => string, 'age' => int); - Optional fields:
?stringinside shape - Nested shapes: shapes within shapes
// Type Aliases and Shapes in Hack
// Type alias for User
type User = shape(
'id' => int,
'name' => string,
'email' => string,
'age' => int,
'active' => bool,
);
// Type alias for nullable user
type ?User = shape(
'id' => int,
'name' => string,
'email' => string,
'age' => int,
'active' => bool,
) | null;
// Function using shape
function formatUser(User $user): string {
return "User: " . $user['name'] . " (" . $user['email'] . ")";
}
// Function with optional fields
type Config = shape(
'host' => string,
'port' => int,
'debug' => bool,
'timeout' => ?int,
);
function getConfig(): Config {
return shape(
'host' => 'localhost',
'port' => 8080,
'debug' => true,
'timeout' => 30,
);
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$user = shape(
'id' => 1,
'name' => 'Alice',
'email' => 'alice@email.com',
'age' => 25,
'active' => true,
);
echo formatUser($user) . "\n";
$config = getConfig();
echo "Config: host={$config['host']}, port={$config['port']}\n";
}Enums define a fixed set of constants. They can have values of type int or string.
- Enum:
enum Color: string { Red = '#FF0000'; Green = '#00FF00'; } - Access:
Color::Red - Methods: Enums can have methods
- Iteration:
Color::getValues()
// Enums in Hack
enum Color: string {
Red = '#FF0000';
Green = '#00FF00';
Blue = '#0000FF';
Yellow = '#FFFF00';
}
enum Status: int {
Pending = 0;
Processing = 1;
Shipped = 2;
Delivered = 3;
Cancelled = 4;
}
function getStatusName(Status $status): string {
return match ($status) {
Status::Pending => 'Pending',
Status::Processing => 'Processing',
Status::Shipped => 'Shipped',
Status::Delivered => 'Delivered',
Status::Cancelled => 'Cancelled',
};
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$color = Color::Red;
echo "Color: " . $color . "\n";
echo "Color value: " . $color . "\n";
$status = Status::Processing;
echo "Status: " . getStatusName($status) . "\n";
// Iterate through enum values
foreach (Color::getValues() as $color) {
echo "Enum value: $color\n";
}
}Tuples are fixed‑size, ordered collections of values, often used for multiple return values.
- Creation:
tuple(1, "Alice", 3.14) - Access:
$tuple[0] - Named:
tuple('name' => 'Alice', 'age' => 25) - Return types:
function getMinMax(): (int, int)
// Tuples in Hack
function tupleExamples(): void {
// Create tuple
$tuple = tuple(1, "Alice", 3.14);
// Access elements
echo "First: " . $tuple[0] . "\n";
echo "Second: " . $tuple[1] . "\n";
echo "Third: " . $tuple[2] . "\n";
// Tuple with named elements
$named = tuple('name' => 'Alice', 'age' => 25);
echo "Name: " . $named['name'] . "\n";
echo "Age: " . $named['age'] . "\n";
// Function returning tuple
function getMinMax(Vector<int> $list): (int, int) {
$min = $list[0];
$max = $list[0];
foreach ($list as $value) {
if ($value < $min) $min = $value;
if ($value > $max) $max = $value;
}
return tuple($min, $max);
}
$result = getMinMax(Vector {5, 2, 8, 1, 9});
echo "Min: " . $result[0] . ", Max: " . $result[1] . "\n";
}Nullable types allow a variable to be either a specific type or null.
- Syntax:
?intmeansint|null - Null coalescing:
$value ?? "default" - Null safe:
$user['email'] ?? 'No email' - Checking:
if ($value !== null) { ... }
// Nullable Types in Hack
function nullableExamples(): void {
// Nullable type
$maybeNull: ?string = null;
$notNull: string = "Hello";
// Check for null
if ($maybeNull !== null) {
echo "Value: $maybeNull\n";
} else {
echo "Value is null\n";
}
// Null coalescing operator
$value = $maybeNull ?? "Default";
echo "Value: $value\n";
// Null safe operator
$user = shape('name' => 'Alice', 'email' => null);
$email = $user['email'] ?? 'No email';
echo "Email: $email\n";
// Function with nullable parameter
function greet(?string $name): string {
return "Hello, " . ($name ?? "Guest") . "!";
}
echo greet(null) . "\n";
echo greet("Alice") . "\n";
}Shapes can have optional fields by using ?type. Use Shapes::idx() for safe access.
- Optional:
phone => ?string - Safe access:
Shapes::idx($shape, 'phone') - Nested: shapes inside shapes
- Validation: custom validation functions
// Shapes in Hack
function shapeExamples(): void {
// Shape definition
$user = shape(
'id' => 1,
'name' => 'Alice',
'email' => 'alice@email.com',
'age' => 25,
'active' => true,
);
// Access fields
echo "ID: " . $user['id'] . "\n";
echo "Name: " . $user['name'] . "\n";
echo "Email: " . $user['email'] . "\n";
echo "Age: " . $user['age'] . "\n";
echo "Active: " . ($user['active'] ? 'true' : 'false') . "\n";
// Shape with optional fields
type UserWithOptional = shape(
'id' => int,
'name' => string,
'email' => string,
'phone' => ?string,
);
$user2 = shape(
'id' => 2,
'name' => 'Bob',
'email' => 'bob@email.com',
'phone' => null,
);
// Function with shape
function formatUserShape(shape('name' => string, 'age' => int) $user): string {
return $user['name'] . " is " . $user['age'] . " years old";
}
echo formatUserShape($user) . "\n";
}<<__Memoize>> caches the result of a method call, improving performance for expensive operations.
- Usage:
<<__Memoize>> public function getData(string $key): string - Cache key: all arguments are used as the cache key
- Scope: per‑instance cache
- Trade‑offs: memory vs. speed
// Memoize Attribute in Hack
// Memoize method to cache results
class CacheExample {
private int $counter = 0;
<<__Memoize>>
public function getExpensiveData(string $key): string {
$this->counter++;
// Simulate expensive operation
return "Data for $key (call #" . $this->counter . ")";
}
<<__Memoize>>
public function getDataWithParams(int $id, string $type): string {
// Results cached based on all parameters
return "Data: id=$id, type=$type";
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$cache = new CacheExample();
// First call - executes the method
echo $cache->getExpensiveData("key1") . "\n";
echo $cache->getExpensiveData("key2") . "\n";
// Second call with same key - returns cached result
echo $cache->getExpensiveData("key1") . "\n"; // counter doesn't increment
// Different parameters produce different cache entries
echo $cache->getDataWithParams(1, "user") . "\n";
echo $cache->getDataWithParams(1, "admin") . "\n";
echo $cache->getDataWithParams(1, "user") . "\n"; // cached
}Asynchronous collections allow parallel processing of items using Vec\map_async and Vec\filter_async.
- Map async:
await Vecmap_async($items, async ($x) ==> ...) - Filter async:
await Vecilter_async($items, async ($x) ==> ...) - Parallel execution: multiple async operations run concurrently
- Error handling: use try/catch inside the async callback
// Asynchronous Collections in Hack
async function asyncCollectionExamples(): Awaitable<void> {
// Map async over collection
$numbers = Vec
ange(1, 10);
$squares = await Vecmap_async($numbers, async ($n) ==> {
await SleepWaitHandle::create(10);
return $n * $n;
});
echo "Squares: " . implode(", ", $squares) . "\n";
// Filter async
$filtered = await Vecilter_async($numbers, async ($n) ==> {
await SleepWaitHandle::create(5);
return $n % 2 === 0;
});
echo "Evens: " . implode(", ", $filtered) . "\n";
// Parallel processing with mapping
$urls = Vector {
"https://api1.example.com",
"https://api2.example.com",
"https://api3.example.com"
};
$results = await Vecmap_async($urls, async ($url) ==> {
// Simulate API call
await SleepWaitHandle::create(100);
return "Data from $url";
});
echo "Results: " . implode(", ", $results) . "\n";
}The HSL is a collection of functional utilities for working with containers, strings, and more.
- Vec:
Vec\map,Vec\filter - Dict:
Dict\map,Dict\filter - Str:
Str\length,Str\uppercase - C: container functions like
C\contains,C\count
// Hack Standard Library (HSL) Examples
use namespace HHLibC;
use namespace HHLibDict;
use namespace HHLibVec;
use namespace HHLibStr;
function hslExamples(): void {
// Vector operations
$vector = Vector {1, 2, 3, 4, 5};
// Vecmap
$doubled = Vecmap($vector, ($x) ==> $x * 2);
echo "Doubled: " . implode(", ", $doubled) . "\n";
// Vecilter
$evens = Vecilter($vector, ($x) ==> $x % 2 === 0);
echo "Evens: " . implode(", ", $evens) . "\n";
// Dict operations
$dict = dict['a' => 1, 'b' => 2, 'c' => 3];
$dictDoubled = Dictmap($dict, ($v) ==> $v * 2);
// Str operations
$text = "Hello, World!";
echo "Length: " . Strlength($text) . "\n";
echo "Upper: " . Str\uppercase($text) . "\n";
echo "Lower: " . Strlowercase($text) . "\n";
// C operations (container functions)
$list = vec[1, 2, 3, 4, 5];
echo "Contains 3: " . (Ccontains($list, 3) ? 'true' : 'false') . "\n";
echo "Count: " . Ccount($list) . "\n";
}Type aliases can be used for unions, shapes, and even generic types.
- Union:
type Status = 'active' | 'inactive' | 'pending'; - Shape alias:
type Person = shape('name' => string, ...); - Function:
type Callback = (function(string): void); - Generic:
type Option<T> = T | null;
// Type Aliases in Hack
// Basic type alias
type UserID = int;
type UserName = string;
// Union type alias
type Status = 'active' | 'inactive' | 'pending';
// Shape type alias
type Person = shape(
'name' => string,
'age' => int,
'email' => string,
);
// Function using type alias
function getUserName(UserID $id): UserName {
// In real app, would fetch from database
return "User_$id";
}
function getUserStatus(UserID $id): Status {
// In real app, would check database
return 'active';
}
function getPersonDetails(Person $person): string {
return "Name: " . $person['name'] . ", Age: " . $person['age'];
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$id = 123;
$name = getUserName($id);
$status = getUserStatus($id);
echo "User: $name, Status: $status\n";
$person = shape(
'name' => 'Alice',
'age' => 25,
'email' => 'alice@email.com',
);
echo getPersonDetails($person) . "\n";
}<<__EntryPoint>> marks the entry point of a Hack script. It allows async main functions.
- Usage:
<<__EntryPoint>> async function main(): Awaitable<void> { ... } - Async support:
main can be async - Arguments: command‑line arguments can be accessed via
$argv
// Hack EntryPoint and Asynchronous Main
// Entry point with async
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "Application started\n";
// Run async operations
$result = await asyncOperation();
echo "Result: $result\n";
// Multiple async operations
$results = await Vecmap_async(
range(1, 5),
async ($i) ==> {
await SleepWaitHandle::create($i * 10);
return $i * 2;
}
);
echo "Results: " . implode(", ", $results) . "\n";
}
async function asyncOperation(): Awaitable<string> {
await SleepWaitHandle::create(100);
return "Completed";
}Traits allow method reuse across classes without inheritance.
- Definition:
trait Loggable { public function log(...) { ... } } - Use:
class User { use Loggable; } - Multiple:
use Trait1, Trait2; - Conflict resolution:
insteadofandas
// Hack Traits
// Trait definition
trait Loggable {
public function log(string $message): void {
echo "[LOG] " . date('Y-m-d H:i:s') . " - $message\n";
}
}
trait Timestampable {
private int $createdAt;
public function setCreatedAt(int $timestamp): void {
$this->createdAt = $timestamp;
}
public function getCreatedAt(): int {
return $this->createdAt;
}
}
// Class using traits
class User {
use Loggable;
use Timestampable;
public string $name;
public function __construct(string $name) {
$this->name = $name;
$this->setCreatedAt(time());
$this->log("User created: $name");
}
public function getName(): string {
return $this->name;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$user = new User("Alice");
echo "User: " . $user->getName() . "\n";
echo "Created at: " . date('Y-m-d H:i:s', $user->getCreatedAt()) . "\n";
}Enums can have methods to encapsulate logic.
- Method:
public function isSuccess(): bool { return $this === self::Success; } - Match: use
matchfor pattern matching on enum values - Description:
getDescription()for human‑readable strings
// Hack Enum with Methods
enum ResponseStatus: string {
Success = '200';
Created = '201';
BadRequest = '400';
Unauthorized = '401';
NotFound = '404';
ServerError = '500';
public function isSuccess(): bool {
return $this === self::Success || $this === self::Created;
}
public function isError(): bool {
return !$this->isSuccess();
}
public function getDescription(): string {
return match ($this) {
self::Success => 'OK',
self::Created => 'Created',
self::BadRequest => 'Bad Request',
self::Unauthorized => 'Unauthorized',
self::NotFound => 'Not Found',
self::ServerError => 'Internal Server Error',
};
}
}
enum OrderStatus: int {
Pending = 0;
Processing = 1;
Shipped = 2;
Delivered = 3;
public function isActive(): bool {
return $this === self::Pending || $this === self::Processing;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$status = ResponseStatus::Success;
echo "Status: " . $status->getDescription() . "\n";
echo "Is success: " . ($status->isSuccess() ? 'true' : 'false') . "\n";
$order = OrderStatus::Processing;
echo "Order is active: " . ($order->isActive() ? 'true' : 'false') . "\n";
}Shapes can be validated by checking required fields and using Shapes::idx for optional ones.
- Required fields: check if
''or empty - Optional:
Shapes::idx($shape, 'phone') - Nested: recursively validate inner shapes
- Return:
boolor throw exception
// Hack Shapes with Optional Fields
type UserWithOptional = shape(
'id' => int,
'name' => string,
'email' => string,
'phone' => ?string,
'address' => shape(
'street' => string,
'city' => string,
'zip' => string,
),
);
function validateUser(UserWithOptional $user): bool {
// Required fields validation
if ($user['name'] === '') {
return false;
}
// Optional field handling
$phone = Shapes::idx($user, 'phone');
if ($phone !== null && !preg_match('/^[0-9-]+$/', $phone)) {
return false;
}
return true;
}
function formatUserAddress(UserWithOptional $user): string {
$address = $user['address'];
return $address['street'] . ", " . $address['city'] . " " . $address['zip'];
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$user = shape(
'id' => 1,
'name' => 'Alice',
'email' => 'alice@email.com',
'phone' => null,
'address' => shape(
'street' => '123 Main St',
'city' => 'NYC',
'zip' => '10001',
),
);
if (validateUser($user)) {
echo "User is valid\n";
echo "Address: " . formatUserAddress($user) . "\n";
}
}Contexts are used for dependency injection or request‑scoped data (e.g., database connections, user sessions).
- Database context: singleton or request‑scoped
- User context: stores current user ID
- Implementation: using classes with static methods or passing through constructor
// Hack Contexts
// Context for database connection
class DatabaseContext {
private static ?DatabaseContext $instance = null;
private string $connection;
private function __construct() {
$this->connection = "Connected to database";
echo "Database context initialized\n";
}
public static function getInstance(): DatabaseContext {
if (self::$instance === null) {
self::$instance = new DatabaseContext();
}
return self::$instance;
}
public function query(string $sql): string {
return "Executing: $sql";
}
}
// Context for user session
class UserContext {
private ?string $userId = null;
public function setUser(string $userId): void {
$this->userId = $userId;
echo "User set: $userId\n";
}
public function getUserId(): ?string {
return $this->userId;
}
public function isLoggedIn(): bool {
return $this->userId !== null;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$db = DatabaseContext::getInstance();
echo $db->query("SELECT * FROM users") . "\n";
$userContext = new UserContext();
$userContext->setUser("123");
echo "Logged in: " . ($userContext->isLoggedIn() ? 'true' : 'false') . "\n";
}Dependency injection is achieved by passing dependencies via constructors or setters.
- Constructor injection:
class UserService { public function __construct(Logger $logger) { ... } } - Interface: program to interfaces
- Manual: create and pass dependencies
- Container: can use a simple DI container
// Hack Dependency Injection
interface Logger {
public function log(string $message): void;
}
class ConsoleLogger implements Logger {
public function log(string $message): void {
echo "[CONSOLE] $message\n";
}
}
class FileLogger implements Logger {
private string $filePath;
public function __construct(string $filePath) {
$this->filePath = $filePath;
}
public function log(string $message): void {
file_put_contents($this->filePath, $message . "\n", FILE_APPEND);
echo "[FILE] $message\n";
}
}
class UserService {
private Logger $logger;
public function __construct(Logger $logger) {
$this->logger = $logger;
}
public function createUser(string $name): void {
$this->logger->log("Creating user: $name");
// Logic to create user
$this->logger->log("User created: $name");
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$consoleLogger = new ConsoleLogger();
$service1 = new UserService($consoleLogger);
$service1->createUser("Alice");
$fileLogger = new FileLogger("app.log");
$service2 = new UserService($fileLogger);
$service2->createUser("Bob");
}Vectors support many operations beyond basic: count, first, last, add, remove, map, filter, fold, linearSearch.
- Count:
$v->count() - First/Last:
$v->firstValue(),$v->lastValue() - Add/Remove:
$v->add(),$v->removeKey() - Transform:
$v->map(),$v->filter(),$v->fold()
// Hack Vector Operations
function vectorOperations(): void {
$vector = Vector {1, 2, 3, 4, 5};
// Basic operations
echo "Count: " . $vector->count() . "\n";
echo "First: " . $vector->firstValue() . "\n";
echo "Last: " . $vector->lastValue() . "\n";
// Add and remove
$vector->add(6);
$vector->add(7);
echo "After add: " . json_encode($vector) . "\n";
$vector->removeKey(2);
echo "After remove: " . json_encode($vector) . "\n";
// Transform
$doubled = $vector->map(($x) ==> $x * 2);
echo "Doubled: " . json_encode($doubled) . "\n";
// Filter
$evens = $vector->filter(($x) ==> $x % 2 === 0);
echo "Evens: " . json_encode($evens) . "\n";
// Reduce
$sum = $vector->fold(0, ($acc, $x) ==> $acc + $x);
echo "Sum: $sum\n";
// Contains
echo "Contains 3: " . ($vector->linearSearch(3) !== -1 ? 'true' : 'false') . "\n";
}Maps offer keys, values, map, filter, containsKey, containsValue, and more.
- Keys/Values:
$map->keys(),$map->values() - Transform:
$map->map(),$map->filter() - Contains:
$map->containsKey('key'),$map->containsValue('value') - Add/Remove:
$map['key'] = 'value',unset($map['key'])
// Hack Map Operations
function mapOperations(): void {
$map = Map {
'name' => 'Alice',
'age' => 25,
'email' => 'alice@email.com'
};
// Basic operations
echo "Count: " . $map->count() . "\n";
echo "Keys: " . json_encode($map->keys()) . "\n";
echo "Values: " . json_encode($map->values()) . "\n";
// Add and remove
$map['city'] = 'NYC';
echo "After add: " . json_encode($map) . "\n";
unset($map['age']);
echo "After remove: " . json_encode($map) . "\n";
// Transform
$doubled = $map->map(($value) ==> is_int($value) ? $value * 2 : $value);
echo "Doubled: " . json_encode($doubled) . "\n";
// Filter
$filtered = $map->filter(($value) ==> is_string($value));
echo "Filtered: " . json_encode($filtered) . "\n";
// Contains
echo "Contains key 'name': " . ($map->containsKey('name') ? 'true' : 'false') . "\n";
echo "Contains value 'Alice': " . ($map->containsValue('Alice') ? 'true' : 'false') . "\n";
}Sets support union, intersection, difference, and membership checks.
- Union:
$set->union($other) - Intersection:
$set->intersect($other) - Difference:
$set->difference($other) - Add/Remove:
$set->add(1),$set->remove(1)
// Hack Set Operations
function setOperations(): void {
$set = Set {1, 2, 3, 4, 5};
// Basic operations
echo "Count: " . $set->count() . "\n";
echo "Contains 3: " . ($set->contains(3) ? 'true' : 'false') . "\n";
// Add and remove
$set->add(6);
$set->add(7);
echo "After add: " . json_encode($set) . "\n";
$set->remove(2);
echo "After remove: " . json_encode($set) . "\n";
// Set operations
$set2 = Set {4, 5, 6, 7, 8};
$union = $set->union($set2);
echo "Union: " . json_encode($union) . "\n";
$intersection = $set->intersect($set2);
echo "Intersection: " . json_encode($intersection) . "\n";
$difference = $set->difference($set2);
echo "Difference: " . json_encode($difference) . "\n";
// Transform
$doubled = $set->map(($x) ==> $x * 2);
echo "Doubled: " . json_encode($doubled) . "\n";
}Pairs are used for two‑element tuples and can be destructured.
- Creation:
Pair {1, 'Alice'} - Access:
$pair[0],$pair[1] - Destructuring:
list($first, $second) = $pair - Function returns:
function(): Pair<int, string>
// Hack Pair Operations
function pairOperations(): void {
// Create pair
$pair = Pair {'name', 'Alice'};
// Access elements
echo "First: " . $pair[0] . "\n";
echo "Second: " . $pair[1] . "\n";
// Pair with different types
$pair2 = Pair {1, 3.14};
echo "First: " . $pair2[0] . "\n";
echo "Second: " . $pair2[1] . "\n";
// Using pair in function
function getUserCredentials(): Pair<string, string> {
return Pair {'alice@email.com', 'password123'};
}
$credentials = getUserCredentials();
echo "Email: " . $credentials[0] . "\n";
echo "Password: " . $credentials[1] . "\n";
// Deconstruct pair
list($email, $password) = $credentials;
echo "Deconstructed: $email, $password\n";
}Vectors can be annotated with a specific type for compile‑time safety.
- Explicit type:
$numbers: Vector<int> = Vector {1, 2, 3}; - Mixed:
$mixed: Vector<mixed> = Vector {1, 'two', 3.0}; - Generic constraints:
function findMax<T as num>(Vector<T> $items): T
// Hack Vector with Type Annotations
function typeAnnotatedVector(): void {
// Vector with explicit type
$numbers: Vector<int> = Vector {1, 2, 3, 4, 5};
$strings: Vector<string> = Vector {'a', 'b', 'c'};
$mixed: Vector<mixed> = Vector {1, 'two', 3.0, true};
// Type safe operations
$doubled = $numbers->map(($x): int ==> $x * 2);
echo "Doubled: " . json_encode($doubled) . "\n";
// Type checking
function processNumbers(Vector<int> $nums): int {
return $nums->fold(0, ($acc, $x) ==> $acc + $x);
}
echo "Sum: " . processNumbers($numbers) . "\n";
// Generic function with constraints
function findMax<T as num>(Vector<T> $items): T {
$max = $items[0];
foreach ($items as $item) {
if ($item > $max) $max = $item;
}
return $max;
}
echo "Max: " . findMax($numbers) . "\n";
}Maps can also be typed to ensure key and value types are correct.
- Typed:
$scores: Map<string, int> = Map {...}; - Mixed values:
Map<string, mixed> - Generic functions:
function findMaxValue<T as num>(Map<string, T> $items): T
// Hack Map with Type Annotations
function typeAnnotatedMap(): void {
// Map with explicit types
$scores: Map<string, int> = Map {
'Alice' => 95,
'Bob' => 87,
'Carol' => 92
};
$config: Map<string, mixed> = Map {
'host' => 'localhost',
'port' => 8080,
'debug' => true
};
// Type safe operations
$doubled = $scores->map(($value): int ==> $value * 2);
echo "Doubled: " . json_encode($doubled) . "\n";
// Type checking
function processScores(Map<string, int> $scores): int {
return $scores->fold(0, ($acc, $value) ==> $acc + $value);
}
echo "Total: " . processScores($scores) . "\n";
// Generic function with constraints
function findMaxValue<T as num>(Map<string, T> $items): T {
$max = $items->firstValue();
foreach ($items as $value) {
if ($value > $max) $max = $value;
}
return $max;
}
echo "Max score: " . findMaxValue($scores) . "\n";
}XHP elements can have required and optional attributes, passed as an array.
- Attribute:
attribute string action @required; - Default:
attribute string method = "POST"; - Spread:
<form {...$attrs}> - Children:
{$this->getChildren()}
// Hack XHP with Attributes
// XHP class with attributes
class :ui:form extends :x:element {
attribute string action @required;
attribute string method = "POST";
attribute string id;
protected function render(): :xhp {
$attrs = [];
$attrs['action'] = $this->:action;
$attrs['method'] = $this->:method;
if ($this->:id !== null) {
$attrs['id'] = $this->:id;
}
return <form {...$attrs}>
{$this->getChildren()}
</form>;
}
}
class :ui:input extends :x:element {
attribute string type = "text";
attribute string name @required;
attribute string value;
attribute string placeholder;
protected function render(): :xhp {
$attrs = [];
$attrs['type'] = $this->:type;
$attrs['name'] = $this->:name;
if ($this->:value !== null) $attrs['value'] = $this->:value;
if ($this->:placeholder !== null) $attrs['placeholder'] = $this->:placeholder;
return <input {...$attrs} />;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$form = <ui:form action="/submit" id="contact-form">
<ui:input type="text" name="name" placeholder="Your name" />
<ui:input type="email" name="email" placeholder="Your email" />
<ui:input type="submit" name="submit" value="Send" />
</ui:form>;
echo $form->toString();
}XHP components can accept nested elements and render them.
- Layout:
<ui:layout> ... </ui:layout> - Grid:
<ui:grid columns="3"> ... </ui:grid> - Card:
<ui:card title="User">...</ui:card> - Composition: compose reusable UI blocks
// Hack XHP with Children
class :ui:layout extends :x:element {
protected function render(): :xhp {
return <div class="layout">
<header class="header">
<h1>My Application</h1>
</header>
<main class="content">
{$this->getChildren()}
</main>
<footer class="footer">
<p>© 2024 My Application</p>
</footer>
</div>;
}
}
class :ui:grid extends :x:element {
attribute int columns = 2;
attribute string gap = "20px";
protected function render(): :xhp {
return <div class="grid" style={"grid-template-columns: repeat(" . $this->:columns . ", 1fr); gap: " . $this->:gap . ";"}>
{$this->getChildren()}
</div>;
}
}
class :ui:card extends :x:element {
attribute string title;
protected function render(): :xhp {
return <div class="card">
{ $this->:title !== null ? <h3>{$this->:title}</h3> : null }
<div class="card-body">
{$this->getChildren()}
</div>
</div>;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$layout = <ui:layout>
<ui:grid columns="3">
<ui:card title="User 1">
<p>Name: Alice</p>
<p>Email: alice@email.com</p>
</ui:card>
<ui:card title="User 2">
<p>Name: Bob</p>
<p>Email: bob@email.com</p>
</ui:card>
<ui:card title="User 3">
<p>Name: Carol</p>
<p>Email: carol@email.com</p>
</ui:card>
</ui:grid>
</ui:layout>;
echo $layout->toString();
}Use \HH\Asio\va() to run multiple async operations in parallel.
- Parallel:
list($a, $b) = await HHAsioa(fetchA(), fetchB()); - Sequential:
$a = await fetchA(); $b = await fetchB(); - Performance: parallel is faster for independent tasks
- Error handling: each task can fail individually
// Hack Async with Concurrency
async function fetchUsers(): Awaitable<Vector<string>> {
await SleepWaitHandle::create(100);
return Vector {'Alice', 'Bob', 'Carol'};
}
async function fetchPosts(): Awaitable<Vector<string>> {
await SleepWaitHandle::create(150);
return Vector {'Post 1', 'Post 2', 'Post 3'};
}
async function fetchComments(): Awaitable<Vector<string>> {
await SleepWaitHandle::create(80);
return Vector {'Comment 1', 'Comment 2'};
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
// Parallel async calls
$start = microtime(true);
list($users, $posts, $comments) = await HHAsioa(
fetchUsers(),
fetchPosts(),
fetchComments()
);
$end = microtime(true);
$duration = ($end - $start) * 1000;
echo "Users: " . implode(", ", $users) . "\n";
echo "Posts: " . implode(", ", $posts) . "\n";
echo "Comments: " . implode(", ", $comments) . "\n";
echo "Duration: " . $duration . "ms\n";
// Sequential async calls
$start2 = microtime(true);
$users2 = await fetchUsers();
$posts2 = await fetchPosts();
$comments2 = await fetchComments();
$end2 = microtime(true);
$duration2 = ($end2 - $start2) * 1000;
echo "Sequential duration: " . $duration2 . "ms\n";
}Use try/catch inside async functions or wrap risky operations.
- Try/catch:
try { await risky(); } catch (Exception $e) { ... } - Parallel with error handling:
await Vecmap_async(..., async ($x) ==> { try { ... } catch { ... } }) - Propagation: allow exceptions to bubble up
// Hack Error Handling with Async
async function riskyOperation(bool $shouldFail): Awaitable<string> {
await SleepWaitHandle::create(50);
if ($shouldFail) {
throw new Exception("Operation failed");
}
return "Success";
}
async function safeOperation(bool $shouldFail): Awaitable<string> {
try {
return await riskyOperation($shouldFail);
} catch (Exception $e) {
return "Error: " . $e->getMessage();
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
// Success case
$result1 = await safeOperation(false);
echo "Result 1: $result1\n";
// Failure case
$result2 = await safeOperation(true);
echo "Result 2: $result2\n";
// Async error with catch
try {
$result3 = await riskyOperation(true);
echo "Result 3: $result3\n";
} catch (Exception $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
// Multiple async operations with error handling
$results = await Vecmap_async(
vec[false, true, false],
async ($fail) ==> {
try {
return await riskyOperation($fail);
} catch (Exception $e) {
return "Failed: " . $e->getMessage();
}
}
);
echo "Results: " . implode(", ", $results) . "\n";
}Chain operations like filter, map, and fold for expressive data processing.
- Chaining:
$numbers->filter(...)->map(...)->fold(...) - Group by:
$numbers->groupBy(...) - Complex transformations: combine multiple steps
// Hack Vector with Functional Programming
function functionalVector(): void {
$numbers = Vector {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Chaining operations
$result = $numbers
->filter(($x) ==> $x % 2 === 0) // Keep evens
->map(($x) ==> $x * 2) // Double
->fold(0, ($acc, $x) ==> $acc + $x); // Sum
echo "Result: $result\n";
// Complex transformation
$processed = $numbers
->map(($x) ==> $x * $x) // Square
->filter(($x) ==> $x > 10) // > 10
->map(($x) ==> "Number: $x"); // Format
echo "Processed: " . implode(", ", $processed) . "\n";
// Group by operation
$grouped = $numbers->groupBy(($x) ==>
$x % 2 === 0 ? 'Even' : 'Odd'
);
foreach ($grouped as $key => $values) {
echo "$key: " . implode(", ", $values) . "\n";
}
}Use list() to destructure pairs.
- Basic:
list($id, $name) = $pair; - In loops:
foreach ($pairs as $pair) { list($id, $name) = $pair; } - Nested:
list(list($id, $name), list($age, $city)) = $nestedPair;
// Hack Pair with Destructuring
function pairDestructuring(): void {
// Create pairs
$pairs = Vector {
Pair {1, 'Alice'},
Pair {2, 'Bob'},
Pair {3, 'Carol'}
};
// Destructure in loop
foreach ($pairs as $pair) {
list($id, $name) = $pair;
echo "ID: $id, Name: $name\n";
}
// Function returning pair
function getUserPair(int $id): Pair<int, string> {
$users = Map {
1 => 'Alice',
2 => 'Bob',
3 => 'Carol'
};
return Pair {$id, $users[$id]};
}
// Destructure function result
list($id, $name) = getUserPair(2);
echo "User: $id => $name\n";
// Nested pairs
$nestedPairs = Vector {
Pair {Pair {1, 'Alice'}, Pair {25, 'NYC'}},
Pair {Pair {2, 'Bob'}, Pair {30, 'LA'}}
};
foreach ($nestedPairs as $pair) {
list(list($id, $name), list($age, $city)) = $pair;
echo "$name ($id) is $age from $city\n";
}
}Define shapes inside shapes for complex data structures.
- Definition:
type Address = shape(...); type Employee = shape('address' => Address, ...); - Access:
$employee['address']['city'] - Function: format nested data
// Hack Shape with Nested Structures
type Address = shape(
'street' => string,
'city' => string,
'state' => string,
'zip' => string,
);
type Contact = shape(
'email' => string,
'phone' => ?string,
);
type Employee = shape(
'id' => int,
'name' => string,
'address' => Address,
'contact' => Contact,
'department' => string,
'salary' => float,
);
function formatEmployee(Employee $employee): string {
$result = "Employee: " . $employee['name'] . " (ID: " . $employee['id'] . ")\n";
$result .= "Department: " . $employee['department'] . "\n";
$result .= "Address: " . $employee['address']['street'] . ", " .
$employee['address']['city'] . ", " .
$employee['address']['state'] . " " .
$employee['address']['zip'] . "\n";
$result .= "Email: " . $employee['contact']['email'];
if ($employee['contact']['phone'] !== null) {
$result .= ", Phone: " . $employee['contact']['phone'];
}
$result .= "\nSalary: $" . number_format($employee['salary'], 2);
return $result;
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$employee = shape(
'id' => 1,
'name' => 'Alice Johnson',
'address' => shape(
'street' => '123 Main St',
'city' => 'New York',
'state' => 'NY',
'zip' => '10001',
),
'contact' => shape(
'email' => 'alice@company.com',
'phone' => '555-1234',
),
'department' => 'Engineering',
'salary' => 85000.00,
);
echo formatEmployee($employee) . "\n";
}A generic repository class works with any entity type.
- Class:
Repository<T> { ... } - Methods: add, get, getAll, find, filter, count
- Usage:
new Repository<User>() - Type safety: operations are type‑checked
// Hack Class with Generics
class Repository<T> {
private Vector<T> $items = Vector {};
public function add(T $item): void {
$this->items[] = $item;
}
public function get(int $index): T {
return $this->items[$index];
}
public function getAll(): Vector<T> {
return $this->items;
}
public function find((function(T): bool) $predicate): ?T {
foreach ($this->items as $item) {
if ($predicate($item)) {
return $item;
}
}
return null;
}
public function filter((function(T): bool) $predicate): Vector<T> {
$result = Vector {};
foreach ($this->items as $item) {
if ($predicate($item)) {
$result[] = $item;
}
}
return $result;
}
public function count(): int {
return $this->items->count();
}
}
class User {
public int $id;
public string $name;
public function __construct(int $id, string $name) {
$this->id = $id;
$this->name = $name;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$userRepo = new Repository<User>();
$userRepo->add(new User(1, 'Alice'));
$userRepo->add(new User(2, 'Bob'));
$userRepo->add(new User(3, 'Carol'));
$allUsers = $userRepo->getAll();
foreach ($allUsers as $user) {
echo "User: " . $user->id . " - " . $user->name . "\n";
}
$found = $userRepo->find(($u) ==> $u->name === 'Bob');
if ($found !== null) {
echo "Found: " . $found->name . "\n";
}
$filtered = $userRepo->filter(($u) ==> $u->id > 1);
echo "Filtered count: " . $filtered->count() . "\n";
}Interfaces can be generic, and classes can implement them with specific types.
- Interface:
interface RepositoryInterface<T> { ... } - Implement:
class UserRepo implements RepositoryInterface<User> { ... } - Cache:
interface CacheInterface<K, V> { ... }
// Hack Interface with Generics
interface RepositoryInterface<T> {
public function add(T $item): void;
public function get(int $index): T;
public function getAll(): Vector<T>;
public function find((function(T): bool) $predicate): ?T;
public function count(): int;
}
interface CacheInterface<K, V> {
public function set(K $key, V $value): void;
public function get(K $key): ?V;
public function has(K $key): bool;
public function remove(K $key): void;
public function clear(): void;
}
class InMemoryCache<K, V> implements CacheInterface<K, V> {
private Map<K, V> $cache = Map {};
public function set(K $key, V $value): void {
$this->cache[$key] = $value;
}
public function get(K $key): ?V {
return $this->cache->get($key);
}
public function has(K $key): bool {
return $this->cache->containsKey($key);
}
public function remove(K $key): void {
unset($this->cache[$key]);
}
public function clear(): void {
$this->cache = Map {};
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$cache = new InMemoryCache<string, int>();
$cache->set('user1', 25);
$cache->set('user2', 30);
echo "user1: " . $cache->get('user1') . "\n";
echo "Has user3: " . ($cache->has('user3') ? 'true' : 'false') . "\n";
$cache->remove('user2');
echo "After remove: " . ($cache->has('user2') ? 'true' : 'false') . "\n";
}Constants and static methods are defined at the class level and accessed without instantiation.
- Constant:
public const float PI = 3.14159; - Static method:
public static function add(int $a, int $b): int - Access:
MathConstants::PI,MathConstants::add(5, 3) - Static property:
private static ?Map $config = null;
// Hack Class Constants and Static Methods
class MathConstants {
public const float PI = 3.14159;
public const float E = 2.71828;
public const int MAX_INT = PHP_INT_MAX;
public static function add(int $a, int $b): int {
return $a + $b;
}
public static function multiply(int $a, int $b): int {
return $a * $b;
}
public static function circleArea(float $radius): float {
return self::PI * $radius * $radius;
}
}
class AppConfig {
private static ?Map<string, mixed> $config = null;
public static function load(): void {
self::$config = Map {
'host' => 'localhost',
'port' => 8080,
'debug' => true,
'timeout' => 30,
};
}
public static function get(string $key): mixed {
if (self::$config === null) {
self::load();
}
return self::$config[$key] ?? null;
}
public static function set(string $key, mixed $value): void {
if (self::$config === null) {
self::load();
}
self::$config[$key] = $value;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "PI: " . MathConstants::PI . "\n";
echo "Add: " . MathConstants::add(5, 3) . "\n";
echo "Circle area: " . MathConstants::circleArea(2.0) . "\n";
echo "Config host: " . AppConfig::get('host') . "\n";
AppConfig::set('port', 9090);
echo "Config port: " . AppConfig::get('port') . "\n";
}Abstract classes cannot be instantiated and may contain abstract methods that must be implemented by subclasses.
- Abstract class:
abstract class Animal { abstract public function makeSound(): string; } - Implementation:
class Dog extends Animal { public function makeSound(): string { return "Woof!"; } } - Concrete methods: can have normal methods
// Abstract Classes in Hack
abstract class Animal {
protected string $name;
public function __construct(string $name) {
$this->name = $name;
}
abstract public function makeSound(): string;
public function getName(): string {
return $this->name;
}
}
class Dog extends Animal {
public function makeSound(): string {
return "Woof!";
}
}
class Cat extends Animal {
public function makeSound(): string {
return "Meow!";
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$animals = Vector {
new Dog("Rex"),
new Cat("Whiskers")
};
foreach ($animals as $animal) {
echo $animal->getName() . " says: " . $animal->makeSound() . "\n";
}
}Final classes cannot be extended. Final methods cannot be overridden.
- Final class:
final class DatabaseConnection { ... } - Final method:
final public function connect(): void - Use: enforce immutability or security
// Final Classes and Methods in Hack
final class DatabaseConnection {
private string $dsn;
private static ?DatabaseConnection $instance = null;
private function __construct(string $dsn) {
$this->dsn = $dsn;
}
public static function getInstance(string $dsn): DatabaseConnection {
if (self::$instance === null) {
self::$instance = new DatabaseConnection($dsn);
}
return self::$instance;
}
public function query(string $sql): string {
return "Executing on " . $this->dsn;
}
}
// Trying to extend final class will cause an error:
// class MyDB extends DatabaseConnection {} // Fatal error
<<__EntryPoint>>
async function main(): Awaitable<void> {
$db = DatabaseConnection::getInstance("mysql://localhost:3306");
echo $db->query("SELECT * FROM users") . "\n";
}Private properties are only accessible within the class. Use reflection for debugging or advanced metaprogramming.
- Declaration:
private string $password; - Access: only inside class
- Reflection: can be used to read/modify (advanced usage)
// Private Properties in Hack
class User {
private string $password = "secret123";
private string $email = "user@email.com";
private function validatePassword(string $input): bool {
return $input === $this->password;
}
}
// Accessing private properties directly is not allowed:
// $user = new User();
// echo $user->password; // Error
// However, we can use reflection to access them (for debugging or advanced metaprogramming)
function hackPrivateProperty(): void {
$user = new User();
$reflection = new ReflectionClass($user);
$prop = $reflection->getProperty('password');
$prop->setAccessible(true);
echo "Password: " . $prop->getValue($user) . "\n";
$prop->setValue($user, 'new_password');
echo "New Password: " . $prop->getValue($user) . "\n";
$method = $reflection->getMethod('validatePassword');
$method->setAccessible(true);
$result = $method->invoke($user, 'new_password');
echo "Validation: " . ($result ? "Valid" : "Invalid") . "\n";
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
hackPrivateProperty();
}Singletons ensure only one instance exists by using a private constructor and a static getInstance method.
- Private constructor:
private function __construct() {} - Static instance:
private static ?self $instance = null; - Getter:
public static function getInstance(): self { ... }
// Singleton Pattern in Hack
class ConfigManager {
private static ?ConfigManager $instance = null;
private array $config = [];
private function __construct() {
$this->config = ['db_host' => 'localhost', 'db_user' => 'root'];
}
public static function getInstance(): ConfigManager {
if (self::$instance === null) {
self::$instance = new ConfigManager();
}
return self::$instance;
}
public function getConfig(string $key): string {
return $this->config[$key] ?? '';
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$config = ConfigManager::getInstance();
echo "DB Host: " . $config->getConfig('db_host') . "\n";
// $config2 = new ConfigManager(); // Error: private constructor
}Hack does not have a built‑in readonly keyword, but you can achieve immutability using private properties and public getters.
- Private property:
private int $value; - Getter:
public function getValue(): int { return $this->value; } - No setter: enforce immutability
- With methods: return new instances for changes
// Readonly Properties (using const and immutable patterns)
class ImmutablePoint {
public const float PI = 3.14159;
public function __construct(
private int $x,
private int $y
) {}
public function getX(): int { return $this->x; }
public function getY(): int { return $this->y; }
public function withX(int $x): ImmutablePoint {
return new ImmutablePoint($x, $this->y);
}
public function withY(int $y): ImmutablePoint {
return new ImmutablePoint($this->x, $y);
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$p1 = new ImmutablePoint(5, 10);
$p2 = $p1->withX(7);
echo "p1: (" . $p1->getX() . ", " . $p1->getY() . ")\n";
echo "p2: (" . $p2->getX() . ", " . $p2->getY() . ")\n";
echo "PI: " . ImmutablePoint::PI . "\n";
}Protected methods are accessible within the class and its subclasses. They can be called via reflection if needed.
- Declaration:
protected function calculate(int $a): int - Access: inside class and children
- Reflection: can bypass visibility
// Protected Methods in Hack
class Calculator {
protected function complexCalculation(int $a, int $b): int {
return ($a * $b) + ($a / $b) - ($a % $b);
}
protected function secretAlgorithm(string $input): string {
return md5($input . "SALT_STRING");
}
}
// Using reflection to call protected methods
function hackProtectedMethods(): void {
$calc = new Calculator();
$reflection = new ReflectionClass($calc);
$method = $reflection->getMethod('complexCalculation');
$method->setAccessible(true);
$result = $method->invoke($calc, 10, 3);
echo "Complex Calculation: " . $result . "\n";
$method2 = $reflection->getMethod('secretAlgorithm');
$method2->setAccessible(true);
$hash = $method2->invoke($calc, "hack_me");
echo "Secret Hash: " . $hash . "\n";
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
hackProtectedMethods();
}Dependencies are passed via the constructor, promoting loose coupling and testability.
- Constructor:
public function __construct(Logger $logger) { ... } - Usage:
$service = new UserService(new ConsoleLogger()); - Benefits: easy mocking and swapping
// Constructor Injection in Hack
class PaymentProcessor {
private array $validators;
public function __construct(array $validators = []) {
$this->validators = $validators;
}
public function processPayment(float $amount): bool {
foreach ($this->validators as $validator) {
if (!$validator($amount)) {
return false;
}
}
return true;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$processor = new PaymentProcessor([
function($amount) { return $amount > 0; },
function($amount) { return $amount < 10000; }
]);
echo "Payment " . ($processor->processPayment(500) ? "Approved" : "Denied") . "\n";
}Classes implement interfaces to adhere to contracts. Anonymous classes can implement interfaces on the fly.
- Interface:
interface Authenticator { public function authenticate(string $token): bool; } - Class:
class JwtAuthenticator implements Authenticator { ... } - Anonymous:
new class implements Authenticator { ... }
// Interface Implementation in Hack
interface Authenticator {
public function authenticate(string $token): bool;
}
class JwtAuthenticator implements Authenticator {
public function authenticate(string $token): bool {
return str_starts_with($token, "valid_");
}
}
// Anonymous class implementing interface
function createFakeAuthenticator(): Authenticator {
return new class implements Authenticator {
public function authenticate(string $token): bool {
return true; // Always authenticate
}
};
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$auth = new JwtAuthenticator();
echo "JWT Auth: " . ($auth->authenticate("valid_token") ? "Success" : "Failed") . "\n";
echo "JWT Auth invalid: " . ($auth->authenticate("invalid") ? "Success" : "Failed") . "\n";
$fake = createFakeAuthenticator();
echo "Fake Auth: " . ($fake->authenticate("any") ? "Success" : "Failed") . "\n";
}Concrete classes extend abstract classes and implement abstract methods.
- Abstract class:
abstract class DatabaseDriver { abstract protected function connect(string $dsn): bool; } - Concrete:
class MySqlDriver extends DatabaseDriver { protected function connect(string $dsn): bool { ... } }
// Abstract Class Implementation in Hack
abstract class DatabaseDriver {
abstract protected function connect(string $dsn): bool;
abstract protected function execute(string $sql): array;
public function query(string $sql): array {
if ($this->connect("default_dsn")) {
return $this->execute($sql);
}
return [];
}
}
class MySqlDriver extends DatabaseDriver {
protected function connect(string $dsn): bool {
echo "Connecting to MySQL: $dsn\n";
return true;
}
protected function execute(string $sql): array {
echo "Executing MySQL query: $sql\n";
return ['result' => 'data'];
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$driver = new MySqlDriver();
$result = $driver->query("SELECT * FROM users");
print_r($result);
}Traits can be combined, and conflicts can be resolved using insteadof and as.
- Combining:
use TraitA, TraitB; - Conflict:
TraitA::method insteadof TraitB; - Alias:
TraitA::method as protected; - Properties: traits can have properties
// Trait Methods in Hack
trait LoggerTrait {
private function log(string $message): void {
echo "[LOG] " . $message . "\n";
}
}
class Application {
use LoggerTrait;
public function run(): void {
$this->log("Application started");
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$app = new Application();
$app->run();
}Magic methods like __get, __set, __call are invoked dynamically.
- __get: intercept property reads
- __set: intercept property writes
- __call: intercept method calls
- __toString: string conversion
// Magic Methods in Hack
class MagicContainer {
private array $data = [];
public function __get(string $name) {
return $this->data[$name] ?? null;
}
public function __set(string $name, $value): void {
$this->data[$name] = $value;
}
public function __call(string $name, array $arguments) {
return "Called method: " . $name;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$container = new MagicContainer();
$container->name = "Alice";
echo "Name: " . $container->name . "\n";
echo $container->nonExistentMethod("arg") . "\n";
}Namespaces organize code and can be accessed from other namespaces using full names.
- Definition:
namespace Core\Security; - Use:
use Core\Security\Encryption; - Reflection: can access private members across namespaces
// Namespace Visibility in Hack
namespace CoreSecurity {
class Encryption {
private static string $masterKey = "SUPER_SECRET_KEY_123";
private static function encryptData(string $data): string {
return openssl_encrypt($data, 'AES-256-CBC', self::$masterKey);
}
}
}
namespace Hack {
// Accessing private static property and method from different namespace using reflection
function hackNamespace(): void {
$reflection = new ReflectionClass('Core\Security\Encryption');
$prop = $reflection->getProperty('masterKey');
$prop->setAccessible(true);
echo "Master Key: " . $prop->getValue() . "\n";
$method = $reflection->getMethod('encryptData');
$method->setAccessible(true);
$encrypted = $method->invoke(null, "hacked_data");
echo "Encrypted: " . $encrypted . "\n";
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
HackhackNamespace();
}A simple container can resolve dependencies recursively.
- Container class: with
getmethod - Resolve: use reflection to find constructor types
- Autowiring: automatically instantiate dependencies
// Dependency Injection Container in Hack
class Container {
private array $services = [];
private array $parameters = [];
private function resolveDependencies(string $class): object {
$reflection = new ReflectionClass($class);
$constructor = $reflection->getConstructor();
if (!$constructor) {
return $reflection->newInstance();
}
$params = [];
foreach ($constructor->getParameters() as $param) {
$params[] = $this->get($param->getType()->getName());
}
return $reflection->newInstanceArgs($params);
}
public function get(string $id) {
return $this->services[$id] ?? $this->resolveDependencies($id);
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$container = new Container();
// Register a service
$container->services['Logger'] = new class {
public function log($msg) { echo "[LOG] $msg\n"; }
};
$logger = $container->get('Logger');
$logger->log("Hello from container");
}Event dispatchers allow you to register listeners for events and dispatch them.
- Listeners: array of callables per event
- Add listener:
addListener('event', callback) - Dispatch:
dispatch('event', $data)
// Event Dispatcher in Hack
class EventDispatcher {
private array $listeners = [];
public function addListener(string $event, callable $listener): void {
$this->listeners[$event][] = $listener;
}
public function dispatch(string $event, array $data = []): void {
foreach ($this->listeners[$event] ?? [] as $listener) {
$listener($data);
}
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$dispatcher = new EventDispatcher();
$dispatcher->addListener('user.login', function($data) {
echo "User " . $data['username'] . " logged in\n";
});
$dispatcher->dispatch('user.login', ['username' => 'Alice']);
}Use custom exceptions, multiple catch blocks, and finally for cleanup.
- Multiple catches: catch specific exception types
- Custom: extend
Exception - Finally: always executed
// Advanced Exception Handling in Hack
class SecureSystem {
private function validateAccess(string $token): void {
if ($token !== "valid_token") {
throw new Exception("Invalid token");
}
}
public function accessData(string $token): string {
$this->validateAccess($token);
return "Sensitive Data";
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$system = new SecureSystem();
try {
echo $system->accessData("valid_token") . "\n";
echo $system->accessData("invalid_token") . "\n";
} catch (Exception $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
}Objects can be serialized using serialize and unserialize. __sleep and __wakeup control the process.
- __sleep: which properties to serialize
- __wakeup: restore after deserialization
- Reflection: can be used to manipulate serialized data
// Serialization in Hack
class SecureData {
private string $password = "secret";
private string $apiKey = "12345";
public function __sleep() {
return ['password']; // Only serialize password
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$data = new SecureData();
$serialized = serialize($data);
echo "Serialized: " . $serialized . "\n";
$unserialized = unserialize($serialized);
// Access private property via reflection for demonstration
$reflection = new ReflectionClass($unserialized);
$prop = $reflection->getProperty('apiKey');
$prop->setAccessible(true);
echo "API Key: " . $prop->getValue($unserialized) . "\n";
}Closures can be bound to objects to access their private properties and methods.
- bindTo:
$closure->bindTo($object, 'ClassName') - Access: call private methods and read private properties
- Uses: for debugging or advanced metaprogramming
// Closure Binding in Hack
class UserService {
private string $userId = "123";
private array $permissions = ['read', 'write'];
private function checkPermission(string $action): bool {
return in_array($action, $this->permissions);
}
}
function hackClosureBinding(): void {
$userService = new UserService();
$closure = function() {
return [
'userId' => $this->userId,
'permissions' => $this->permissions,
'canDelete' => $this->checkPermission('delete')
];
};
// Bind closure to object
$bound = $closure->bindTo($userService, 'UserService');
$data = $bound();
print_r($data);
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
hackClosureBinding();
}Implementing ArrayAccess allows objects to be accessed like arrays.
- Methods: offsetGet, offsetSet, offsetExists, offsetUnset
- Use:
$config['key'] - Restrictions: can be bypassed via reflection
// ArrayAccess Interface in Hack
class Config implements ArrayAccess {
private array $settings = [
'db_host' => 'localhost',
'db_user' => 'root',
'db_pass' => 'secret'
];
public function offsetGet($offset) {
return $this->settings[$offset] ?? null;
}
public function offsetSet($offset, $value) {
throw new Exception("Cannot modify config");
}
public function offsetExists($offset) {
return isset($this->settings[$offset]);
}
public function offsetUnset($offset) {
throw new Exception("Cannot unset config");
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$config = new Config();
echo "DB Host: " . $config['db_host'] . "\n";
// $config['db_host'] = 'new_host'; // Exception
}Implement Iterator to allow object iteration in foreach.
- Methods: current, key, next, rewind, valid
- Use:
foreach ($collection as $key => $value) - Manipulation: can modify iterator state via reflection
// Iterator Interface in Hack
class DataCollection implements Iterator {
private array $items = [];
private int $position = 0;
public function __construct(array $items) {
$this->items = $items;
}
public function current() {
return $this->items[$this->position];
}
public function key() {
return $this->position;
}
public function next() {
$this->position++;
}
public function rewind() {
$this->position = 0;
}
public function valid() {
return isset($this->items[$this->position]);
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$collection = new DataCollection(['item1', 'item2', 'item3']);
foreach ($collection as $key => $value) {
echo "Key: $key, Value: $value\n";
}
}Objects with __invoke can be called like functions.
- Definition:
public function __invoke(string $msg): string - Usage:
$obj("Hello") - Reflection: can modify invoke behavior
// Invoke Object in Hack
class FunctionObject {
private string $prefix = ">> ";
public function __invoke(string $message): string {
return $this->prefix . $message;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$obj = new FunctionObject();
echo $obj("Hello World") . "\n";
}__clone is called when an object is cloned. It allows modification of the cloned object.
- Clone:
$copy = clone $original; - __clone: can modify properties of the new instance
- Reflection: can bypass clone behavior
// Clone Behavior in Hack
class Cloneable {
private string $secret = "original_secret";
private array $data = ['key' => 'value'];
public function __clone() {
$this->secret = "cloned_" . $this->secret;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$original = new Cloneable();
$copy = clone $original;
// Use reflection to inspect private properties
$reflection = new ReflectionClass($original);
$prop = $reflection->getProperty('secret');
$prop->setAccessible(true);
echo "Original secret: " . $prop->getValue($original) . "\n";
echo "Copy secret: " . $prop->getValue($copy) . "\n";
}__toString defines how an object is converted to a string.
- Definition:
public function __toString(): string - Usage:
echo $object; - Reflection: can override the method
// __toString Magic Method in Hack
class User {
private string $username = "user123";
private string $email = "user@email.com";
public function __toString(): string {
return "User: " . $this->username;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$user = new User();
echo $user . "\n";
}__debugInfo controls the output of var_dump for the object.
- Definition:
public function __debugInfo(): array - Usage:
var_dump($object) - Expose: can hide or reveal sensitive data
// __debugInfo in Hack
class Debuggable {
private array $sensitive = [
'password' => 'secret123',
'api_key' => 'xyz789',
'token' => 'abc456'
];
public function __debugInfo(): array {
return ['public' => 'info'];
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$debuggable = new Debuggable();
var_dump($debuggable);
}Reflection can inspect static properties and methods.
- ReflectionClass: get static properties
- Modify: change static values via reflection
- Tools: Hack's type checker provides compile‑time analysis
// Static Analysis in Hack
class StaticAnalyzer {
private static array $rules = [
'security' => ['password', 'token'],
'performance' => ['cache', 'optimize']
];
private static function validateRule(string $rule): bool {
return in_array($rule, array_keys(self::$rules));
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$reflection = new ReflectionClass('StaticAnalyzer');
$prop = $reflection->getProperty('rules');
$prop->setAccessible(true);
$rules = $prop->getValue();
print_r($rules);
}Hack's type system enforces static types. Reflection can bypass some type restrictions.
- Type annotations: optional but recommended
- Type inference: compiler infers types
- Bypass: using reflection to assign wrong types
// Type System in Hack
class TypedContainer {
private int $id = 123;
private string $name = "container";
private array $items = ['item1', 'item2'];
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$container = new TypedContainer();
$reflection = new ReflectionClass($container);
$prop = $reflection->getProperty('id');
$prop->setAccessible(true);
echo "ID: " . $prop->getValue($container) . "\n";
$prop->setValue($container, 456);
echo "New ID: " . $prop->getValue($container) . "\n";
}Enums can have methods, implement interfaces, and be used with pattern matching.
- Methods: define behavior
- Interfaces: enums can implement interfaces
- Match: use
matchfor exhaustive handling
// Advanced Enum Features in Hack
enum Color: string {
Red = '#FF0000';
Green = '#00FF00';
Blue = '#0000FF';
}
enum HttpStatus: int {
OK = 200;
Created = 201;
BadRequest = 400;
NotFound = 404;
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$color = Color::Red;
echo "Color: " . $color . "\n";
$status = HttpStatus::OK;
echo "Status: " . $status . "\n";
}Attributes like <<__Memoize>> add metadata to classes, methods, or properties.
- Built‑in:
<<__Memoize>>,<<__EntryPoint>> - Custom: can define custom attributes
- Reflection: read attributes at runtime
// Attributes in Hack (using << >> syntax)
<<__Memoize>>
class ExpensiveCalculator {
public function compute(int $n): int {
// Simulate expensive computation
return $n * $n;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$calc = new ExpensiveCalculator();
echo $calc->compute(5) . "\n";
echo $calc->compute(5) . "\n"; // cached
}Hack does not have built‑in property promotion (unlike PHP 8), but you can declare properties in the class body.
- Manual: define properties and assign in constructor
- Type annotations: use type hints
- Visibility: public, private, protected
// Constructor Property Promotion (not in Hack, but we can use normal)
class Product {
public function __construct(
private string $id,
private float $price
) {}
public function getId(): string { return $this->id; }
public function getPrice(): float { return $this->price; }
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$p = new Product("PROD-001", 99.99);
echo "ID: " . $p->getId() . ", Price: $" . $p->getPrice() . "\n";
}Match expressions are similar to switch but return a value and are stricter.
- Syntax:
match ($code) { 200 => 'OK', default => 'Unknown' } - Exhaustive: must cover all cases if no default
- Expression: returns a value
// Match Expression in Hack
function getStatusName(int $code): string {
return match ($code) {
200 => 'OK',
404 => 'Not Found',
500 => 'Server Error',
default => 'Unknown'
};
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "Status 200: " . getStatusName(200) . "\n";
echo "Status 404: " . getStatusName(404) . "\n";
echo "Status 418: " . getStatusName(418) . "\n";
}Hack does not have Fibers, but you can achieve similar cooperative multitasking using async/await.
- Async: non‑blocking operations
- SleepWaitHandle: simulate delays
- Cooperative: yield control using
await
// Fibers (not in Hack, but we can simulate with async)
async function simulateFiber(): Awaitable<void> {
echo "Fiber started\n";
await SleepWaitHandle::create(100);
echo "Fiber resumed after 100ms\n";
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
await simulateFiber();
}Generators use yield to produce a sequence of values lazily.
- Function:
function gen(): Generator<int> { yield 1; yield 2; } - Iteration:
foreach ($gen as $value) - Send: can send values to generators
// Generators in Hack
function genNumbers(): Generator<int> {
for ($i = 0; $i < 5; $i++) {
yield $i;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$gen = genNumbers();
foreach ($gen as $value) {
echo "Value: $value\n";
}
}WeakMaps hold weak references to objects, allowing them to be garbage collected.
- Creation:
new WeakMap() - Store:
$wm[$obj] = 'value' - Garbage: if object is collected, entry is removed
// Weak Maps in Hack
$wm = new WeakMap();
$obj1 = new stdClass();
$obj2 = new stdClass();
$wm[$obj1] = 'value1';
$wm[$obj2] = 'value2';
echo "Value for obj1: " . $wm[$obj1] . "\n";
unset($obj1); // $wm entry for obj1 may be removed
echo "After unset obj1: " . (isset($wm[$obj1]) ? 'exists' : 'gone') . "\n";
Hack does not have readonly classes, but you can make a class immutable by using only private properties and getters.
- Private: all properties private
- No setters: only getters
- Final: optionally mark class as final
// Readonly Classes (Hack doesn't have this, but we can use final)
final class ImmutableData {
public function __construct(
private string $name,
private int $age
) {}
public function getName(): string { return $this->name; }
public function getAge(): int { return $this->age; }
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$data = new ImmutableData('Alice', 25);
echo "Name: " . $data->getName() . ", Age: " . $data->getAge() . "\n";
}Hack does not have DNF types, but you can use union types to handle multiple types.
- Union:
int|string|float - Type checking:
is_int(),is_string() - Type safety: runtime checks
// DNF Types (not in Hack, but we can use union)
function processValue(int|string|float $value): void {
if (is_int($value)) echo "Integer: $value\n";
else if (is_string($value)) echo "String: $value\n";
else echo "Float: $value\n";
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
processValue(42);
processValue("Hello");
processValue(3.14);
}Class constants can be arrays, and reflection can access private constants.
- Array constants:
private const CONFIG = ['debug' => false]; - Reflection:
ReflectionClass::getConstant() - Modification: via reflection (advanced)
// Constants in Hack
class ConstantManager {
private const API_KEY = 'secret_123';
private const CONFIG = [
'debug' => false,
'env' => 'production'
];
public static function getApiKey(): string {
return self::API_KEY;
}
public static function getConfig(): array {
return self::CONFIG;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "API Key: " . ConstantManager::getApiKey() . "\n";
$config = ConstantManager::getConfig();
print_r($config);
}Global constants are defined with define() and are globally accessible.
- Define:
define('KEY', 'value') - Access:
KEY - Modify: can be redefined (with warning)
// Global Constants in Hack
define('SECURE_KEY', 'secure_value_123');
define('DATABASE_CONFIG', [
'host' => 'localhost',
'user' => 'root',
'pass' => 'secret'
]);
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "Secure Key: " . SECURE_KEY . "\n";
print_r(DATABASE_CONFIG);
}Late static binding resolves the called class at runtime using static instead of self.
- self: class where method is defined
- static: class where method is called
- Use:
static::$value
// Late Static Binding in Hack
class BaseClass {
protected static string $value = 'base';
public static function getValue(): string {
return static::$value;
}
}
class ChildClass extends BaseClass {
protected static string $value = 'child';
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
echo "Base value: " . BaseClass::getValue() . "\n";
echo "Child value: " . ChildClass::getValue() . "\n";
}Anonymous classes are created on the fly and can be used for one‑off objects.
- Creation:
new class { ... } - Properties: can have private properties
- Reflection: can inspect and modify
// Anonymous Class Properties in Hack
$object = new class {
private string $secret = 'hidden_value';
private array $data = ['key' => 'value'];
public function getSecret(): string {
return $this->secret;
}
};
<<__EntryPoint>>
async function main(): Awaitable<void> {
$reflection = new ReflectionClass($object);
$prop = $reflection->getProperty('secret');
$prop->setAccessible(true);
echo "Secret: " . $prop->getValue($object) . "\n";
$prop->setValue($object, 'hacked_secret');
echo "New Secret: " . $object->getSecret() . "\n";
}Stringable ensures an object has __toString.
- Interface:
interface Stringable { public function __toString(): string; } - Implement: any class can implement it
- Use: type‑hint
Stringable
// Stringable Interface in Hack
class User implements Stringable {
private string $username;
private string $email;
public function __construct(string $username, string $email) {
$this->username = $username;
$this->email = $email;
}
public function __toString(): string {
return "User: " . $this->username;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$user = new User('alice', 'alice@email.com');
echo $user . "\n";
}Hack does not have intersection types, but you can require multiple interfaces using a class that implements them.
- Multiple interfaces:
class User implements Authenticatable, Authorizable { ... } - Type check:
function check(Authenticatable&Authorizable $user)not supported, but you can use a class type
// Intersection Types (not in Hack, but we can use interfaces)
interface Authenticatable { public function auth(): bool; }
interface Authorizable { public function authz(string $action): bool; }
class User implements Authenticatable, Authorizable {
public function auth(): bool { return true; }
public function authz(string $action): bool { return true; }
}
function check( Authenticatable&Authorizable $user ): void {
echo "Authenticated: " . ($user->auth() ? 'Yes' : 'No') . "\n";
echo "Authorized: " . ($user->authz('read') ? 'Yes' : 'No') . "\n";
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$user = new User();
check($user);
}IteratorAggregate allows a class to return an iterator via getIterator().
- Interface:
interface IteratorAggregate { function getIterator(): Traversable; } - Implementation: return an
ArrayIteratoror custom iterator - Use:
foreach ($object as $item)
// IteratorAggregate in Hack
class DataCollection implements IteratorAggregate {
private array $items = ['item1', 'item2', 'item3'];
public function getIterator(): Traversable {
return new ArrayIterator($this->items);
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$collection = new DataCollection();
foreach ($collection as $item) {
echo "Item: $item\n";
}
}ArrayObject allows objects to be used like arrays.
- Creation:
$array = new ArrayObject(['key' => 'value']); - Access:
$array['key'] - Reflection: can manipulate underlying storage
// ArrayObject in Hack
$array = new ArrayObject(['key1' => 'value1', 'key2' => 'value2']);
$array['key3'] = 'value3';
echo "Key1: " . $array['key1'] . "\n";
echo "Key3: " . $array['key3'] . "\n";
unset($array['key2']);
print_r($array);SplFixedArray provides fixed‑size arrays. Hack's Vector is more commonly used.
- Size: fixed, can be changed via reflection
- Access: array‑like
- Performance: slightly faster for fixed sizes
// SplFixedArray in Hack (use Vector instead)
$fixed = new SplFixedArray(5);
for ($i = 0; $i < 5; $i++) {
$fixed[$i] = "item_" . $i;
}
echo "Size: " . $fixed->getSize() . "\n";
echo "Item 2: " . $fixed[2] . "\n";
// Hack has Vector which is dynamic, so SplFixedArray is not commonly used.
// But it's available via HHVM compatibility.SplObjectStorage stores objects as keys and allows attaching arbitrary values.
- Create:
$storage = new SplObjectStorage(); - Attach:
$storage->attach($obj, 'value'); - Iterate:
foreach ($storage as $obj)
// SplObjectStorage in Hack
$storage = new SplObjectStorage();
$obj1 = new stdClass();
$obj2 = new stdClass();
$storage->attach($obj1, 'value1');
$storage->attach($obj2, 'value2');
foreach ($storage as $obj) {
echo "Object: " . spl_object_id($obj) . " => " . $storage[$obj] . "\n";
}Reflection can change property visibility and values.
- Set accessible:
$prop->setAccessible(true) - Get/Set value:
$prop->getValue($obj),$prop->setValue($obj, 'new') - Add property: create dynamic properties
// ReflectionProperty Modifiers in Hack
class ModifierTest {
private string $private = 'private_val';
protected string $protected = 'protected_val';
public string $public = 'public_val';
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$test = new ModifierTest();
$reflection = new ReflectionClass($test);
$prop = $reflection->getProperty('private');
$prop->setAccessible(true);
echo "Private: " . $prop->getValue($test) . "\n";
$prop->setValue($test, 'hacked_private');
echo "New Private: " . $prop->getValue($test) . "\n";
}Reflection can make private or protected methods accessible.
- ReflectionMethod:
$method->setAccessible(true) - Invoke:
$method->invoke($obj, ...) - Static:
$method->invoke(null, ...)
// Method Visibility in Hack
class MethodVisibility {
private function secretMethod(): string {
return "This is a secret";
}
protected function protectedMethod(): string {
return "This is protected";
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$test = new MethodVisibility();
$reflection = new ReflectionClass($test);
$method = $reflection->getMethod('secretMethod');
$method->setAccessible(true);
echo $method->invoke($test) . "\n";
}Static private and protected methods can also be made accessible via reflection.
- ReflectionMethod: for static methods
- Set accessible:
$method->setAccessible(true) - Invoke:
$method->invoke(null, ...)
// Static Method Visibility in Hack
class StaticMethods {
private static function privateStatic(): string {
return "Private static";
}
protected static function protectedStatic(): string {
return "Protected static";
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$reflection = new ReflectionMethod('StaticMethods', 'privateStatic');
$reflection->setAccessible(true);
echo $reflection->invoke(null) . "\n";
}Final methods cannot be overridden, but they can still be called via reflection.
- Call:
$obj->finalMethod()works - Override: not possible, but you can create a new method
- Reflection: can still access the method
// Final Methods in Hack
class ParentClass {
final public function finalMethod(): string {
return "Final method";
}
private function privateMethod(): string {
return "Private method";
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$parent = new ParentClass();
$reflection = new ReflectionClass($parent);
$method = $reflection->getMethod('privateMethod');
$method->setAccessible(true);
echo $method->invoke($parent) . "\n";
// final method cannot be overridden, but can be called
echo $parent->finalMethod() . "\n";
}Reflection can create instances without calling the constructor using newInstanceWithoutConstructor.
- ReflectionClass:
$reflection->newInstanceWithoutConstructor() - Singleton bypass: create multiple instances
- Limitations: properties may need manual initialization
// Private Constructor in Hack
class SingletonHack {
private static ?SingletonHack $instance = null;
private function __construct() {}
public static function getInstance(): SingletonHack {
if (self::$instance === null) {
self::$instance = new SingletonHack();
}
return self::$instance;
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$reflection = new ReflectionClass('SingletonHack');
$instance = $reflection->newInstanceWithoutConstructor();
echo "Instance created: " . ($instance ? 'Yes' : 'No') . "\n";
}This is a comprehensive example of using reflection to bypass all visibility restrictions and compromise a secure system.
- Static properties: modify master keys
- Private properties: read and write sensitive data
- Private methods: call hidden logic
- Final methods: bypass final restrictions
- Private constructor: create instances without calling constructor
// Reflection and Metaprogramming in Hack
class SuperSecure {
private static string $masterKey = "super_secret";
private string $sessionId = "session_456";
private array $userData = ['username' => 'admin', 'password' => 'admin123'];
final private function validateToken(string $token): bool {
return $token === "valid_token";
}
private function getSensitiveData(): array {
return $this->userData;
}
public function authenticate(string $token): bool {
return $this->validateToken($token);
}
}
<<__EntryPoint>>
async function main(): Awaitable<void> {
$system = new SuperSecure();
$reflection = new ReflectionClass($system);
// Hack static property
$staticProp = $reflection->getProperty('masterKey');
$staticProp->setAccessible(true);
echo "Master Key: " . $staticProp->getValue() . "\n";
// Hack private property
$sessionProp = $reflection->getProperty('sessionId');
$sessionProp->setAccessible(true);
echo "Session: " . $sessionProp->getValue($system) . "\n";
// Hack private method
$dataMethod = $reflection->getMethod('getSensitiveData');
$dataMethod->setAccessible(true);
$data = $dataMethod->invoke($system);
print_r($data);
// Bypass final method
$authMethod = $reflection->getMethod('authenticate');
$authMethod->setAccessible(true);
echo "Auth: " . ($authMethod->invoke($system, 'any') ? 'Success' : 'Failed') . "\n";
}Quick Navigation
Ready for your next interview?
Take a real-time mock interview and boost your confidence.
Start Mock Interview