Skip to content

v1.22.0

Choose a tag to compare

@github-actions github-actions released this 31 Oct 22:53
· 372 commits to main since this release
0483a88

What most Laravel applications reach for every day: forms that post _method, validation that redirects back, file downloads with Range, 22 more validation rules, route model binding with scoping, subdomains, JSON columns, and helpers on relations. daravel_pulse now shows N+1 queries and connection waits, and daravel_teobiefy reads the Zstandard frames real clients send.

Upgrading can change what an application sees:

  • A failed validation in the web group redirects back, unless the client expects JSON. Request.expectsJson follows Laravel: an XHR that asks for HTML is not JSON.
  • _method is on by default. A POST route that streams a form body needs middleware.methodOverride = false.
  • Since 1.21.1 a try around next catches nothing, in any middleware. Read the new response.exception instead.
  • whereJsonContains sends JSON on MySQL and PostgreSQL. Code that encoded the value itself now encodes it twice.
  • UniqueRule.ignore is a method; the field is ignored.

Fixed

  • PostgreSQL inet, cidr, macaddr and macaddr8 columns came back as undecoded bytes, so comparing one with request.ip always failed. They are text now, as PostgreSQL prints them, and model:sync types them as String.
  • Validation errors in a JSON session use the bag format of Laravel 13 ({default: {format, messages}}), which its Store::marshalErrors needs.
  • whereJsonContains sends a JSON document on MySQL and PostgreSQL, as Laravel: a plain number or string failed there.
  • daravel_teobiefy: refusals (406, 409, 413, 401) follow the route's response profile, as upstream's api()->response().
  • accepted_if and declined_if: with a boolean rule on the other attribute, true and false compare as booleans, and :other uses the custom attribute names.

Changed

  • Request.expectsJson follows Laravel: an XHR that asks for HTML, as an Inertia visit does, no longer counts as JSON.

Added

  • Response.exception, Laravel's $response->exception: since 1.21.1 an exception no longer passes through next, so a try around next catches nothing, not only in transaction middleware. A middleware reads it here instead.
  • DB.transaction(attempts: n) waits a short random time between retries (decorrelated jitter, 2 to 250 ms), so isolates that collided do not collide again. Frozen test clocks retry at once.
  • QueryExecuted.poolWait: the time a query waited for a connection, apart from time. PostgreSQL and MySQL measure it while a listener is registered.
  • daravel_pulse: cards for N+1 queries and query storms (PULSE_N_PLUS_ONE_THRESHOLD, PULSE_QUERY_STORM_THRESHOLD) and for connection waits (PULSE_POOL_WAIT_THRESHOLD).
  • dartisan route:diff --by-prefix: progress per module.
  • Cache.touch(key, ttl), WithoutOverlapping(key, shared: true) and ReadThroughDisk, Laravel's read-through filesystem for moving files to a new disk gradually.
  • Subdomain routing: router.group(domain: '{tenant}.example.com') and .domain(), with routes of the same path on different hosts, host parameters before path ones, and router.url() on that host. Request.fake now takes its host from a host header, as the socket path does.
  • Route model binding: router.bindModel('post', Post.definition), {post:slug}, .scopeBindings() (implied by a custom column, as in Laravel) so another owner's row is a 404, .withoutScopedBindings(), .withTrashed() and .missing(). It is explicit: a Dart handler has no parameter types to tell a model from an id.
  • ORM helpers: BelongsTo.withDefault, HasOne.latestOfMany, oldestOfMany and ofMany (one query when eager loading), syncWithoutDetaching, toggle and updateExistingPivot, on BelongsToMany and on the model, updateQuietly, deleteQuietly, namedGlobalScopes with withoutGlobalScope(name), and QueryBuilder.whereNone.
  • JSON columns: where('meta->lang', 'id'), orderBy and update({'meta->lang': 'en'}) on SQLite, PostgreSQL and MySQL, as Laravel compiles them. Writing a nested path needs its parent on PostgreSQL and MySQL, as in Laravel; SQLite creates it. Tested live on SQLite, PostgreSQL, MySQL 8.4 and MariaDB 11.
  • withoutMiddleware(['csrf']) takes aliases, groups, types and instances, also those from an outer group.
  • 22 validation rules: gt, gte, lt, lte, decimal, multiple_of, min_digits, max_digits, date_equals, timezone, required_with_all, required_without_all, required_if_accepted, present_if, prohibited_if, prohibited_unless, prohibits, and the exclude family, which leaves attributes out of validated(). Numbers compare as exact decimals. timezone also accepts old IANA aliases, and not per_country.
  • Rules.enum_, and Rules.unique(...).ignore(id), .where(...) and Rules.exists(...).where(...). Both rules accept schema.table, which failed with an ArgumentError.
  • Response.download, Response.file, Storage.download and Storage.response, with RFC 6266 file names, ETag, and Range (206) when given the request. deleteFileAfterSend removes a temporary file once it was sent. Several ranges in one request get the whole file.
  • _method and X-HTTP-Method-Override turn a POST into PUT, PATCH or DELETE, on by default as in Laravel; only form bodies are read. Request.originalMethod keeps the verb on the wire.
  • A failed validation in the web group redirects browsers and Inertia back, with errors and old input flashed (session.errors, session.old); clients expecting JSON keep the 422. Response.back ignores a foreign Referer.
  • Remember-me cookies in SessionGuard: attempt(..., remember: true) and login(user, remember: true) write Laravel's remember_web_<sha1> cookie, and either side can log in from the other's.
  • Payload profiles of teobiefy-laravel-api-response 0.3 in daravel_teobiefy: withTeobiefyProfiles() and the teobiefy.profile middleware (route patterns, XChaCha20, ChaCha20 and AES-256-GCM with key ids, HMAC-SHA256, replay protection). Responses are raw frames; requests may use any frame ext-zstd writes, read by a pure Dart decoder.
  • ModelQuery.orderByRaw, KeyType.manualInt (Laravel's $incrementing = false on an integer key), Request.inputSync for model scopes, and Clock.nowIn('Asia/Makassar').