AssertableJsonApi::data(Closure) — scope into the data member using the full AssertableJson fluent API (where, has, missing, count, etc, dd, dump, when, tap, …)AssertableJsonApi::meta(Closure) — scope into metaAssertableJsonApi::links(Closure) — scope into linksAssertableJsonApi::errors(Closure) — scope into errorsAssertableJsonApi::included(Closure) — scope into includedAssertableJsonApi::relationship(string $name, Closure) — scope into relationships.{name}.data of the current resourceAssertableJsonApi::at(int $position, Closure $callback) — optional closure variant; when a callback is supplied the method runs assertions inside that scope and returns $this (the collection) for further chainingAssertableJsonApi::atRelation(Model $model, Closure $callback) — optional closure variant on atRelationAssertableJsonApi::assertIsJsonApiDocument() — validates the top-level JSON:API document structure (requires at least one of data/errors/meta; data and errors must not coexist; included requires data)errors, no data) are now accepted by fromTestResponse and can be asserted via errors()AssertableJsonApi constructor is now aligned with the parent AssertableJson(array $props, ?string $path = null). All JSON:API state (id, type, attributes, relationships, included) is now derived from props via accessors instead of stored as separate fields. As a result, parent methods scope(), first(), each(), where(), has(), missing(), count(), etc(), dd(), dump(), when(), tap() are now fully functional on AssertableJsonApi instances.fromTestResponse no longer silently collapses a collection response to the first item. Calling hasAttribute(), hasId(), hasType(), or relationship methods directly on a collection root now operates on the root document, not the first item. Use ->at(0) to scope into a specific collection item before running those assertions.hasAttribute($name, $value) value comparison tightened. The old implementation used assertContains($value, $attributes) which matched the value against any attribute — regardless of key. The new implementation uses assertSame($value, $attributes[$name]) which correctly checks the exact key/value pair. Action required: any test that relied on the old loose matching (e.g. hasAttribute('subtitle', 'Hello') passing because a different key happened to hold 'Hello') needs to be corrected.hasAttribute($name, $value) skipping falsy values fixed. The old guard was if ($value), which skipped the value check when $value was 0, false, or ''. The guard is now if ($value !== null), so values of 0 and false are correctly asserted.toArray() now returns the full document props (delegating to AssertableJson::toArray()) instead of only the attributes of the current resource. Code that called ->toArray() to obtain attributes should use ->toArray()['data']['attributes'] or ->prop('data.attributes').hasNotAttribute($name, $value) semantics clarified. Without $value, asserts the attribute key is absent. With $value, asserts that either the key is absent or the key's value differs from $value. Previously the method always failed if the key existed, even when only checking that a specific value was not present.hasAttributes() now accepts both list form (['title', 'abstract'] — key existence only) and map form (['title' => 'Hello'] — key + exact value).scope(), first(), each() from the parent now work correctly because the constructor matches the parent signatureAssertableJsonApi::rootProps is threaded into child scopes so included, meta, and links are reachable from any scope depthApiable::modelResourceMap() to register custom JsonApiResource classes per model globallyApiable::jsonApiResourceFor() to resolve the registered resource class for a given model instanceJsonApiResponse::usingResource() to override the resource class used for serialization per-responseJsonApiResource::toApplicationJsonArray() for plain JSON serialization merging model attributes with computed onesJsonApiResource classarray_walk callback was not returning the modified array); replaced with an explicit loop/?filter[withStatuses]=Active&filter[withStatuses]=Archived) (#15)apiable:docs command to generate API documentation from PHP attributes in Postman v2.1, Markdown/MDX, and OpenAPI 3.1 formats$description property to all *QueryParam attributes for documentation generation#[DocumentedResource], #[DocumentedEndpointSection], and #[EndpointResource] PHP attributes for annotating controllersOpenSoutheners\LaravelApiable\Documentation\Generator class for building resource documentation trees from registered routesOpenSoutheners\LaravelApiable\Http\JsonApiPaginator class exposing pagination logic as a typed static methodHandler::includesTrace() was calling env('APP_DEBUG') outside of the config directory, which returns null in production; now uses config('app.debug')IteratesResultsAfterQuery had leaked application-specific imports (App\Domain\Process\Models\Box\Box, App\Models\ModelForm) left over from a real projectaddAppendsToResult() instanceof JsonApiCollection check was unreachable because it was nested inside an instanceof JsonApiResource branch (since JsonApiCollection extends JsonApiResource); logic restructured to check the most-specific subtype firstaddAppendsToResult() instanceof Paginator branch was using the Paginator contract which does not declare through(); changed to AbstractPaginatorCollectsResources::collects() was missing a return null fallback, causing PHPStan to report a missing return statementisScope() in ApplyFiltersToQuery was using get_parent_class() to exclude base builder methods, which could return false for unknown types; now diffs against Illuminate\Database\Eloquent\Builder::class directly, which is also more accurateGenerator (malformed class name OpenSouthenersLaravelApiableDocumentationResource → \OpenSoutheners\LaravelApiable\Documentation\Resource)FastPaginateApiable::toJsonApi() given collection as input returns empty responsejsonApiPaginate() method typosjsonApiPaginate() methodopen-southeners/laravel-helpers with open-southeners/extended-phpJsonApiResponseOpenSoutheners\LaravelApiable\Http\JsonApiResponseOpenSoutheners\LaravelApiable\Http\RequestQueryObjectOpenSoutheners\LaravelApiable\Http\Resources\JsonApiResourceOpenSoutheners\LaravelApiable\Http\Resources\JsonApiCollectionOpenSoutheners\LaravelApiable\Contracts\ViewQueryableOpenSoutheners\LaravelApiable\Contracts\ViewableBuilderHandler::withHeader (used as apiable()->jsonApiRenderable()->withHeader) to add headers to JSON errorstags_count (needs to end with _count). E.g:JsonApiResponse::using(Post::class)->allowInclude(['tags_count']);
// Then request this with ?include=tags_count
self types in traits (replaced to static)open-southeners/laravel-helpers utilities package version constrain so other packages can use similar versions under the same major releasewithCount method usage with apiable is replacing the underlying added selects for these countsresponse.formatting with type and force sub-items as optionsApiable::forceResponseFormatting())ForceAppendAttribute when reading controller attributesJsonApiResponse::applyDefaultSort method or OpenSoutheners\LaravelApiable\Attributes\ApplyDefaultSort attributeJsonApiResponse::applyDefaultFilter method or OpenSoutheners\LaravelApiable\Attributes\ApplyDefaultFilter attributeJsonApiResponse::forceFormatting methodRequest::wantsJsonApi() only looks at Accept header, not looking for Content-Type one anymoreAccept header if they want a diffent response formatting. For e.g. sending Accept: application/json will get raw JSON response. Default being configured in the config/apiable.php make sure to publish or update it/posts?sort=tags.created_at)JsonApiResponse using viewable builder (query scopes to filter by view permission) didn't send any user as first parameterJsonApiResponse::paginateUsing(fn ($query) => $query->simplePaginate()) method to customise pagination used in JSON API responses.config/apiable.php, they will always be applied as long as there are any under the relationship. They can be found under relationships.data.*.meta and keyed following the same as before: pivot_name_pivot_attribute (pivot_name being customisable through: https://laravel.com/docs/10.x/eloquent-relationships#customizing-the-pivot-attribute-name)include_pivot_attributes config option to optionally add pivot attributes to the relationships data (default to false to match previous functionality). Pivot data will be added as pivot_name_pivot_attribute (remember if you're using as it will use this one: https://laravel.com/docs/10.x/eloquent-relationships#customizing-the-pivot-attribute-name).Apiable::toJsonApi() returning false sometimes, now returns an API resource/collection all the time (might be a breakchange therefore this was minor release)JsonApiResponse::conditionallyLoadResults() method doesn't work as expectedapiable()->jsonApiRenderable() now returns thrown exception headersContent-Type=application/vnd.api+json response headerfilter[attr]=0 won't be filtered outApiable::jsonApiRenderable method, Handler missing import of Symfony's HttpExceptionApiable::jsonApiRenderable now accepts 2 arguments: Throwable exception & second a bool nullable withTrace, error handling redesign to properly match JSON:API & Laravel.JsonApiResponse::conditionallyLoadResults to manage adding viewable query or not to the responseQueryParametersValidator constants ENFORCE_VALIDATION_STRATEGY & FILTER_VALIDS_ONLY_STRATEGY, now strategy parameter is changed to a boolean insteadIteratesResultsAfterQuery::appendToApiResourcefilter[attribute][0]=1&filter[attribute][1]=2)explode second argumentforceAppendWhen method to JsonApiResponseapiable()->jsonApiRenderable()apiable()->jsonApiRenderable()filter[attribute]=0 now doesn't get removedpage[size] in request doesn't get the number of results when lower than model's $perPage (only on higher)?q=hello&q[filter][attribute]=foo or ?search=hello&search[filter][attribute]=foo?filter=test was throwing an errorwithQueryfilter[status]=Active,Inactive) will be fully invalidated if one member doesn't pass the validationRequestQueryObject::filters method now implements own logic instead of reusing Symfony's request one to be able to get AND filters with same keys (attributes) on a different array itemwithWhereHas under filtering, now all related data is loaded (until reimplemented with AND/OR conditionals...)JsonApiResponse resource collectionsresponses.include_ids_on_attributes)JsonApiResponse::includingIdAttributes() method for setting responses.include_ids_on_attributes option value within an individual responseresolveFromRoute when using invokable controller JsonApiResponseJsonApiResponse::toArray returning wrong formatted JSON:API arrayJsonApiResponse::forceAppend method fails getting model class when from query builderrequests.validate_params config option to apiable.php for enforcing validation. Requires to publish or manually copy [#7]gte (greater or equal than), gt (greater than), lte (lower or equal than), lt (lower than) [#5]OpenSoutheners\LaravelApiable\Http\QueryParamsValidatorallowed_filters included to meta data are now URL-safeallowed_sorts structure is now based on ['attribute' => 'allowed_direction']Illuminate\Contracts\Support\Arrayable interface to JsonApiResponse so it can be used in Inertia responses (as an example)OpenSoutheners\LaravelApiable\Contracts\ViewQueryable and OpenSoutheners\LaravelApiable\Contracts\ViewableBuilder classes for models & custom query builders now functional with JsonApiResponse (can be disabled by config see config/apiable.php)⚠️ Please make sure to run php artisan vendor:publish --force --provider="OpenSoutheners\LaravelApiable\ServiceProvider" ⚠️
⚠️ Please make sure to run php artisan vendor:publish --force --provider="OpenSoutheners\LaravelApiable\ServiceProvider" ⚠️
JsonApiResponse::list and JsonApiResponse::getOne methods as isn't the responsibility of this package to act as query repository so it makes easier to encapsulate it into repository or whatever design pattern you're using. Use JsonApiResponse::using instead sending the query as its parameter.OpenSoutheners\LaravelApiable\Attributes\FilterQueryParamOpenSoutheners\LaravelApiable\Attributes\IncludeQueryParamOpenSoutheners\LaravelApiable\Attributes\AppendsQueryParamOpenSoutheners\LaravelApiable\Attributes\FieldsQueryParamOpenSoutheners\LaravelApiable\Attributes\SortQueryParamOpenSoutheners\LaravelApiable\Attributes\SearchQueryParamJsonApiResponse::from can now be non-statically called as JsonApiResponse::using (for dependency injection usage in controllers, etc)php artisan vendor:publish --force --provider="OpenSoutheners\LaravelApiable\ServiceProvider"JsonApiResponse now only accepts 1 optional parameter (being the query parameter removed in favor of manually setting this by calling JsonApiResponse::using method)RequestQueryObject now only accepts 1 optional parameter (being the query parameter removed in favor of manually setting this by calling RequestQueryObject::setQuery method)JsonApiResponse:forceAppend method to force appends to the final responseJsonApiResponse::getPipelineQuery now accepts an optional callback closure & is exposed as public for repositories usageRequestQueryObject::allowFilter method now accepts 3 arguments (attribute, operator/values, values)AllowedFilter::scopedValue method was removed and accidentally pushed onto RequestQueryObject::allowScopedFilterRequestQueryObject::allows to be able to group everything in the same method call without needing to use class methods (AllowedFilters, AllowedFields, etc...)AllowedFilter::scoped method for Laravel query scopes filters to specify the filter is not an actual attribute but a query builder scopeenforce_scoped_names to config/apiable.php to be used so they rename scoped filters in case there are attributes with the same name on the model (remember to use vendor:publish artisan command to update the config file)include_allowed to config/apiable.php to be used so any JsonApiResponse will include allowed filters and sorts (like using JsonApiResponse::includeAllowedToResponse but on all requests)AssertableJsonApi::hasNotAttribute and AssertableJsonApi::hasNotAttributes methods for negative test assertions (counter part of AssertableJsonApi::hasAttribute and AssertableJsonApi::hasAttributes)allowAppends & allowFields sending array of attributes will wrongly parse themallowIncludes adds nested array which leads into issuesOpenSoutheners\LaravelApiable\JsonApiOptions replaced by OpenSoutheners\LaravelApiable\Facades\Apiable::modelResourceTypeMap facade method)toJsonApiallowSort & allowInclude methodsgetOne method for parse current model instance or model key passedisCollection and isResource testing methods to AssertableJsonApi?appends[type]=my_attribute,another_attribute as they're completely different from fieldsJsonApiResource::withRelations() method (bad idea, lots of possible N+1 problems to the dev-user)How can I help you explore Laravel packages today?