askesis

PHP essentials: 50 cards on modern PHP 8, arrays, enums and PDO

Modern PHP 8 with short snippets: strict types, arrays, match, nullsafe and named arguments, enums, readonly, Composer, PDO and password_hash.

ProgrammingBeginner50 cardsFree

Practice now

Study without an account

0 of 50 seen0 graduated
What does declare(strict_types=1); do?

Scheduled with the default Askesis timings. Progress is saved in this browser.

All 50 cards

  1. 1
    What does declare(strict_types=1); do?
    Makes scalar type declarations strict for calls made from that file: passing "5" to an int parameter throws a TypeError instead of being coerced to 5
    It must be the first statement in the file, and it applies per file, to the calling code.
  2. 2
    Add parameter and return types to a function
    php
    function add(int $a, int $b): int
    {
        return $a + $b;
    }
  3. 3
    Nullable and union types
    ?string accepts a string or null (the same as string|null); union types like int|float (PHP 8.0) accept any of the listed types
  4. 4
    == vs ===
    == compares after type juggling; === requires the same type and value. Prefer ===
    "1" == "01" is true, because numeric strings compare as numbers; "1" === "01" is false.
  5. 5
    What is 0 == "a" in PHP 8?
    false. Since PHP 8.0, comparing a number with a non-numeric string compares them as strings
    In PHP 7 the string was cast to 0, so the result was true, a classic source of bugs.
  6. 6
    What is "10" == "1e1"?
    true: both strings are numeric, so == compares them as numbers (10 and 10.0)
  7. 7
    void vs never return types
    void: the function returns without a value. never (PHP 8.1): it never returns normally, because it always throws or exits
  8. 8
    Single vs double quotes
    Double quotes interpolate variables ("Hi $name", "Hi {$user->name}") and escapes like \n; single quotes are literal. Concatenate strings with .
  9. 9
    What does 7 / 2 return in PHP?
    3.5: / returns a float unless the division is exact (6 / 2 is int(3))
    Use intdiv(7, 2) for integer division (3).
  10. 10
    What is a PHP array?
    An ordered map: one type serves as both list and dictionary, with integer or string keys, and it remembers insertion order
  11. 11
    What does this print?
    php
    $a = ["1" => "x", 1 => "y"];
    echo count($a);
    1: the string key "1" is cast to the integer 1, so the second entry overwrites the first
    Keys like true are cast too (to 1), but "01" stays a string.
  12. 12
    Append to an array
    php
    $list = [1, 2];
    $list[] = 3; // [1, 2, 3]
    array_push($list, 3) does the same.
  13. 13
    What does array_filter([1, 0, 2]) return?
    [0 => 1, 2 => 2]: it drops falsy values but keeps the original keys, leaving a gap
    Wrap it in array_values(…) to reindex. Mind the argument order: array_filter($array, $fn) but array_map($fn, $array).
  14. 14
    Destructure an array
    php
    [$first, $second] = [1, 2];
    ['id' => $id, 'name' => $name] = $row;
    [$a, $b] = [$b, $a]; // swap
  15. 15
    Merge arrays with the spread operator
    php
    $all = [...$defaults, ...$overrides];
    Since PHP 8.1 this works with string keys too, where later keys win.
  16. 16
    What does this print?
    php
    $a = [1, 2, 3];
    foreach ($a as &$v) {}
    foreach ($a as $v) {}
    print_r($a);
    [1, 2, 2]: after the first loop $v is still a reference to $a[2], so the second loop overwrites that element on every pass
    Call unset($v) after a by-reference foreach.
  17. 17
    Are arrays passed by value or by reference?
    By value: assigning or passing an array gives an independent copy (made lazily, on write). Objects, by contrast, are passed as handles to the same object
  18. 18
    isset($a['k']) vs array_key_exists('k', $a)
    isset returns false when the key exists but its value is null; array_key_exists checks only for the key
  19. 19
    Read a request parameter with a fallback
    php
    $page = $_GET['page'] ?? 1;
    ?? doesn’t warn about undefined keys. $config['debug'] ??= false; assigns only when the key is unset or null.
  20. 20
    A match expression
    php
    $label = match ($status) {
        200, 201 => 'OK',
        404 => 'Not found',
        default => 'Error',
    };
    PHP 8.0+.
  21. 21
    match vs switch
    match is an expression that returns a value, compares with ===, never falls through, and throws UnhandledMatchError when no arm matches; switch compares with == and falls through without break
  22. 22
    Get a nested value when any link may be null
    php
    $country = $session?->user?->getAddress()?->country;
    The nullsafe operator ?-> (PHP 8.0) short-circuits the rest of the chain to null.
  23. 23
    What are named arguments?
    Passing arguments by parameter name (PHP 8.0), so you can skip optional parameters and the order doesn’t matter
    php
    htmlspecialchars($text, double_encode: false);
  24. 24
    Declare and assign properties in the constructor signature
    php
    class Point
    {
        public function __construct(
            public readonly int $x = 0,
            public readonly int $y = 0,
        ) {}
    }
    Constructor property promotion, PHP 8.0.
  25. 25
    What does readonly do on a property?
    The property can be initialized once, from inside the class, and any later write throws an Error (PHP 8.1). It must be typed
    PHP 8.2 added readonly class, which makes every property readonly.
  26. 26
    A backed enum
    php
    enum Suit: string
    {
        case Hearts = 'H';
        case Spades = 'S';
    }
    
    Suit::from('H');    // Suit::Hearts
    Suit::tryFrom('X'); // null
    Enums arrived in PHP 8.1. from throws a ValueError for an unknown value.
  27. 27
    What can PHP enums do?
    Cases are singleton objects compared with ===; ->name gives the case name and ->value the backing value; Suit::cases() lists them; enums can have methods and constants and implement interfaces
  28. 28
    Arrow functions vs anonymous functions
    fn($x) => $x * $factor captures outer variables by value automatically; function ($x) use ($factor) { … } must list them in use
    Arrow functions (PHP 7.4) hold a single expression.
  29. 29
    Get a callable for a function or method
    php
    $len = strlen(...);
    $save = $repo->save(...);
    First-class callable syntax, PHP 8.1.
  30. 30
    Check whether a string contains a substring
    php
    if (str_contains($haystack, 'needle')) { /* … */ }
    str_contains, str_starts_with and str_ends_with arrived in PHP 8.0.
  31. 31
    Why is this check buggy?
    php
    if (strpos('apple', 'a')) {
        echo 'found';
    }
    strpos returns 0 when the match is at the start, and 0 is falsy. Compare with !== false, or use str_contains
  32. 32
    Throw if a lookup returns null, in one expression
    php
    $user = $repo->find($id) ?? throw new NotFoundException();
    throw became an expression in PHP 8.0.
  33. 33
    A computed property with a property hook
    php
    class User
    {
        public function __construct(
            public string $first,
            public string $last,
        ) {}
    
        public string $fullName {
            get => "{$this->first} {$this->last}";
        }
    }
    Property hooks arrived in PHP 8.4.
  34. 34
    What is a trait?
    A reusable set of methods (and properties) copied into a class with use, giving code reuse despite single inheritance
    php
    trait Greets
    {
        public function greet(): string
        {
            return 'Hello from ' . static::class;
        }
    }
    
    class User { use Greets; }
  35. 35
    How does PSR-4 autoloading work?
    Composer maps a namespace prefix to a directory, so App\Models\User is loaded from src/Models/User.php the first time the class is used
    Configure it under autoload.psr-4 in composer.json, e.g. "App\\": "src/".
  36. 36
    PHP’s exception hierarchy
    Everything thrown implements Throwable, which has two branches: Exception (application errors) and Error (engine errors like TypeError, ValueError and DivisionByZeroError)
    catch (Exception $e) doesn’t catch Errors; catch Throwable to get both.
  37. 37
    Add a package with Composer
    bash
    composer require monolog/monolog
    This updates composer.json and composer.lock and installs the package into vendor/.
  38. 38
    composer install vs composer update
    install installs the exact versions recorded in composer.lock; update resolves the newest versions allowed by composer.json and rewrites the lock file
    Commit composer.lock for applications, so every machine gets the same versions.
  39. 39
    Load Composer’s autoloader
    php
    require __DIR__ . '/vendor/autoload.php';
  40. 40
    What does the version constraint ^2.3 allow?
    Any version from 2.3.0 up to, but not including, 3.0.0: the updates that shouldn’t break compatibility under semantic versioning
    ~2.3 also means >=2.3.0 <3.0.0, but ~2.3.1 means >=2.3.1 <2.4.0.
  41. 41
    Query a database safely with PDO
    php
    $stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
    $stmt->execute([$email]);
    $user = $stmt->fetch();
    Prepared statements send values separately from the SQL, which prevents SQL injection. Never interpolate input into SQL.
  42. 42
    Use named placeholders with PDO
    php
    $stmt = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
    $stmt->execute(['name' => $name, 'id' => $id]);
  43. 43
    How do PDO errors surface in PHP 8?
    As PDOExceptions: PDO::ERRMODE_EXCEPTION is the default since PHP 8.0. Before that the default was silent mode, so a failed query just returned false
    php
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
  44. 44
    Hash and check a password
    php
    $hash = password_hash($password, PASSWORD_DEFAULT);
    
    if (password_verify($input, $hash)) { /* logged in */ }
    The hash string includes the algorithm, cost and salt; store it in a VARCHAR(255) column. PHP 8.4 raised the default bcrypt cost from 10 to 12.
  45. 45
    Why not store passwords with md5() or sha1()?
    They are fast, unsalted hashes, so leaked hashes can be brute-forced or looked up quickly. password_hash uses a slow, salted algorithm (bcrypt by default, or Argon2)
  46. 46
    Escape user input when printing it into HTML
    php
    echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
    This prevents XSS. Since PHP 8.1, ENT_QUOTES is part of the default flags.
  47. 47
    echo vs print_r vs var_dump
    echo outputs strings; print_r prints arrays and objects readably; var_dump also shows types and lengths, e.g. string(3) "abc"
  48. 48
    Common superglobals
    $_GET (query string), $_POST (form body), $_SERVER (request and server info), $_SESSION (session data, after session_start()), $_COOKIE and $_FILES
  49. 49
    Chain function calls with the pipe operator
    php
    $slug = $title
        |> trim(...)
        |> strtolower(...)
        |> (fn($s) => str_replace(' ', '-', $s));
    The pipe operator |> arrived in PHP 8.5. Each step must be a callable that takes one argument; arrow functions need parentheses.
  50. 50
    Serve the current directory for local development
    bash
    php -S localhost:8000
    The built-in web server is meant for development only, not production.

