Pop PHP
Introduction

What's New in v7

Pop PHP Framework v7.0.0 adds 278 features across the bundled components and one new package. This page is the guided version: what changed at the framework level, the additions worth reorganizing code around, and the patterns that recur often enough to name. The exhaustive list is NEW-FEATURES.md.

If you have a v6 application to move, read Upgrading from v6 alongside this. The two documents cover the same release from opposite directions.

Framework-Level Changes#

These are the changes that apply to the whole framework, and to any project that installs it.

There Is a New Installer Package#

popphp/framework is now the official way to start a project. In v6 you required popphp/popphp-framework directly and inherited its 30-plus requirements into your own composer.json; in v7 the skeleton depends on the metapackage for you, so your project file carries one line:

BASH
composer create-project popphp/framework my-app

The skeleton also brings the kettle script to the project root, and hooks into Composer to run pop:init before the installer finishes. Installation walks through both paths, including the core-only one.

There Is a New Component#

pop-parser parses free-form personal names and US or Canadian street addresses into their component parts, with no third-party parsing dependency. It's bundled with the full framework. With popcorn and pop-ftp dropped against that one addition, the v7 metapackage is one package smaller than v6's — see All Components.

Every Component Requires PHP 8.4 and Declares strict_types=1#

There is no partial upgrade path and no component that still runs on 8.3. Strict mode is decided by the calling file, so your own loosely-typed code is not retyped by this — but a loose value handed to a component's strict internals is now a TypeError where v6 coerced it. Every component also gained PHPStan coverage alongside its existing PHPUnit coverage.

Every component takes a major version bump as well. No breaking change ships behind a minor number, so a caret constraint on a v6 version refuses the v7 release rather than taking it quietly. That's deliberate: an accidental upgrade is the one failure mode a release this size cannot afford.

Two packages left the framework. popphp/popcorn is gone because the core absorbed it, and popphp/pop-ftp is gone with no replacement — the guidance is to move that traffic to HTTPS or SSH. Three pop-auth adapters went with them: Table moved to pop-db, Http was replaced by calling pop-http directly, and Ldap was dropped. pop-form and pop-image both dropped their Captcha classes. Upgrading from v6 covers what each removal means for existing code.

The Most Significant Additions#

278 features is more than anyone reads in order. These are the ones that change how you would write an application rather than adding to what it can do — the places where the v7 answer to a problem is a different answer, not a longer one.

The Core Absorbed Popcorn#

HTTP-verb routing was a separate package in v6 — popphp/popcorn — and in v7 it is Pop\Router\Match\Http itself. Method-grouped route configs, a fluent verb API on Application, Router and the matcher, custom verbs registered at runtime, and a real 405 with an Allow header when a path matches but the method does not:

PHPpublic/index.php
<?php

$autoloader = include __DIR__ . '/../vendor/autoload.php';

$app = new Pop\Application($autoloader);

$app->get('/orders[/:id]', function($id = null) {
    echo ($id === null) ? 'All orders' : 'Order ' . $id;
})->post('/orders', function() {
    echo 'Created';
});

$app->run();

A GET /orders/7 answers from the first route, a POST /orders from the second, and a PUT /orders gets a 405 Method Not Allowed carrying Allow: POST, GET — not the 404 v6 would have produced.

Alongside it the core grew PSR layers on three of its managers: PSR-11 on the service locator, PSR-14 on the event manager, and a PSR-15 bridge on middleware. Pop\Dispatch decoupled dispatchability from controllers, so a plain class, an invokable object or a CallableObject is a route target without extending anything. And a new app.shutdown event fires from a finally inside run(), giving you teardown that runs whether the request completed, aborted or threw. Routing and Error Handling cover the routing half; Services, Events and Middleware cover the rest.

pop-http Became a PSR-7/17/18 Client and Server#

Immutable messages, six factories, a PSR-18 sendRequest(), and — the part that changes how you write tests — a Mock handler that answers from a canned script with no network involved. On top of that sits a composable client middleware pipeline with retry-and-backoff and PSR-3 request logging, a native streaming Body, and superglobal-free Server\Request construction, which is what makes a server-side request testable at all. See HTTP Client and Requests & Responses.

pop-db's Shorthand Syntax Became a Query Language#

In v6 the shorthand was a convenience over simple equality. In v7 it takes structured [OPERATOR, ...] tuples, OR and AND groups, IN and EXISTS subqueries and JSON column paths, so a non-trivial WHERE no longer forces you down to the builder. Records gained lifecycle hooks and mass-assignment protection, relationships gained composite foreign keys and multi-path eager loading, and pop-auth's table adapter moved here as Record\Auth with lockout and MFA. See Auth Records and Query Builder.

pop-queue Became Crash-Safe#

The adapter contract was rewritten around leases: a worker reserves a job, then releases, deletes or buries it. A worker that dies mid-job no longer loses it, and a job that keeps failing lands in a dead-letter store. Add a worker registry with heartbeats, per-job delay and retry backoff, HMAC-signed payloads, daemon mode with signal handling, and a queue:* command family to drive it. See Queues & Scheduled Tasks.

pop-pdf Gained a Native PDF Reader#

Pop\Pdf\Extract is a full reading engine — cross-reference and object streams, every standard filter, CMap and CID font decoding, and a repair pass for damaged files — replacing the third-party parser v6 leaned on. Built on it: document merging, image-only page detection for routing scans to OCR, and HTML rendering that now does real <table> layout with colspan, rowspan and repeating headers. CID font output means Cyrillic, Greek and Arabic text renders from an embedded font. See PDFs.

