All lessons
LanguagesBeginner11 min readIn my stack

Modern PHP

PHP 8.x is typed, fast and expressive: enums, readonly, match, attributes and Composer.

  • PHP 8.x
  • Composer
  • PSR
  • Enums & readonly
Step 1/5
A request arrives: POST /orders. The router reads the URL and method and picks the controller action that will handle it.

In 30 seconds

PHP is a server-side language built for the web: a request comes in, a PHP script builds the response, and everything is cleaned up afterwards. Modern PHP (8.x) is very different from its reputation: it has strict types, enums, immutable objects and a just-in-time compiler. With Composer for packages and PSR standards for shared conventions, it powers frameworks like Laravel and Symfony and a large share of the web.

Why I use it

  • I have built most of my backends in PHP with Laravel, from my first production system in 2020 to today's ordering, e-commerce and CRM platforms, because it lets a small team ship a large, reliable API quickly.
  • I like PHP's share-nothing request model: each request starts clean, so one bad request cannot leak memory or state into the next.
  • Modern features such as enums, readonly classes and typed properties let me write domain code that is as explicit as TypeScript.
  • Hosting is easy and cheap: nginx plus PHP-FPM runs on any Linux server, which suits the projects my clients actually have.

How it works

01How PHP runs your code: PHP-FPM and the request lifecycle

Most PHP in production runs under PHP-FPM (FastCGI Process Manager). Think of it as a team of workers waiting for jobs. nginx receives the HTTP request, serves static files itself, and forwards PHP requests over FastCGI to FPM. A master process manages a pool of worker processes; one free worker takes the request, runs your entry script (for Laravel, public/index.php), sends the response back and then throws away all of the request's objects and variables. This is the share-nothing model: nothing survives between requests except what you store outside the process (database, Redis, files). OPcache keeps the compiled bytecode of your files in shared memory, so PHP does not re-parse source code on every request. The pool size (pm.max_children) limits how many requests run at once, so size it to your RAM. Long-running runtimes such as FrankenPHP worker mode, RoadRunner or Swoole (Laravel Octane) keep the app booted in memory for extra speed, but then you must avoid leaking state between requests.

ini
; /etc/php/8.4/fpm/pool.d/app.conf  (example)
[app]
user = app_user
group = app_user
listen = /run/php/php8.4-fpm-app.sock
pm = dynamic
pm.max_children = 20      ; roughly free RAM / average worker size
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500     ; recycle workers to contain slow leaks
 
; php.ini (production)
; opcache.enable=1
; opcache.validate_timestamps=0  ; reload FPM after each deploy

02Types and the PHP 8 language features you should use

Start every file with declare(strict_types=1); so PHP refuses to silently convert "5" into 5 when a function expects an int. Type everything: parameters, return values and properties, including union types (int|string), nullable types (?string) and never for functions that always throw. PHP 8.0 brought match (a strict, expression-based switch that throws if nothing matches), named arguments, the nullsafe operator ?-> and constructor property promotion. PHP 8.1 added enums, readonly properties and first-class callables like strlen(...). PHP 8.2 added readonly classes, 8.3 typed class constants and the #[\Override] attribute, 8.4 property hooks and asymmetric visibility (public private(set)), and 8.5 (November 2025) the pipe operator |>, clone-with and #[\NoDiscard]. PHP 8.6 is scheduled for late November 2026 with partial function application.

php
<?php
declare(strict_types=1);
 
final readonly class Money
{
    public function __construct(
        public int $amount,     // minor units, e.g. cents
        public string $currency,
    ) {}
 
    public function add(Money $other): self
    {
        if ($other->currency !== $this->currency) {
            throw new InvalidArgumentException('Currency mismatch');
        }
        return new self($this->amount + $other->amount, $this->currency);
    }
}

03Enums, match and attributes

