From 59ce75824cf33a9b7e25e2d1b1209732f7016bb5 Mon Sep 17 00:00:00 2001 From: Christopher Gammie Date: Fri, 15 Mar 2019 12:53:59 +0000 Subject: [PATCH 1/3] [Feature] Allow non-camel case adapter relation method names. Although developers should follow the PSR1 standard of camel casing method names, there are some developers who follow the Eloquent attribute standard of snake casing attributes when defining relationships. This commit allows an adapter to have its relation method name in exactly the same format as the JSON API field name. I.e. a field name of `user_history` and a relation method called `user_history`. Otherwise it falls back to the PSR1 standard and expects the method name to be the camel case version of the field name. See #315 for discussion. --- CHANGELOG.md | 8 ++++++++ src/Adapter/AbstractResourceAdapter.php | 23 ++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e23bd6a1..d62799b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/) and [this changelog format](http://keepachangelog.com/). +## Unreleased + +### Added +- [#315](https://github.com/cloudcreativity/laravel-json-api/issues/315) +Allow developers to use the exact JSON API field name as the relationship method name on their +adapters. Although we recommend following the PSR1 standard of using camel case for method names, +this does allow a developer to use snake case field names with snake case method names. + ## [1.0.1] - 2019-03-12 ### Fixed diff --git a/src/Adapter/AbstractResourceAdapter.php b/src/Adapter/AbstractResourceAdapter.php index 83cc023a..580a2a98 100644 --- a/src/Adapter/AbstractResourceAdapter.php +++ b/src/Adapter/AbstractResourceAdapter.php @@ -197,11 +197,32 @@ protected function isFillableRelation($field, $record) } /** - * @param $field + * Get the method name on this adapter for the supplied JSON API field. + * + * By default we expect the developer to be following the PSR1 standard, + * so the method name on the adapter should use camel case. + * + * However, some developers may prefer to use the actual JSON API field + * name. E.g. they could use `user_history` as the JSON API field name + * and the method name. + * + * Therefore we return the field name if it exactly exists on the adapter, + * otherwise we camelize it. + * + * A developer can use completely different logic by overloading this + * method. + * + * @param string $field + * the JSON API field name. * @return string|null + * the adapter's method name, or null if none is implemented. */ protected function methodForRelation($field) { + if (method_exists($this, $field)) { + return $field; + } + $method = Str::camelize($field); return method_exists($this, $method) ? $method : null; From 8205613f20d8ad9d201bf548175df09dbf69f426 Mon Sep 17 00:00:00 2001 From: Christopher Gammie Date: Fri, 15 Mar 2019 13:14:15 +0000 Subject: [PATCH 2/3] [Feature] Allow camel casing of include paths to be disabled Adds a property on the includes models trait so that camel casing of include path segments can be disabled. Note that the default behaviour will remaing to camel case these as PSR1 says that method names on classes must be camel case. See #315 --- CHANGELOG.md | 5 +-- src/Eloquent/AbstractAdapter.php | 2 +- src/Eloquent/Concerns/IncludesModels.php | 40 +++++++++++++++++++++++- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d62799b4..9182d5e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,9 @@ All notable changes to this project will be documented in this file. This projec ### Added - [#315](https://github.com/cloudcreativity/laravel-json-api/issues/315) Allow developers to use the exact JSON API field name as the relationship method name on their -adapters. Although we recommend following the PSR1 standard of using camel case for method names, -this does allow a developer to use snake case field names with snake case method names. +adapters, plus for default conversion of names in include paths. Although we recommend following +the PSR1 standard of using camel case for method names, this does allow a developer to use snake +case field names with snake case method names. ## [1.0.1] - 2019-03-12 diff --git a/src/Eloquent/AbstractAdapter.php b/src/Eloquent/AbstractAdapter.php index 349ff7b4..1022b8df 100644 --- a/src/Eloquent/AbstractAdapter.php +++ b/src/Eloquent/AbstractAdapter.php @@ -622,7 +622,7 @@ private function guessRelation() { list($one, $two, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); - return $caller['function']; + return $this->methodForRelation($caller['function']); } } diff --git a/src/Eloquent/Concerns/IncludesModels.php b/src/Eloquent/Concerns/IncludesModels.php index 9e41946e..8d1296ad 100644 --- a/src/Eloquent/Concerns/IncludesModels.php +++ b/src/Eloquent/Concerns/IncludesModels.php @@ -79,6 +79,13 @@ trait IncludesModels */ protected $includePaths = []; + /** + * Whether Eloquent relations are camel cased. + * + * @var bool + */ + protected $camelCaseRelations = true; + /** * Add eager loading to the query. * @@ -145,7 +152,38 @@ protected function convertIncludePath($path) } return collect(explode('.', $path))->map(function ($segment) { - return Str::camelize($segment); + return $this->modelRelationForField($segment); })->implode('.'); } + + /** + * Convert a JSON API field name to an Eloquent model relation name. + * + * According to the PSR1 spec, method names on classes MUST be camel case. + * However, there seem to be some Laravel developers who snake case + * relationship methods on their models, so that the method name matches + * the snake case format of attributes (column values). + * + * The `$camelCaseRelations` property controls the behaviour of this + * conversion: + * + * - If `true`, a field name of `user-history` or `user_history` will + * expect the Eloquent model relation method to be `userHistory`. + * - If `false`, the field name will be expected to be identical to + * the Eloquent method, i.e. `user_history` in both JSON API and on the + * model. (For this scenario, `user-history` will not work as a field + * name.) + * + * If the developer has different conversion logic, they should overload + * this method and implement it themselves. + * + * @param string $field + * the JSON API field name. + * @return string + * the expected relation name on the Eloquent model. + */ + protected function modelRelationForField($field) + { + return $this->camelCaseRelations ? Str::camelize($field) : $field; + } } From 66053e485d528d972ab6054458bc03cff9440533 Mon Sep 17 00:00:00 2001 From: Christopher Gammie Date: Fri, 15 Mar 2019 13:41:21 +0000 Subject: [PATCH 3/3] [Refactor] Use snake case if not camel casing model relation names --- src/Eloquent/AbstractAdapter.php | 3 ++- src/Eloquent/Concerns/IncludesModels.php | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Eloquent/AbstractAdapter.php b/src/Eloquent/AbstractAdapter.php index 1022b8df..225ec3cf 100644 --- a/src/Eloquent/AbstractAdapter.php +++ b/src/Eloquent/AbstractAdapter.php @@ -26,6 +26,7 @@ use CloudCreativity\LaravelJsonApi\Document\ResourceObject; use CloudCreativity\LaravelJsonApi\Exceptions\RuntimeException; use CloudCreativity\LaravelJsonApi\Pagination\CursorStrategy; +use CloudCreativity\LaravelJsonApi\Utils\Str; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations; @@ -622,7 +623,7 @@ private function guessRelation() { list($one, $two, $caller) = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); - return $this->methodForRelation($caller['function']); + return $this->modelRelationForField($caller['function']); } } diff --git a/src/Eloquent/Concerns/IncludesModels.php b/src/Eloquent/Concerns/IncludesModels.php index 8d1296ad..77a9cb02 100644 --- a/src/Eloquent/Concerns/IncludesModels.php +++ b/src/Eloquent/Concerns/IncludesModels.php @@ -169,10 +169,10 @@ protected function convertIncludePath($path) * * - If `true`, a field name of `user-history` or `user_history` will * expect the Eloquent model relation method to be `userHistory`. - * - If `false`, the field name will be expected to be identical to - * the Eloquent method, i.e. `user_history` in both JSON API and on the - * model. (For this scenario, `user-history` will not work as a field - * name.) + * - If `false`, a field name of `user-history` or `user_history` will + * expect the Eloquent model relation method to be `user_history`. I.e. + * if PSR1 is not being followed, the best guess is that method names + * are snake case. * * If the developer has different conversion logic, they should overload * this method and implement it themselves. @@ -184,6 +184,6 @@ protected function convertIncludePath($path) */ protected function modelRelationForField($field) { - return $this->camelCaseRelations ? Str::camelize($field) : $field; + return $this->camelCaseRelations ? Str::camelize($field) : Str::underscore($field); } }