Skip to content

Commit

Permalink
[Feature] Use hyphenated member names by default
Browse files Browse the repository at this point in the history
The JSON API spec recommends using hyphenated member names. This
package now follows that recommendation by default.

The Eloquent schema will dasherize model keys, so `foo_bar` will
become `foo-bar`. This behaviour can be turned off or overloaded
as required.

The abstract search will snake case sort parameters if the model
uses snake cased attributes. If the model does not use snake case,
it will came case the search field instead. The behaviour can
be changed by overloaded as required.

See issue #17
  • Loading branch information
lindyhopchris committed Sep 1, 2016
1 parent db2087f commit 22e5e28
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 7 deletions.
31 changes: 31 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ occurs before validation, so it makes more sense for the arguments to be this wa
The method signature for the `errors` method has changed. To maintain the old behaviour use `error` instead (it
accepts an error collection as well as a single error).

### Schemas

The JSON API specification recommends using hyphens for member names, e.g. `foo-bar`. The Eloquent schema now uses
this recommendation as its default behaviour. E.g. a model key of `foo_bar` will be serialized as `foo-bar`.

To maintain the old behaviour (of using the model key for the member key), set the `$hyphenated` property on your
schema to `false`. If you want to implement your own logic, overload the `keyForAttribute` method.

If you have irregular mappings of model keys to attribute keys, you can define these in your `$attributes` property,
e.g.

``` php
protected $attributes = [
'foo',
'bar',
'foo_bar' // will be dasherized to 'foo-bar' by default
'baz' => 'bat' // model key `baz` will be serialized to attribute key `bat`
];
```

### Sort