Enums replace magic strings and class constants with a closed set of values. A backed enum (enum OrderStatus: string) maps each case to a value you can store in the database, and you get OrderStatus::from('paid') to convert back, which throws on invalid input, or tryFrom which returns null. Enums can have methods and implement interfaces, so the rules about a status live next to the status itself. match pairs naturally with enums: it compares strictly, returns a value and throws UnhandledMatchError if a case is missing, so an unhandled new status fails loudly instead of silently. Attributes (#[...]) are structured metadata attached to classes, methods or properties, read through reflection. Frameworks use them for routes, validation, event listeners and dependency injection, replacing fragile docblock annotations.

php
<?php
enum OrderStatus: string
{
    case Pending = 'pending';
    case Paid = 'paid';
    case Cancelled = 'cancelled';
 
    public function label(): string
    {
        return match ($this) {
            self::Pending => 'Waiting for payment',
            self::Paid => 'Paid',
            self::Cancelled => 'Cancelled',
        };
    }
}
 
$status = OrderStatus::from($row['status']); // throws on invalid value
echo $status->label();

04Composer and autoloading

Composer is PHP's package manager and autoloader. composer.json lists your dependencies with version constraints (^12.0 means 'any 12.x'), and composer.lock records the exact versions installed. Commit both files, so every developer and every server installs the same code. Autoloading means you never write require for your own classes: following PSR-4, the namespace App\Billing\Invoice maps to the file src/Billing/Invoice.php, and Composer loads it the first time it is used. In production, install with --no-dev to skip testing tools and --optimize-autoloader (or --classmap-authoritative) to build a fast class map. Run composer audit regularly, or in CI, to check installed packages against known security advisories, and update with composer update package/name rather than updating everything at once.

json
{
  "require": {
    "php": "^8.3",
    "laravel/framework": "^13.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^12.0"
  },
  "autoload": {
    "psr-4": { "App\\": "src/" }
  },
  "config": { "sort-packages": true }
}
 
// Production install:
// composer install --no-dev --optimize-autoloader --no-interaction

05PSR standards and code quality tools

PSRs are PHP Standards Recommendations from PHP-FIG, a group of framework authors. They let libraries from different authors fit together. The ones you meet most often: PSR-4 (autoloading), PSR-3 (logger interface), PSR-7 and PSR-17 (HTTP request and response objects), PSR-15 (middleware), PSR-11 (dependency-injection containers), PSR-14 (events) and PSR-18 (HTTP client). For formatting, PSR-12 has been superseded by the PER Coding Style, which tools apply automatically. A modern PHP project usually adds static analysis with PHPStan or Psalm, run at a high level in CI, because they catch type errors that PHP itself only reports at runtime. Add an automatic formatter (PHP-CS-Fixer or Laravel Pint) and Rector for automated upgrades between PHP and framework versions. With these, PHP feels close to a compiled language in day-to-day safety.

bash
# Install quality tools as dev dependencies
composer require --dev phpstan/phpstan laravel/pint rector/rector
 
# Check types (raise the level over time)
vendor/bin/phpstan analyse src --level=8
 
# Format code to the project style
vendor/bin/pint
 
# Preview automated upgrades (e.g. to new PHP syntax)
vendor/bin/rector process src --dry-run
 
# Check dependencies for known vulnerabilities
composer audit

06Production tips

Keep PHP on a supported version: each release gets two years of active support and two more of security fixes, so check php.net/supported-versions and plan upgrades instead of waiting for an emergency. Turn display_errors off in production and log errors instead; showing stack traces to users leaks paths and secrets. Enable OPcache, and because validate_timestamps=0 means PHP will not notice new files, reload PHP-FPM as part of every deploy. Run one FPM pool per site under its own system user, so a compromised site cannot read another's files. Never build SQL with string concatenation; use prepared statements or your ORM's query builder. Hash passwords with password_hash() (bcrypt or Argon2id), never md5 or sha1. Move slow work such as emails, PDFs and third-party API calls to queue workers, so web requests stay fast.

Use it when

  • Web applications and REST APIs, especially with Laravel or Symfony, where productivity and a huge ecosystem matter.
  • Content sites, e-commerce, CRMs and admin-heavy business systems.
  • Projects that need cheap, simple hosting on standard Linux servers or shared and managed hosting.
  • Teams that want a mature, batteries-included framework with queues, scheduling, auth and an ORM out of the box.

Skip it when

  • Long-lived connections at very high scale (thousands of WebSockets per server); Node.js, Go or Elixir fit better, or a hosted service such as Pusher alongside PHP.
  • CPU-heavy work such as video processing or machine learning, where Python, Go or Rust have better libraries and performance.
  • A team that already knows another backend stack well; the best language is often the one the team can maintain.

Alternatives

  • Node.js (NestJS, Express, Fastify)

    One language across frontend and backend, strong for realtime and I/O-heavy services.

  • Python (Django, FastAPI)

    Excellent for data, AI and scripting-heavy backends; Django is as batteries-included as Laravel.

  • Go

    Compiled, very fast and simple to deploy as a single binary; more code to write for typical CRUD apps.

  • Ruby on Rails

    Similar convention-over-configuration productivity to Laravel, with a smaller hiring pool in many regions.

Best practices

  1. Start files with declare(strict_types=1) and type every parameter, return value and property.
  2. Use backed enums for statuses and types, and match for mapping them, instead of magic strings.
  3. Make value objects readonly (Money, Email, Address) so they cannot be changed by accident.
  4. Commit composer.lock, install with --no-dev --optimize-autoloader in production and run composer audit in CI.
  5. Run PHPStan or Psalm at a high level and an automatic formatter on every pull request.
  6. Stay on a supported PHP version and upgrade one minor version at a time with Rector's help.
  7. Size PHP-FPM pools to available memory and run each site under its own system user.

Common mistakes

  • Leaving display_errors on in production and exposing stack traces, file paths or credentials.
  • Concatenating user input into SQL instead of using prepared statements.
  • Forgetting to reload PHP-FPM after a deploy when OPcache timestamp validation is off, then wondering why old code still runs.
  • Setting pm.max_children far higher than RAM allows, so the server swaps and slows down under load.
  • Storing money as floats instead of integers in minor units or a decimal type.

Deep dive

07OPcache, preloading and the JIT under the hood

PHP compiles each source file into opcodes, the instructions its virtual machine executes. OPcache stores those opcodes, plus interned strings, in shared memory used by every FPM worker, so a file is compiled once rather than on every request. If the cache fills up, PHP starts recompiling and performance silently drops, so size opcache.memory_consumption (default 128 MB) and opcache.max_accelerated_files (default 10000) to your codebase and watch opcache_get_status() for hit rate and wasted memory. Preloading (opcache.preload, PHP 7.4+, not on Windows) runs a script at server start that loads chosen classes into memory permanently, saving the per-request linking cost; any change to those files needs a full FPM restart. The JIT compiles hot opcodes to machine code. Since PHP 8.4 it is configured with opcache.jit (default disable) and opcache.jit_buffer_size (default 64M), and its memory adds to OPcache's. For typical I/O-bound web apps the JIT gains little, because time is spent waiting on the database; it shines for CPU-heavy work like image maths or parsers.

ini
; php.ini - production OPcache tuning (example values)
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0      ; reload FPM on deploy
; opcache.preload=/var/www/app/preload.php
; opcache.preload_user=app_user
 
; Only for CPU-heavy workloads; measure before and after
; opcache.jit=tracing
; opcache.jit_buffer_size=64M
 
realpath_cache_size=4096K
realpath_cache_ttl=600

08Long-running workers: Octane, FrankenPHP and state leaks

Worker-mode runtimes boot the framework once and then serve thousands of requests from the same process. That removes bootstrapping cost and can multiply throughput, but it breaks the share-nothing guarantee you relied on before. Anything stored in a static property, a singleton bound in the container, or a global array now survives into the next request, possibly for a different user. Typical bugs: a service that caches the current tenant or user in a property, a static array that grows forever and leaks memory, a configuration value changed for one request and never reset. Rules for worker mode: resolve request-specific data from the request each time instead of storing it, register request-scoped services as scoped bindings that are flushed between requests, avoid static mutable state, and let the runtime recycle workers after a fixed number of requests (Octane's --max-requests) as a safety net. Load-test with two users in parallel and check that neither sees the other's data.

php
<?php
// Dangerous in worker mode: state survives between requests
final class CurrentTenant
{
    private static ?int $id = null;          // leaks to the next request
    public static function set(int $id): void { self::$id = $id; }
}
 
// Safer: derive it from the request every time
final class TenantResolver
{
    public function __construct(private readonly Request $request) {}
 
    public function id(): int
    {
        return (int) $this->request->attributes->get('tenant_id');
    }
}
// Laravel: $this->app->scoped(TenantResolver::class); // reset per request

09Security internals: sessions, hashing and unserialize

Several PHP security issues come from defaults and legacy functions rather than your own logic. Sessions: set session.cookie_secure, session.cookie_httponly and session.cookie_samesite=Lax, enable session.use_strict_mode so PHP rejects session ids it did not create, and regenerate the id with session_regenerate_id(true) after login to prevent session fixation. Passwords: password_hash with PASSWORD_DEFAULT (currently bcrypt) or PASSWORD_ARGON2ID, verified with password_verify, which compares in constant time; call password_needs_rehash after a successful login to upgrade old hashes transparently. Never pass untrusted data to unserialize(): crafted input can instantiate arbitrary classes and trigger their magic methods, a technique called object injection that can lead to remote code execution. Use json_decode instead, or at least unserialize with allowed_classes set to false. Compare secrets and HMAC signatures with hash_equals, not ===, to avoid timing leaks. Finally, keep disable_functions and open_basedir as defence in depth, not as the main protection.

php
<?php
// Login: verify, then upgrade the hash if the algorithm or cost changed
if (password_verify($password, $user->password_hash)) {
    if (password_needs_rehash($user->password_hash, PASSWORD_ARGON2ID)) {
        $user->password_hash = password_hash($password, PASSWORD_ARGON2ID);
        $user->save();
    }
    session_regenerate_id(true); // prevent session fixation
}
 
// Webhook signature: constant-time comparison
$expected = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signatureHeader)) {
    http_response_code(401);
    exit;
}
 
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR); // not unserialize()

