PHPStan Generic Support
ListCollection provides full generic type support through PHPStan annotations, enabling type-safe operations with complete IDE autocomplete and static analysis.
Template Definition
The class uses the @template TValue annotation to enable generic type inference:
/**
* @template TValue
*
* @extends Collection<int, TValue>
*/
class ListCollection extends Collection
{
// ...
}
This means ListCollection maintains type information about its values while enforcing that keys are always integers.
Type Inference Examples
Automatic Type Inference
PHPStan automatically infers the value type from the items you pass:
// Inferred as ListCollection<int>
$numbers = new ListCollection([1, 2, 3, 4, 5]);
// Inferred as ListCollection<string>
$names = new ListCollection(['Alice', 'Bob', 'Charlie']);
// Inferred as ListCollection<User>
$users = new ListCollection([
new User('[email protected]'),
new User('[email protected]'),
]);
Explicit Type Annotations
For better clarity and type safety, especially with empty collections, use explicit PHPDoc annotations:
/** @var ListCollection<Product> */
$products = new ListCollection();
// Now PHPStan knows this returns Product|null
$first = $products->first();
// And this callback expects Product
$products->each(function (Product $product) {
$product->activate();
});
Type-Safe Operations
The map() method preserves type information through transformations:
/** @var ListCollection<int> */
$numbers = new ListCollection([1, 2, 3]);
// PHPStan infers ListCollection<string>
$strings = $numbers->map(fn(int $n): string => "Number: {$n}");
// PHPStan infers ListCollection<float>
$floats = $numbers->map(fn(int $n): float => $n * 1.5);
The transform() method supports type changes with the @phpstan-this-out annotation:
/**
* @template TMapValue
*
* @param callable(TValue, int): TMapValue $callback
*
* @phpstan-this-out static<TMapValue>
*/
public function transform(callable $callback): static
Usage example:
/** @var ListCollection<int> $collection */
$collection = new ListCollection([1, 2, 3]);
// After transform, it's ListCollection<string>
$collection->transform(fn(int $n): string => "Item {$n}");
Unlike map(), which returns a new collection, transform() mutates the original collection and changes its type. Make sure your variable annotation reflects the new type after transformation.
Pull Method with Default Types
The pull() method demonstrates advanced generic usage with default values:
/**
* @template TPullDefault
*
* @param int $key
* @param TPullDefault|(Closure(): TPullDefault) $default
* @return TValue|TPullDefault
*/
public function pull($key, $default = null)
Type-safe usage:
/** @var ListCollection<string> */
$names = new ListCollection(['Alice', 'Bob']);
// Returns: string
$first = $names->pull(0, 'Unknown');
// Returns: string|int (union type)
$value = $names->pull(10, 42);
Working with Complex Types
Collections of Objects
class Order
{
public function __construct(
public int $id,
public float $total,
public string $status,
) {}
}
/** @var ListCollection<Order> */
$orders = new ListCollection([
new Order(1, 99.99, 'pending'),
new Order(2, 149.99, 'shipped'),
new Order(3, 79.99, 'delivered'),
]);
// PHPStan knows this is ListCollection<Order>
$pending = $orders->filter(
fn(Order $order): bool => $order->status === 'pending'
);
// PHPStan knows this is ListCollection<float>
$totals = $orders->map(
fn(Order $order): float => $order->total
);
// PHPStan knows $order is Order
$orders->each(function (Order $order) {
echo "Order #{$order->id}: {$order->status}\n";
});
Nested Collections
/** @var ListCollection<ListCollection<int>> */
$matrix = new ListCollection([
new ListCollection([1, 2, 3]),
new ListCollection([4, 5, 6]),
new ListCollection([7, 8, 9]),
]);
// PHPStan infers ListCollection<int>
$flattened = $matrix->flatten();
// Access with full type safety
$firstRow = $matrix->first(); // ListCollection<int>
$firstValue = $firstRow?->first(); // int|null
Type Safety with Static Factories
Make Factory
// Type is inferred from input
$list = ListCollection::make([1, 2, 3]); // ListCollection<int>
// Explicit annotation for clarity
/** @var ListCollection<string> */
$names = ListCollection::make(['Alice', 'Bob']);
Times Factory
// PHPStan infers the return type from the callback
$list = ListCollection::times(5, fn(int $i): string => "Item {$i}");
// Type: ListCollection<string>
Wrap Factory
$single = ListCollection::wrap('value'); // ListCollection<string>
$array = ListCollection::wrap([1, 2, 3]); // ListCollection<int>
PHPStan Configuration
To get full type safety benefits, ensure you have PHPStan configured in your project:
Laravel List requires PHPStan/Larastan for type inference. The package is tested with larastan/larastan: ^3.0.
Basic Configuration
Add to your phpstan.neon or phpstan.neon.dist:
includes:
- vendor/larastan/larastan/extension.neon
parameters:
level: 8
paths:
- app
- tests
IDE Support
For full IDE autocomplete and type hints:
PhpStorm: PHPStan annotations are supported natively. Install the PHPStan plugin for enhanced support.
VS Code: Install the PHPStan extension and configure it to use your project’s PHPStan configuration.
Common Type Issues
// ❌ Type information is lost
$collection = new ListCollection([1, 2, 3]);
foreach ($collection as $item) {
// $item is mixed, not int
}
Solution: Add explicit type annotations:
// ✅ Type information is preserved
/** @var ListCollection<int> */
$collection = new ListCollection([1, 2, 3]);
foreach ($collection as $item) {
// $item is now int
echo $item * 2;
}
Issue: Empty Collections
// ❌ Type cannot be inferred from empty array
$collection = new ListCollection([]);
Solution: Always annotate empty collections:
// ✅ Explicit type annotation
/** @var ListCollection<User> */
$users = new ListCollection();
When working with Laravel Eloquent models, consider using ListCollection::make($query->get()) with a type annotation to maintain type safety throughout your application.
Benefits of Type Safety
- IDE Autocomplete: Get accurate method suggestions and parameter hints
- Early Error Detection: Catch type mismatches before runtime
- Refactoring Confidence: Safely rename and restructure code
- Documentation: Types serve as inline documentation
- Reduced Testing: Static analysis catches bugs that would require tests
Next Steps