pop-cache, pop-log and pop-code Caught up with Their Ecosystems#

pop-cache added PSR-6 and PSR-16 alongside its native API, plus remember() with probabilistic early recomputation, tag-based invalidation, atomic counters and an injectable clock. pop-log became a genuine PSR-3 logger with string levels, placeholder interpolation, pluggable formatters and JSON Lines output. pop-code learned the PHP shipped since v6 — attributes, enums, readonly, constructor promotion, typed constants, intersection types — in both directions. See Cache, Logging and Code Generation.

Kettle Became More than Just a CLI Helper#

pop-kettle gained 16 features, more than any component except popphp and pop-http, and now houses the skeleton install through pop:init — which deprecates the old popphp/popphp-skeleton and popphp/popphp-tutorial repos. pop:init is a guided interview rather than a wall of flags, and it registers your namespace in composer.json instead of writing v6's kettle.inc.php. It offers front-end scaffolding — AlpineJS, Vue or React, each with Tailwind through Vite — driven afterward by the web: commands. Commands can go to Kettle or to your own application namespace, and the built-in commands moved to pop:* so app:* is yours. See Kettle.

pop-console Grew the Output Vocabulary a Real CLI Needs#

Table rendering, progress bars, a multi-select prompt, a non-exiting confirm(), and help screens that can be narrowed to one command namespace. Underneath, commands became dispatchable objects with their own help text, which is the mechanism Kettle's auto-discovery uses, and an injectable input stream makes a prompt testable without a terminal. See Building Console Applications.

Mail Message Assembly Moved into pop-mime#

pop-mail's native message and part classes were replaced by refactored ones in pop-mime, which gained part factories, RFC 5322 address parsing, RFC 2047 encoded words with no ext-imap, correct header folding and lazy streamed attachment bodies. What pop-mail kept is the sending: batch-capable transports, a pre-rendered body reused across repeated sends, injectable HTTP handlers on the API clients, and O(1) queue de-duplication. See Mail and MIME.

pop-config Reads and Writes Dot Paths#

$config['database.host'] traverses nested arrays in both directions, isset() understands it, YAML moved to symfony/yaml so it works without ext-yaml, and parse failures raise typed exceptions instead of returning an empty array. See Config Objects.

Two more worth knowing about: pop-view can compile stream templates to plain PHP, turning every render after the first into an include, and its multi-block layout inheritance works, where v6 resolved only a block named header (Views & Templates). And pop-color added seven color spaces — HSV, HSB, HWB, Lab, Lch, Oklab and Oklch — with colorimetrically correct conversions, CSS Color 4 parsing and rendering, and hex with alpha (CSS & Color).

Smaller components got smaller versions of the same treatment. pop-acl gained wildcard permissions, role and resource removal, and introspection (Authorization). pop-storage gained streaming I/O, presigned URLs, and full pagination on S3 and Azure listings (File Storage). pop-validator added 19 more Has* validators for asserting things about a whole array (Forms & Validation). pop-csv got a streaming row reader and formula-injection escaping (CSV), and pop-dir collapsed to a single filesystem walk (File Storage).

Themes#

Five patterns run through the release. Recognizing them is the fastest way to guess what a component you have not read about yet probably gained.

PSR Interoperability#

Pop v7 drops into a wider PHP ecosystem, with the PSR surface sitting alongside the native API rather than replacing it: PSR-3 in pop-log and pop-debug, PSR-6 and PSR-16 in pop-cache, PSR-7/17/18 in pop-http, PSR-11 on the service locator, PSR-14 on events and PSR-15 on middleware. A library expecting a LoggerInterface, CacheInterface or ClientInterface takes a Pop object without an adapter. The one displacement is pop-log's levels, now strings to match PSR-3.

Testability Seams#

Every component that talks to the outside world grew a way to substitute that world: Client\Handler\Mock in pop-http, the Memory adapter and Queue::fake() in pop-queue, Clock\MutableClock in pop-cache, setInputStream() in pop-console, injectable HTTP handlers on pop-storage and pop-mail, and superglobal-free request construction. A test suite that can run a queue, a cache and an HTTP call without any of them existing is the largest practical difference from v6.

Security Hardening#

XChaCha20-Poly1305 and HKDF key separation in pop-crypt; per-field CSRF tokens compared in constant time in pop-form; needsRehash() and hash_equals() in pop-auth; automatic redaction in pop-debug; path-traversal rejection in pop-storage and pop-view; HMAC-signed job payloads in pop-queue; formula-injection escaping in pop-csv; secure-by-default cookie flags in pop-session; and output escaping in pop-dom, pop-nav, pop-paginator and pop-i18n. The escaping additions are the ones to watch on an upgrade — see Upgrading from v6.

Streaming and Memory#

Large payloads stopped being buffered whole. Stream bodies and zero-copy multipart in pop-http, putFileStream() and fetchFileStream() in pop-storage, a generator row reader in pop-csv, lazy attachments through php://filter in pop-mime, one rendered body reused across a BCC loop in pop-mail, compiled templates in pop-view, and a single filesystem walk in pop-dir where v6 made two or three.

Operational Visibility#

The framework got easier to run rather than easier to write: worker registries and dead-letter queues in pop-queue, syslog, stdout and NDJSON output in pop-log, NDJSON debug storage in pop-debug, and the queue:* commands in Kettle. Each is a line of configuration, so an application that doesn't want them pays nothing.

See Also#

  • Upgrading from v6 — the same release from the breaking-change side
  • Changelog — the release history, including everything before v7
  • All Components — every package, with its guide and its README