10Concurrency and profiling: Fibers, FPM status and slow logs

A single PHP request runs on one thread, but there are tools for doing more at once. Fibers (PHP 8.1) are lightweight, cooperatively scheduled code blocks that can pause and resume; you rarely use them directly, but libraries such as Revolt, AMPHP and ReactPHP build event loops on them so one process can wait on many network calls concurrently. For classic web apps, concurrency usually means parallel HTTP calls (Guzzle promises or Laravel's Http::pool) and queue workers for background jobs. When a site is slow, look before guessing. The FPM status page (pm.status_path) shows active, idle and queued processes; a growing listen queue means the pool is too small or requests are too slow. request_slowlog_timeout writes a stack trace of any request that runs longer than the limit, which points straight at the blocking call. For deeper profiling, use a sampling profiler such as SPX, Blackfire or Tideways, or Xdebug's profiler in development only, since it slows PHP down heavily.

ini
; FPM pool: observability
pm.status_path = /fpm-status          ; restrict to localhost in nginx
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/app-slow.log
request_terminate_timeout = 60s       ; kill runaway requests
 
# Read the status page from the server itself
curl -s "http://127.0.0.1/fpm-status?full" | head -20
# Look at: listen queue, max listen queue, max children reached

Real-world scenarios

01

Old code is still served after a deploy

Problem

A fix was deployed and the files on disk are correct, but users still see the old behaviour. Clearing the Laravel cache did not help, and the problem disappears only after someone restarts the server.

How to solve it

This is OPcache with opcache.validate_timestamps=0: PHP never checks whether files changed, so it keeps serving the old compiled opcodes. Make an FPM reload part of the deploy script, after the new release is in place: systemctl reload php8.4-fpm, which gracefully replaces workers without dropping requests. With symlink-based releases, also make sure nginx passes $realpath_root as SCRIPT_FILENAME and DOCUMENT_ROOT, otherwise the realpath cache can keep resolving the old release directory. Add a version endpoint or response header with the git commit, and check it in the deploy pipeline so a stale release is detected automatically instead of by users.

02

The server swaps and requests time out at peak hours

Problem

At lunchtime the site becomes slow, the server starts swapping, and nginx logs 502 and 504 errors. The FPM pool was configured with pm.max_children = 100 'to be safe' on a 4 GB machine.

How to solve it

Measure the real worker size: ps -o rss -C php-fpm8.4 shows memory per worker; suppose the average is 60 MB. With about 2.5 GB left after the database, Redis and the OS, the safe maximum is around 40 workers, not 100. Lower pm.max_children accordingly, set pm.max_requests to recycle leaky workers, and enable pm.status_path to watch the listen queue. Then turn on the slow log to find the requests that hold workers longest, often external API calls or report exports, and move them to queues. If the queue still grows after that, the answer is more CPU or another app server, not more workers.

03

Upgrading a large app from PHP 8.1 to 8.4

Problem

PHP 8.1 is out of security support, and the codebase has hundreds of thousands of lines, some old packages and few tests in older modules. The business cannot afford a broken checkout.

How to solve it

Upgrade one minor version at a time. First run composer why-not php 8.4 to find packages that block the upgrade and update them. Add the target version to CI as a second matrix entry so tests run on both versions. Run Rector with the PHP 8.2, 8.3 and 8.4 rule sets in dry-run mode, review the changes and apply them in small pull requests. Run PHPStan at your current level on the new version and treat deprecation notices in logs as errors in staging. Deploy to one server or a canary first, watch error rates, then roll out fully and remove the old version from CI.

Cheat sheet

Show PHP version and loaded ini files
php -v && php --ini
Check OPcache status from CLI config
php -i | grep -E 'opcache.(enable|jit|memory)'
Test FPM config, then reload gracefully
sudo php-fpm8.4 -t && sudo systemctl reload php8.4-fpm
Measure FPM worker memory
ps -o rss=,cmd= -C php-fpm8.4 | sort -n | tail
Why can't I upgrade?
composer why-not php 8.4
Find outdated direct dependencies
composer outdated --direct
Production install
composer install --no-dev --classmap-authoritative --no-interaction
Security check of dependencies
composer audit
Safe decode of external data
json_decode($body, true, 512, JSON_THROW_ON_ERROR)
Constant-time secret comparison
hash_equals($expected, $given)

In my real projects

  • I have shipped Laravel APIs on everything from PHP 7.3 and Laravel 8 (my first production portal in 2020) to PHP 8.3 and Laravel 13 on a gym-chain CRM.
  • The pet-supplies platform uses around 50 PHP enums and about 110 service classes, with prices modelled as value objects and 95 backend test files.
  • When migrating a WordPress shop to Laravel, I kept compatibility with the old password hashes, so existing customers could log in without a forced reset.
  • On the servers I manage, PHP runs under PHP-FPM behind nginx, with queue workers under supervisor and the scheduler on cron.

Questions & answers

Q1How does PHP-FPM handle a request, and why does PHP 'forget everything' afterwards?

nginx accepts the connection and forwards PHP requests over FastCGI to PHP-FPM. FPM's master process keeps a pool of workers; a free worker runs the entry script, builds the response and returns it. When the request ends, PHP frees every object and variable it created. This share-nothing model is a feature: memory leaks and stale state cannot accumulate, and a crash affects only one request. The cost is bootstrapping the framework on every request, which OPcache reduces by caching compiled bytecode. Persistent state lives outside PHP, in the database, Redis or the session store. Worker-mode runtimes like Octane or FrankenPHP trade that safety for speed.

Q2What changed in PHP 8 that makes it a 'modern' language?

Several things together. Types became genuinely useful: union, nullable, mixed, never, typed properties and class constants, enforced with strict_types. The language gained expressive features: match expressions, named arguments, constructor promotion, the nullsafe operator, enums in 8.1, readonly properties and classes, property hooks and asymmetric visibility in 8.4, and the pipe operator in 8.5. Attributes replaced docblock annotations for metadata. Performance improved with the JIT compiler and continuous engine work. Around the language, Composer, PSR standards, PHPStan and Rector give a tooling story comparable to TypeScript. The PHP of 2010 tutorials and the PHP of today are very different languages to work in.

Q3What is the difference between match and switch?

match is an expression, so it returns a value you can assign directly. It compares with strict identity (===), so '1' does not match 1, which avoids a whole class of switch bugs. Each arm is a single expression with no fall-through, so you cannot forget a break. Most importantly, if no arm matches and there is no default, match throws UnhandledMatchError instead of silently doing nothing. Combined with enums, that means adding a new case and forgetting to handle it fails loudly in tests. I still use switch rarely, for multi-statement branches with side effects, but match is my default for mapping values.

Q4Why commit composer.lock, and what is the difference between install and update?

composer.json declares acceptable version ranges; composer.lock records the exact versions, and their hashes, that were resolved. composer install reads the lock file and installs exactly those versions, so every developer, CI run and server gets identical code. composer update ignores the lock, resolves the newest versions allowed by the ranges and rewrites the lock file. So you run update deliberately, ideally for one package at a time, review the changes and commit the new lock; deploys and CI only ever run install. Without a committed lock file, two servers deployed a week apart can run different library versions.

Q5How do you keep a PHP codebase safe from type errors without a compiler?

Layered checks. First, declare(strict_types=1) and full type declarations make PHP throw TypeErrors instead of silently coercing values. Second, static analysis with PHPStan or Psalm, run in CI at a high level, reads the whole codebase and finds wrong types, undefined methods and impossible conditions before deployment. Generics in docblocks (array<int, Order>, Collection<User>) give the analyser even more information. Third, enums and readonly value objects make invalid states harder to express. Finally, tests cover the behaviour that types cannot describe. Together this gets very close to the guarantees of a compiled language.

Senior-level questions

S1When would you move a PHP application to a worker-mode runtime such as Octane or FrankenPHP, and what are the risks?

I consider it when profiling shows that framework bootstrapping, not the database or external APIs, is a large share of response time, and when traffic is high enough that the extra throughput matters, such as a busy API with short requests. Gains of several times are realistic in that case. The risks are about state: static properties, singletons holding the current user or tenant, and caches in memory now survive between requests, which can leak data between customers or grow memory until the process dies. Some packages are not written for it. So I audit the container bindings, mark request-specific services as scoped, add a max-requests limit, and run parallel load tests with different users before switching. If requests are dominated by slow SQL, fixing queries gives more benefit for far less risk.

S2How do you size and tune PHP-FPM for a server, and which pm mode do you choose?

I start from memory, not from guesses. Measure the average resident memory of a worker under real traffic, subtract what the OS, database, Redis and other services need, and divide: that is the ceiling for pm.max_children. For a dedicated app server with steady traffic, pm = static avoids fork costs and gives predictable memory. For a server hosting several sites, pm = dynamic or ondemand saves memory for quiet pools at the cost of spawning workers under bursts. I set pm.max_requests so slow leaks are contained, enable pm.status_path to watch the listen queue and max children reached, and use the slow log to find requests that hold workers too long. If workers are mostly waiting on I/O, the fix is usually queues or caching, not a bigger pool.

S3How would you design a PHP codebase so it can survive many PHP and framework upgrades?

By keeping the code that changes for business reasons separate from the code that changes for technical reasons. Domain logic lives in plain, typed PHP classes, value objects and enums, with as few framework calls as possible, so a framework upgrade touches mostly controllers, configuration and infrastructure adapters. Dependencies are few and chosen for maintenance history, because an abandoned package is often the real upgrade blocker. Static analysis at a high level and automated tests around critical flows make large automated changes safe, and Rector applies most syntax migrations mechanically. CI runs against the next PHP version early, so deprecations show up months before they become errors. The trade-off is slightly more structure up front, which pays back the first time a major version arrives.

Glossary

Opcode
A low-level instruction for the PHP virtual machine, produced by compiling a source file.
OPcache
A shared-memory cache of compiled opcodes, so PHP does not recompile files on every request.
Preloading
Loading selected classes into memory once at server start so every request can use them immediately.
JIT
Just-in-time compiler that turns frequently executed opcodes into native machine code; helps CPU-bound work most.
Share-nothing
Each request starts with fresh memory, and nothing except external storage carries over to the next.
Worker mode
Running a booted application in a long-lived process that serves many requests, as Octane and FrankenPHP do.
Fiber
A pausable, resumable block of code (PHP 8.1) used by async libraries to build event loops.
Object injection
An attack where crafted serialized input makes unserialize create objects whose magic methods run harmful code.
Session fixation
An attacker sets a victim's session id before login; regenerating the id after login prevents it.
Listen queue
Requests waiting for a free FPM worker; a growing queue means the pool is too small or too slow.

Have a product in mind?Let's build it.

Tell me about your idea, your product or your team. I usually reply within one working day.