Frequently asked questions

Which PHP version does the deck use?

PHP 8. Newer features are marked with the version that introduced them, from match and named arguments (8.0) and enums (8.1) to property hooks (8.4) and the pipe operator (8.5).

Does it cover Laravel or Symfony?

No. It covers the language, Composer and PDO, which both frameworks build on.

How do I prevent SQL injection in PHP?

Use prepared statements (PDO or mysqli) with placeholders and pass the values separately. Never build SQL strings from user input.

Intermediate

JavaScript

Master the JavaScript every developer uses daily: scope, closures, equality, array methods, promises, async/await and the event loop.

50 cards
Intermediate

Advanced JS

Fifty advanced JavaScript cards: event-loop ordering puzzles, generators and async iteration, this binding, Proxy and Reflect, modules, AbortController and coercion edge cases.

50 cards
Intermediate

TypeScript

Learn the TypeScript you use every day: narrowing, generics, keyof, mapped and conditional types, utility types, satisfies and strict mode, with code on every card.

50 cards
Beginner

Python

Learn idiomatic Python fast: comprehensions, f-strings, args and kwargs, generators, decorators, context managers and the standard library.

50 cards
Beginner

Git & terminal

The Git commands and shell skills developers use every day: branching, rebasing, undoing mistakes, pipes, redirects, permissions and processes.

