Laravel New in 13: Features, Helpers, and Upgrade Notes
#laravel #php #upgrade #backend

Laravel New in 13: Features, Helpers, and Upgrade Notes

3 min read Mohamed Said Mohamed Said

Laravel 13: What Actually Matters for Senior Engineers

Laravel 13 continues the framework's trend of shipping opinionated defaults while keeping the escape hatches open. This post focuses on the changes that affect production codebases — not the marketing highlights.

PHP 8.3 as the Minimum Baseline

Laravel 13 drops PHP 8.1 and 8.2 support entirely. That is the first gate. If your hosting stack is behind, upgrade PHP before touching the framework.

The payoff is real: typed class constants, json_validate(), readonly class improvements, and the #[\Override] attribute are all first-class citizens now. The framework itself uses typed constants on several core classes, so your IDE and static analysis tools get sharper inference out of the box.

// Framework internals now look like this — your own code can too
class Status
{
    const string ACTIVE = 'active';
    const string SUSPENDED = 'suspended';
}

Fluent Uri Value Object

Laravel 13 ships a Uri value object that wraps League URI under the hood but exposes a fluent, immutable API directly from the Illuminate\Support namespace.

use Illuminate\Support\Uri;

$uri = Uri::of('https://example.com/api/v1')
    ->withPath('/api/v2/users')
    ->withQueryParam('page', 3)
    ->withQueryParam('per_page', 25);

echo $uri; // https://example.com/api/v2/users?page=3&per_page=25

This replaces the scattered parse_url / http_build_query gymnastics that litter most codebases. It is immutable, so you can safely pass it through pipelines without defensive cloning.

Request::string() and Tighter Input Casting

The Request object gains string(), integer(), float(), and boolean() methods that return typed scalars rather than raw strings. This closes a long-standing gap where $request->input('limit') returned a string even when you expected an int.

// Before
$limit = (int) $request->input('limit', 20);

// Laravel 13
$limit = $request->integer('limit', 20); // already existed
$search = $request->string('q')->trim()->lower()->value();

The string() method returns a Stringable instance, so you can chain fluent string operations before extracting the value.

Arr::from() and Collection Interop

Arr::from() is a small but welcome addition that normalises any iterable — including generators, Traversable objects, and Collection instances — into a plain PHP array without the iterator_to_array boilerplate.

use Illuminate\Support\Arr;

$array = Arr::from($lazyCollection->take(500));

Breaking Changes Worth Auditing

1. Model::preventLazyLoading() is on by default in APP_ENV=local. If you were relying on lazy loading in tests or local tooling, you will see LazyLoadingViolationException immediately. Fix your eager loads rather than disabling the guard.

2. Storage::url() now throws on missing disks. Previously it silently returned a broken URL. Wrap calls in a try/catch or guard with Storage::disk($disk)->exists() first.

3. Queue serialization of closures requires laravel/serializable-closure ^2.0. Bump the constraint in composer.json before upgrading.

Upgrade Path

# 1. Bump PHP to 8.3 in your Dockerfile / runtime
# 2. Update composer.json
composer require laravel/framework:^13.0 --update-with-dependencies

# 3. Run the automated upgrade checks
php artisan about
php artisan config:clear && php artisan cache:clear

# 4. Run your full test suite with strict mode on
php artisan test --parallel

Check CHANGELOG.md in the framework repo for the exhaustive list; the above covers the changes most likely to bite a real production app.

Takeaways

  • PHP 8.3 is now mandatory — audit your hosting stack first.
  • The Uri value object eliminates a whole class of URL-manipulation bugs.
  • Request::string() and friends return typed values, tightening input contracts.
  • Arr::from() normalises any iterable cleanly.
  • Lazy loading violations are surfaced by default locally — treat them as bugs, not noise.
  • Queue closure serialization requires serializable-closure ^2.0.

Found this useful?

Frequently Asked Questions

3 questions
Q01 Can I upgrade to Laravel 13 without upgrading to PHP 8.3?
No. Laravel 13 requires PHP 8.3 as its minimum version. You must upgrade your runtime before bumping the framework constraint in composer.json.
Q02 Does the new Uri value object replace the existing URL helper?
It complements rather than replaces it. The `url()` helper and `URL` facade remain for route-aware URL generation. `Uri::of()` is for constructing and manipulating arbitrary URIs in a type-safe, immutable way.
Q03 Will enabling lazy loading violations by default break my test suite?
Only if your tests rely on implicit lazy loading. The fix is to add the missing `with()` eager loads to your queries. You can temporarily disable the guard in a specific test with `Model::withoutLazyLoadingViolations()` while you work through the backlog.

Continue reading

More Articles

View all