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_caseCamelCase- Converts property names to/fromcamelCaseAlias- 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() or Bag::withoutValidation(). 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(), $Bag->toCollection(), 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
) {}
}