vistik/typed-collections
A Laravel/PHP package for type-safe collections: enforce element types at runtime, improve IDE and static-analysis support, and keep familiar Collection ergonomics. Define typed collections for models, DTOs, scalars, and custom classes.
composer require vistik/typed-collections. Note the package is archived and last tagged in 2017, so it targets Laravel 5.x-era Illuminate\Support\Collection and older PHP versions. Test installs against your actual Laravel/PHP matrix before relying on it.README.md for the intended usage example.src/ for the base class (typically a TypedCollection extending Illuminate\Support\Collection).composer.json for PHP and illuminate/support constraints.use Vistik\TypedCollections\TypedCollection;
class UserCollection extends TypedCollection
{
protected $type = \App\Models\User::class;
}
$users = new UserCollection([$userA, $userB]);
$users->push($userC); // allowed
$users->push(new stdClass()); // should throw
collect() result, so callers get immediate type enforcement at the boundary.UserCollection, InvoiceLineCollection, ProductCollection. Keep the $type property as the single source of truth and avoid scattering instanceof checks through callers.UserCollection, not an arbitrary Collection that might contain arrays or nulls.class EloquentUserRepository
{
public function all(): UserCollection
{
return new UserCollection(User::query()->get()->all());
}
}
Collection. Wrap it explicitly with new UserCollection($models->all()) or a named constructor. Avoid assuming User::all() returns your subclass.Collection, most collection methods are available. Be deliberate about whether a map() result should remain typed. If the mapping changes the element type, create a new typed collection for the new type instead of reusing the old one.push, prepend, offset assignment) enforce the type. Use the typed collection in tests to assert that invalid elements fail fast rather than leaking into later code.Illuminate\Support\Collection has changed significantly since 2017. Methods such as map, filter, and when may return base collections or behave differently with subclassing. Verify the return type of every transformation you rely on.static in one Laravel version may return Collection in another. Do not assume chained calls preserve your typed collection; re-wrap when the type must be preserved.null, associative array keys, and nested collections. If null is allowed, confirm it is intentional. If the type check uses is_a() versus instanceof, behavior with interfaces and parent classes may differ from expectations.Model::all() and relationship properties return Laravel’s own Collection. Wrapping is explicit; there is no automatic conversion.$type. Add a small test that pushes a wrong-type value and asserts the failure, so future Laravel upgrades do not silently weaken enforcement.class TypedCollection extends \Illuminate\Support\Collection
{
protected string $type;
public function __construct($items = [])
{
parent::__construct($items);
$this->assertValid($this->all());
}
public function push(...$values)
{
$this->assertValid($values);
return parent::push(...$values);
}
protected function assertValid(array $values): void
{
foreach ($values as $value) {
if (! $value instanceof $this->type) {
throw new \InvalidArgumentException(
sprintf('Expected [%s], got [%s].', $this->type, get_debug_type($value))
);
}
}
}
}
@var Collection<int, User> docblocks plus PHPStan/Psalm often provide better day-to-day safety than a runtime subclass, without an abandoned dependency.How can I help you explore Laravel packages today?