v2 ships intentional behaviour changes. Most are bug fixes that have a visible BC impact; a handful are deliberate hardening of defaults. Below is the full list, what changes from your side, and how to migrate.
| v1 | v2 | |
|---|---|---|
| PHP | >=7.4 |
^8.0 (tested on 8.0–8.4) |
initphp/parameterbag |
^1.0 |
^2.0 |
Bump your composer.json constraint to ^2.0 and run
composer update initphp/auth.
v1 bug: the constructor stored permissions verbatim while
is()/push()/remove() lower-cased the needle, so a mixed-case
permission supplied at construction time could never match.
// v1
$perm = new Permission(['Editor']);
$perm->is('editor'); // falsev2 fix: the constructor normalizes (lower-case + trim) and
deduplicates, the same way push() does.
// v2
$perm = new Permission(['Editor']);
$perm->is('editor'); // trueAction: if you worked around the bug by lower-casing input before passing it in, that workaround now becomes a no-op (still correct). Nothing to change.
Renamed for plural consistency. The old method survives as a deprecated alias and will be removed in v3.
$perm->getPermission(); // still works in v2, raises @deprecated notice in IDEs
$perm->getPermissions(); // preferredAction: find/replace at your leisure.
Underscore-prefixed properties were a v1 PSR-12 violation. Anything
that touched $_perms directly (subclasses, reflection, serialized
payloads from v1) will need to be updated.
__sleep() now lists permissions, so v1 serialized blobs cannot be
reinflated under v2 — re-serialize them when you next hydrate.
v1: always returned true, which combined with get() always
returning the default produced the inconsistent pair
has(x) === true && get(x) === null.
v2: has() returns false — nothing is ever present in a Null
Object store.
Action: verify that no production code relies on the buggy
has() === true to satisfy a guard. The kind of guard you wrote was
almost certainly meant to fall through, which is exactly what v2 makes
it do.
v1: base64(serialize([data, hash])) with md5(sha1(...)).
v2: base64url(json_encode($data)) . "." . hash_hmac('sha256', $json, $salt).
Why:
- HMAC +
hash_equals()instead of a hand-rolled hash with!=(constant-time comparison, no timing side-channel). - JSON instead of
serialize()(no object instantiation path during decode, no POP-gadget risk). - Hash verified before the payload is parsed.
Action: v2 cannot read v1 cookies — they are silently dropped and
the user is issued a fresh v2 cookie on their next write. Plan for a
quiet logout of everyone holding a v1 cookie. There is no migration
path that would risk running unserialize() against attacker bytes.
v1: minimum 8 characters.
v2: minimum 32 bytes (matches the SHA-256 output length).
// Generate one
echo bin2hex(random_bytes(32));Action: if your existing salt is shorter, generate a longer one and update your environment. (You will need to do this anyway because the wire format changed.)
| Option | v1 default | v2 default |
|---|---|---|
secure |
false |
true |
samesite |
'None' |
'Lax' |
SameSite=None requires Secure=true per the modern cookie spec.
v2 rejects the unsafe combination with InvalidArgumentException
instead of silently emitting a cookie the browser will drop.
Action: if you were running on plain HTTP in development, opt
back into secure=false explicitly and drop SameSite back to
Lax/Strict. Production should run on HTTPS.
v1 bug: the deletion setcookie() call only set expires, so the
browser refused to delete a cookie originally written with a
non-default path.
v2 fix: the deletion reuses $this->options and only overrides
expires. No action required; cookies that previously refused to
delete will now delete.
The constructor gained an optional third argument:
new CookieAdapter(
string $name,
array $options = [],
?CookieWriterInterface $writer = null,
);Default behaviour is unchanged (NativeCookieWriter wraps setcookie()).
Tests can inject InMemoryCookieWriter to capture calls without
touching response headers.
v1: SessionAdapter::__call($name, $args) forwarded to the
internal ParameterBag. Calls like $adapter->merge([...]) mutated
the bag but never synced back to $_SESSION — silent data loss.
v2: the magic is gone. Use the documented get/set/has/remove/collective/destroy
methods, all of which sync $_SESSION after every write.
Action: if you reached into bag methods through __call, switch
to $adapter->collective([...]) or to one of the explicit methods.
The constructor's second argument is now forwarded straight to the
underlying ParameterBag. The biggest practical effect is that you can
opt into dotted-path access:
$auth = new SessionAdapter('auth', ['isMulti' => true]);
$auth->get('profile.name');In v1 the options array was accepted and ignored.
Segment::create() and the constructor still take an int|string
adapter (kept for v1 BC), but new code should use the typed factories:
Segment::session($name, $options);
Segment::cookie($name, $options);
Segment::custom($name, $adapterClass, $options);The error messages on misuse are also more helpful — passing an
unknown integer constant or a class that does not extend
AbstractAdapter now tells you exactly which case it hit.
v1: the interface declared __construct(string $name, array $options = []),
which is a PSR anti-pattern and forced every implementation to take
options through an array even when it wanted to inject a PDO handle
or a Redis client directly.
v2: constructors are out of the contract. Segment::custom() still
invokes new YourClass($name, $options), so the convention if you want
Segment compatibility is unchanged — but you can hand-build adapters
with any constructor signature now.
Action: existing adapters that extend AbstractAdapter keep
working. Adapters that implemented the interface directly without
extending the abstract can drop the __construct declaration if they
want.
v1 redeclared every interface method as abstract in the base class
without adding any shared behaviour. v2 keeps only a default
collective() that iterates set() for adapters that cannot commit
atomically; everything else is satisfied by implementing the interface.
v2 ships with the same dev workflow as the rest of the InitPHP ecosystem:
composer install
composer test # PHPUnit
composer analyse # PHPStan level 8
composer cs:check # PHP-CS-Fixer dry-run
composer cs:fix # PHP-CS-Fixer applyThe test suite covers Permission, SessionAdapter, CookieAdapter,
Segment, and NullAdapter. CI runs on PHP 8.0 through 8.4.