Mapping
Bag allows you to map both input names to properties, and properties to output names. This is useful when transforming JSON snake_case
to your codes camelCase
and vice-versa.
How Mapping Works
Mapping should be thought of as aliasing property names. The input mapper determines what the incoming aliases could be by transforming the original property name, while the output mapper transform the property name and uses it for the outgoing property name. This means that if you want to map a propertyName
from the input property_name
you would use a SnakeCase
mapper, rather than a camelCase
mapper.
Mapping Names
To map names use the Bag\Attributes\MapName
, Bag\Attributes\MapInputName
, or Bag\Attributes\MapOutputName
attributes. These attributes can either be applied to the entire class, or to an individual property.
Class-level Mapping
Class-level mapping applies to all properties on the class. This is useful when all properties on a class should be mapped in the same way.
This is done by applying the mapper attribute to the class:
use Bag\Bag;
use Bag\Attributes\MapName;
use Bag\Mappers\SnakeCase;
#[MapInputName(SnakeCase::class)
#[MapOutputName(SnakeCase::class)]
class MyValue extends Bag {
public function __construct(
public string $myValue,
public string $myOtherValue
) {}
}
NOTE
The above is functionally equivelent to using:
#[MapName(input: SnakeCase::class, output: SnakeCase::class)]
We recommend using the MapInputName
and MapOutputName
attributes as mapper arguments can be passed directly, rather than as an array of values to either the inputParams
and/or outputParams
arguments:
#[MapInputName(MapperName::class, 'param1', 'param2')]
#[MapOutputName(MapperName::class, 'param1', 'param2')]
// vs
#[MapName(input: MapperName::class, inputParams: ['param1', 'param2'], output: MapperName::class, outputParams: ['param1', 'param2'])]
In this above example, the MyValue
class will map all property names from snake_case
to camelCase
on both input and output, in this case, my_value
to myValue
and my_other_value
to myOtherValue
:
$value = MyValue::from([
'my_value' => 'value',
'my_other_value' => 'other value'
]);
In addition to the mapped names, you can still use the original property names:
$value = MyValue::from([
'myValue' => 'value',
'myOtherValue' => 'other value'
]);
TIP
You can specify a mapper on either the class or property level, or both. If you specify a mapper on both the class and property level, the property-level mapper will take precedence.
Built-in Mappers
Bag comes with a few built-in mappers:
SnakeCase
- Converts property names to/fromsnake_case
CamelCase
- Converts property names to/fromcamelCase
Alias
- Allows you to specify a custom alias for a specific property nameStringable
- Converts property names using a sequence of fluent string helper methods.
Using the Alias Mapper
The Alias
mapper allows you to specify a custom alias for a specific property name.
WARNING
Unlike other mappers, the Alias
mapper must only be applied to individual properties.
In the following example we alias the input name uuid
to the property id
:
use Bag\Bag;
use Bag\Attributes\MapInputName;
use Bag\Mappers\Alias;
class MyValue extends Bag {
public function __construct(
#[MapInputName(Alias::class, 'uuid')])]
public string $id,
) {}
}
Using the Stringable Mapper
The Stringable
mapper allows you to chain any of the fluent string helper methods to convert property names.
The Stringable
mapper accepts any number of transformations. To pass in arguments to a given transformation, use a colon :
followed by a comma-separated list of arguments.
use Bag\Bag;
use Bag\Mappers\Stringable;
#[MapInputName(Stringable::class, 'camel', 'kebab')]
#[MapOutputName(Stringable::class, 'camel', 'kebab')]
class MyValue extends Bag {
public function __construct(
public string $myValue,
public string $myOtherValue
) {}
}
When Mapping Applies
Input mapping is applied when calling Bag::from()
. You can use either the original property name or the mapped name when creating a Bag.
TIP
Validation is applied to the original property name, not the mapped name.
Output mapping is applied when calling $Bag->toArray()
or $Bag->toJson()
(or when using json_encode()
).
Mapping Hierarchy
For input mapping, all mappers are used, allowing multiple mapped names to match to the same property. The last incoming property name that matches will be the value used for the Bag.
The mapping hierarchy is as follows:
- Class Level:
MapName(input)
- Class Level:
MapInputName
- Property Level:
MapName(input)
- Property Level:
MapInputName
- Property Level:
- Property Level:
- Class Level:
For output mapping, only the last mapper is used. The mapping hierarchy is as follows:
- Class Level:
MapName(output)
- Class Level:
MapOutputName
- PropertyLevel Level:
MapName(Output)
- PropertyLevel Level:
MapOutputName
- PropertyLevel Level:
- PropertyLevel Level:
- Class Level:
NOTE
The MapName
and MapOutputName
attributes can only be added once at each level. You can add as many MapInputName
attributes as you like!
Custom Mappers
You can also create your own mappers by implementing the \Bag\Mappers\MapperInterface
interface.
use Bag\Mappers\MapperInterface;
use Illuminate\Support\Str;
class Kebab implements MapperInterface {
public function input(string $name): string {
return Str::of($name)->camel()->kebab();
}
public function output(string $name): string {
return Str::of($name)->camel()->kebab();
}
}
Then specify it in the Mapping attribute:
use \App\Values\Mappers\Kebab;
use Bag\Bag;
use Bag\Mappers\Stringable;
#[MapInputName(Kebab::class)]
#[MapOutputName(Kebab::class)]
class MyValue extends Bag {
public function __construct(
public string $myValue,
public string $myOtherValue
) {}
}