overtrue/laravel-qcloud-federation-token
A Laravel package for generating Tencent Cloud federation tokens and temporary credentials. It wraps QCloud STS so you can securely issue scoped, expiring keys for COS uploads and other cloud services, with simple configuration and familiar Laravel integration.
This release (v5.0.1) is a compatibility hardening release, not a feature release. The practical upshot for day-to-day work: the package now supports PHP 8.3, 8.4 and 8.5 cleanly, so the first thing to do is align your composer.json platform constraint and re-run your suite.
Minimal steps:
composer require <vendor>/<package>:^5.0 (or composer update <vendor>/<package> if already on 5.x).php -v should be 8.3+. If you develop on 8.4/8.5 but deploy to 8.3, set config.platform.php in composer.json so dependency resolution matches production.composer check-runtime-compatibility
composer test
composer check-style
Statement class. It is the object you will touch most, and it is deliberately dual-purpose: it implements ArrayAccess (so a result row behaves like an array) and JsonSerializable (so it can be handed straight to json_encode / a Laravel response).First use case — return a statement as a JSON API response without an intermediate mapping layer:
Route::get('/orders/{id}', function (string $id) {
$statement = Order::query()->where('id', $id)->statement();
abort_if(! $statement, 404);
return response()->json($statement); // hits jsonSerialize()
});
That single line is the shortest path to value; everything else is about keeping it predictable.
Statement as a read model, not a mutable array. Because Statement implements ArrayAccess, it is tempting to write to it ($statement['status'] = 'paid'). Treat it as a read-only projection. If you need mutation, hydrate to an array or DTO first, mutate that, and persist explicitly. This keeps the boundary between "query result" and "domain object" honest.
Serialization at the edge only. Let JsonSerializable do its job in controllers and API resources; never let a Statement leak into a Blade view or a job payload. For a stable API contract, wrap it:
return OrderResource::make($statement)->response();
so column additions or renames in the underlying query do not silently change your public JSON shape.
Single-row vs. multi-row access. Use offset access for keyed lookups on a single row and foreach for sets. Do not mix the two idioms inside the same method — it makes it ambiguous whether a null means "no row" or "column is null".
$row = $repository->find($id); // Statement|null
$total = $row['total'] ?? 0; // single-row offset access
foreach ($repository->all() as $row) { // iteration
$totals[] = $row['total'];
}
Fake it in tests. Because the class is ArrayAccess + JsonSerializable, a small test double is cheap and lets you assert both behaviours:
final class FakeStatement implements ArrayAccess, JsonSerializable
{
public function __construct(private array $data) {}
public function jsonSerialize(): mixed { return $this->data; }
public function offsetExists(mixed $offset): bool { return isset($this->data[$offset]); }
public function offsetGet(mixed $offset): mixed { return $this->data[$offset] ?? null; }
public function offsetSet(mixed $offset, mixed $value): void { $this->data[$offset] = $value; }
public function offsetUnset(mixed $offset): void { unset($this->data[$offset]); }
}
Note the mixed signatures — they match what the package now declares, and matching them is how you avoid the warnings this release fixed.
Add the compat check to your own pipeline. The new composer check-runtime-compatibility script autoloads source classes under E_ALL before PHPUnit runs. That is a pattern worth copying into application code: PHPUnit does not fail on E_DEPRECATED, so signature-level rot accumulates silently until a minor PHP bump breaks you in production.
Upgrade workflow. For any 5.x patch bump: composer update → composer check-runtime-compatibility → composer test. If you subclass or decorate Statement, do a grep for extends Statement and inspect every hit before merging — that is where signature drift bites.
offsetExists and null columns. ArrayAccess is conventionally implemented with isset(), so isset($statement['cancelled_at']) returns false when the column exists but is NULL. Use $statement['cancelled_at'] ?? null for null-safe reads, or ->toArray() if you need true key-presence semantics. This distinction silently breaks "has this been set?" checks in real code.
Subclass signatures are now effectively enforced. If you extend Statement, every overridden method must carry the exact mixed parameter and return types: jsonSerialize(): mixed, offsetExists(mixed $offset): bool, offsetGet(mixed $offset): mixed, offsetSet(mixed $offset, mixed $value): void, offsetUnset(mixed $offset): void. Omitting mixed on a parameter is not the same as declaring it — PHP emits a deprecation for implicitly nullable/inconsistent signatures, and that is precisely what this release cleaned up. Expect a future major to make these errors rather than warnings.
Warnings are invisible under Laravel's default error handling. Laravel converts many notices into ErrorException, but deprecations raised during class autoload (before your handler is registered) often go nowhere. That is why the maintainers added an explicit pre-PHPUnit check. When debugging a signature warning, reproduce with:
php -d error_reporting=E_ALL -d display_errors=1 artisan tinker
Duplicate column names in joins. Offset access is keyed, so SELECT orders.*, users.name collapses name to whichever column lands last. Always alias (users.name as user_name) when you plan to use offset access rather than iteration with positional rows.
Don't iterate and index the same cursor. On a lazily-evaluated statement, $stmt[0] may buffer or rewind the underlying cursor, so a subsequent foreach can yield surprising results. Pick one access style per statement instance.
Serialization can over-share. jsonSerialize() typically returns the full row. If the query selects password_hash, api_token or internal flags, they land in your JSON. Select explicitly rather than SELECT *, or gate with an API resource — the package is not responsible for redacting.
Pin the platform when testing across versions. If CI runs 8.5 but production is 8.3, config.platform.php in composer.json keeps the resolved dependency graph honest. Run the compat check under both the lowest and highest supported PHP to catch both deprecation and missing-feature failures.
Extension points to prefer. Compose rather than inherit: a decorator that wraps Statement and implements the same two interfaces gives you logging, redaction or type-casting without coupling to internal method signatures that the maintainers just proved they will adjust.
How can I help you explore Laravel packages today?