We've merged the abstract sorted search class into the abstract search class, to simplify things.
Expand All @@ -130,6 +150,17 @@ class Search extends AbstractSearch
}
```

#### Sort Parameter Field Conversion

When working out what column to use for a sort parameter, the sort parameter will automatically be snake cased if
your model uses snake attributes. If it does not use snake attributes, the parameter will be camel cased.

If you have irregular mappings of search params to column names, add that mapping to your `$sortColumns` property,
e.g. `$sortColumns = ['foo-bar' => 'baz_bat']`.

If you want to use a complete different logic for converting search params to column names, overload the
`columnForField()` method.

### Validator Providers

The classes that provide validators for individual resource types generally extend `AbstractValidatorProvider`. This
Expand Down
20 changes: 16 additions & 4 deletions src/Schema/EloquentSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
namespace CloudCreativity\LaravelJsonApi\Schema;

use Carbon\Carbon;
use CloudCreativity\JsonApi\Exceptions\SchemaException;
use CloudCreativity\JsonApi\Exceptions\RuntimeException;
use CloudCreativity\LaravelJsonApi\Utils\Str;
use DateTime;
use Illuminate\Database\Eloquent\Model;

Expand Down Expand Up @@ -83,14 +84,25 @@ abstract class EloquentSchema extends AbstractSchema
*/
protected $attributes = [];

/**
* Whether resource member names are hyphenated
*
* The JSON API spec recommends using hyphens for resource member names, so this package
* uses this as the default. If you do not want to follow the recommendation, set this
* to `false`.
*
* @var bool
*/
protected $hyphenated = true;

/**
* @param object $resource
* @return mixed
*/
public function getId($resource)
{
if (!$resource instanceof Model) {
throw new SchemaException('Expecting an Eloquent model.');
throw new RuntimeException('Expecting an Eloquent model.');
}

$key = $this->idName ?: $resource->getKeyName();
Expand All @@ -105,7 +117,7 @@ public function getId($resource)
public function getAttributes($resource)
{
if (!$resource instanceof Model) {
throw new SchemaException('Expecting an Eloquent model to serialize.');
throw new RuntimeException('Expecting an Eloquent model to serialize.');
}

return array_merge(
Expand Down Expand Up @@ -172,7 +184,7 @@ protected function getModelAttributes(Model $model)
*/
protected function keyForAttribute($modelKey)
{
return $modelKey;
return $this->hyphenated ? Str::dasherize($modelKey) : $modelKey;
}

/**
Expand Down
13 changes: 10 additions & 3 deletions src/Search/AbstractSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use CloudCreativity\JsonApi\Contracts\Pagination\PaginatorInterface;
use CloudCreativity\LaravelJsonApi\Contracts\Search\SearchInterface;
use CloudCreativity\LaravelJsonApi\Pagination\Page;
use CloudCreativity\LaravelJsonApi\Utils\Str;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Model;
Expand Down Expand Up @@ -277,7 +278,7 @@ protected function sortBy(Builder $builder, SortParameterInterface $param)
*/
protected function getQualifiedSortColumn(Builder $builder, $field)
{
$key = $this->columnForField($field);
$key = $this->columnForField($field, $builder->getModel());

if (!str_contains('.', $key)) {
$key = sprintf('%s.%s', $builder->getModel()->getTable(), $key);
Expand All @@ -290,11 +291,17 @@ protected function getQualifiedSortColumn(Builder $builder, $field)
* Get the table column to use for the specified search field.
*
* @param string $field
* @param Model $model
* @return string
*/
protected function columnForField($field)
protected function columnForField($field, Model $model)
{
return isset($this->sortColumns[$field]) ? $this->sortColumns[$field] : $field;
/** If there is a custom mapping, return that */
if (isset($this->sortColumns[$field])) {
return $this->sortColumns[$field];
}

return $model::$snakeAttributes ? Str::snake($field) : Str::camel($field);
}

/**
Expand Down
70 changes: 70 additions & 0 deletions src/Utils/Str.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

/**
* Copyright 2016 Cloud Creativity Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace CloudCreativity\LaravelJsonApi\Utils;

use Illuminate\Support\Str as IlluminateStr;

/**
* Class Str
* @package CloudCreativity\LaravelJsonApi
*/
final class Str
{

/**
* Dasherize a string
*
* The JSON API spec recommends using hyphens for member names. This method
* converts snake case or camel case strings to their hyphenated equivalent.
*
* @param $value
* @return string
*/
public static function dasherize($value)
{
$value = IlluminateStr::snake($value);

return str_replace('_', '-', $value);
}

/**
* Snake case a dasherized string
*
* @param $value
* @param string $delimiter
* @return string
*/
public static function snake($value, $delimiter = '_')
{
$value = IlluminateStr::camel($value);

return IlluminateStr::snake($value, $delimiter);
}

/**
* Camel case a dasherized string
*
* @param $value
* @return string
*/
public static function camel($value)
{
return IlluminateStr::camel($value);
}
}
73 changes: 73 additions & 0 deletions test/Utils/StrTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/**
* Copyright 2016 Cloud Creativity Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace CloudCreativity\LaravelJsonApi\Utils;

use CloudCreativity\LaravelJsonApi\TestCase;

/**
* Class StrTest
* @package CloudCreativity\LaravelJsonApi
*/
final class StrTest extends TestCase
{

public function testDasherize()
{
$values = [
'foo' => 'foo',
'foo_bar' => 'foo-bar',
'fooBar' => 'foo-bar',
];

foreach ($values as $value => $expected) {
$actual = Str::dasherize($value);
$this->assertSame($expected, $actual, "Did not dasherize '$value' correctly");
}
}

public function testCamel()
{
$values = [
'foo' => 'foo',
'foo-bar' => 'fooBar',
'fooBar' => 'fooBar',
'foo_bar' => 'fooBar',
];

foreach ($values as $value => $expected) {
$actual = Str::camel($value);
$this->assertSame($expected, $actual, "Did not camel case '$value' correctly");
}
}

public function testSnake()
{
$values = [
'foo' => 'foo',
'foo-bar' => 'foo_bar',
'fooBar' => 'foo_bar',
'foo_bar' => 'foo_bar',
];

foreach ($values as $value => $expected) {
$actual = Str::snake($value);
$this->assertSame($expected, $actual, "Did not snake case '$value' correctly");
}
}
}

0 comments on commit 22e5e28

Please sign in to comment.