50 cards
Beginner

Java

Core Java with short snippets: equals vs ==, records, sealed types, collections, generics, exceptions, streams, lambdas and JVM basics.

50 cards
Beginner

C#

C# and .NET fundamentals with short snippets: value vs reference types, properties, records, nullable reference types, LINQ, async/await and DI.

50 cards
Beginner

Kotlin

Kotlin fundamentals with short snippets: null safety, val vs var, data and sealed classes, extension functions, scope functions and coroutines.

50 cards
Beginner

Swift

Swift fundamentals with short snippets: optionals, structs vs classes, protocols, enums with associated values, closures, ARC, async/await and SwiftUI.

50 cards
Beginner

Flutter

Flutter and Dart basics: null safety, futures and streams, stateless vs stateful widgets, setState, hot reload, keys, layout and state management.

50 cards
Beginner

Ruby

Ruby fundamentals with short snippets: blocks, procs and lambdas, symbols, Enumerable, mixins, truthiness, method_missing, gems and Bundler.

50 cards
Beginner

Go

Go (Golang) fundamentals with short, idiomatic snippets: slices vs arrays, maps, defer, error wrapping, interfaces, goroutines, channels, select and context.

50 cards
Intermediate

Rust

Learn the Rust that the compiler keeps asking about: ownership, borrowing, lifetimes, Option and Result, traits, closures, smart pointers and async.

50 cards
Intermediate

C++

Modern C++ in short snippets: references vs pointers, RAII, smart pointers, move semantics, STL containers and algorithms, const, templates and UB.

50 cards
Intermediate

Haskell

Haskell fundamentals with short code: types and type classes, pattern matching, laziness, Maybe and Either, folds, Functor, Applicative and Monad.

50 cards
Intermediate

Elixir

Elixir fundamentals with short snippets: immutability, pattern matching, the pipe operator, Enum vs Stream, processes, GenServer, supervisors and mix.

50 cards
Intermediate

x86-64 assembly

x86-64 assembly in Intel syntax: registers and sub-registers, flags, mov and lea, jumps, the stack frame, the System V calling convention and Linux syscalls.

50 cards
Beginner

Regex

Learn regular expressions for good: anchors, classes, greedy vs lazy quantifiers, groups, lookarounds, flags, replacements and the backtracking trap.

50 cards