Skip to content

Commit c240495

Browse files
committed
Initial commit
0 parents  commit c240495

File tree

8 files changed

+699
-0
lines changed

8 files changed

+699
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021-present Markus Wetzel
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Laravel Struct
2+
3+
[![Latest Stable Version](https://poser.pugx.org/proai/laravel-struct/v/stable)](https://packagist.org/packages/proai/laravel-struct) [![Total Downloads](https://poser.pugx.org/proai/laravel-struct/downloads)](https://packagist.org/packages/proai/laravel-struct) [![Latest Unstable Version](https://poser.pugx.org/proai/laravel-struct/v/unstable)](https://packagist.org/packages/proai/laravel-struct) [![License](https://poser.pugx.org/proai/laravel-struct/license)](https://packagist.org/packages/proai/laravel-struct)
4+
5+
A struct is a collection of typed variables. Structs are a well known datatype in other programming languages, but unfortunately not natively part of PHP yet. This package aims to bring structs to PHP and in particular to Laravel.
6+
7+
## Installation
8+
9+
You can install the package via composer:
10+
11+
```bash
12+
composer require proai/laravel-struct
13+
```
14+
15+
Please note that you need at least **PHP 7.4** and **Laravel 8** for this package.
16+
17+
## Usage
18+
19+
The package uses named properties, which were introduced in PHP 7.4, to define a struct:
20+
21+
```php
22+
use App\Structs\GeoLocation;
23+
use ProAI\Struct\Struct;
24+
25+
class Address extends Struct
26+
{
27+
public string $street;
28+
29+
public string $city;
30+
31+
public GeoLocation $geo_location;
32+
}
33+
```
34+
35+
You can use all primitive types like `string`, `bool`, `float`, `int`, but also you can type a property as an object. The object can also be another struct, which enables you to nest structs (like `GeoLocation` above).
36+
37+
Structs are instantiated by using an array of values:
38+
39+
```php
40+
$address = new Address([
41+
'street' => 'Baker Street',
42+
'city' => 'London',
43+
'geo_location' => [
44+
'latitude' => 51.52,
45+
'longitude' => -0.1566,
46+
],
47+
]);
48+
```
49+
50+
Properties that are typed as objects will be converted to these objects on instantiation. Thus in the example above an object of `App\Structs\GeoLocation` will be created for the `$geo_location` property.
51+
52+
Properties can be accessed normally:
53+
54+
```php
55+
$address->street
56+
=> "Baker Street"
57+
58+
$address->country
59+
=> App\Structs\GeoLocation { ... }
60+
```
61+
62+
**Hint: Snake cased properties are used to mimic the behaviour of Eloquent attributes.**
63+
64+
### Attribute Casting
65+
66+
You can use attribute casting with structs in your Eloquent models:
67+
68+
```php
69+
use App\Structs\Address;
70+
use Illuminate\Database\Eloquent\Model;
71+
72+
class User extends Model
73+
{
74+
protected $casts = [
75+
'address' => Address::class,
76+
];
77+
}
78+
```
79+
80+
In order to make this work you need to define a `json` column of the specified key, so in this case `address`.
81+
82+
Alternatively you can use the composed struct caster by adding the argument `composed` to compose a struct from multiple columns:
83+
84+
```php
85+
use App\Structs\Address;
86+
use Illuminate\Database\Eloquent\Model;
87+
88+
class User extends Model
89+
{
90+
protected $casts = [
91+
'address' => Address::class.':composed',
92+
];
93+
}
94+
```
95+
96+
The column names must start with the specified key followed by an underscore and the property name. This also works with nested structs. For the example above we would need the following columns:
97+
98+
```
99+
address_street
100+
address_city
101+
address_geo_location_latitude
102+
address_geo_location_longitude
103+
```
104+
105+
Finally you can also write your own custom caster by overwriting the `castUsing` method of the struct like described in the Laravel docs.
106+
107+
### Collections
108+
109+
Sometimes you need an array of structs. For this purpose you can define a struct collection. The struct collection class is inherited from the Laravel collection class, so you can use all methods of a Laravel collection.
110+
111+
```php
112+
use App\Structs\Address;
113+
use ProAI\Struct\Collection;
114+
115+
class AddressCollection extends Collection
116+
{
117+
public $type = Address::class;
118+
}
119+
```
120+
121+
By the way, attribute casting also works with struct collections for `json` columns.
122+
123+
## Support
124+
125+
Bugs and feature requests are tracked on [GitHub](https://github.com/proai/laravel-struct/issues).
126+
127+
## License
128+
129+
This package is released under the [MIT License](LICENSE).

composer.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "proai/laravel-struct",
3+
"description": "A Laravel package for using structs with Laravel including attribute casting.",
4+
"keywords": [
5+
"laravel",
6+
"struct",
7+
"eloquent",
8+
"attribute casting",
9+
"value object"
10+
],
11+
"homepage": "http://github.com/proai/laravel-struct",
12+
"license": "MIT",
13+
"authors": [
14+
{
15+
"name": "Markus J. Wetzel",
16+
"email": "[email protected]"
17+
}
18+
],
19+
"require": {
20+
"php": ">=7.4",
21+
"illuminate/database": "^8.0",
22+
"illuminate/support": "^8.0"
23+
},
24+
"autoload": {
25+
"psr-4": {
26+
"ProAI\\Struct\\": "src/"
27+
}
28+
}
29+
}

src/Casts/CollectionCast.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace ProAI\Struct\Casts;
4+
5+
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
6+
use InvalidArgumentException;
7+
8+
class CollectionCast implements CastsAttributes
9+
{
10+
/**
11+
* The struct collection class name.
12+
*
13+
* @var string
14+
*/
15+
protected $class;
16+
17+
/**
18+
* Create a new collection cast.
19+
*
20+
* @param string $class
21+
* @return void
22+
*/
23+
public function __construct($class)
24+
{
25+
$this->class = $class;
26+
}
27+
28+
/**
29+
* Transform the attribute from the underlying model values.
30+
*
31+
* @param \Illuminate\Database\Eloquent\Model $model
32+
* @param string $key
33+
* @param mixed $value
34+
* @param array $attributes
35+
* @return mixed
36+
*/
37+
public function get($model, $key, $value, $attributes)
38+
{
39+
if (is_null($value)) {
40+
return null;
41+
}
42+
43+
$class = $this->class;
44+
$typeClass = (new $class)->getTypeClass();
45+
46+
$items = array_map(function ($item) use ($typeClass) {
47+
return new $typeClass($typeClass::parseDatabase($item));
48+
}, json_decode($value, true));
49+
50+
return new $class($items);
51+
}
52+
53+
/**
54+
* Transform the attribute to its underlying model values.
55+
*
56+
* @param \Illuminate\Database\Eloquent\Model $model
57+
* @param string $key
58+
* @param mixed $value
59+
* @param array $attributes
60+
* @return mixed
61+
*/
62+
public function set($model, $key, $value, $attributes)
63+
{
64+
$class = $this->class;
65+
66+
if (! $value instanceof $class) {
67+
throw new InvalidArgumentException('The given value is not a struct collection instance, "'.get_class($value).'" given.');
68+
}
69+
70+
$typeClass = $value->getTypeClass();
71+
72+
$value = array_map(function ($item) use ($typeClass) {
73+
return $typeClass::serializeDatabase($item);
74+
}, $value->all());
75+
76+
return json_encode($value);
77+
}
78+
}

src/Casts/ComposedStructCast.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
namespace ProAI\Struct\Casts;
4+
5+
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
6+
use ProAI\Struct\Struct;
7+
use InvalidArgumentException;
8+
9+
class ComposedStructCast implements CastsAttributes
10+
{
11+
/**
12+
* The struct class name.
13+
*
14+
* @var string
15+
*/
16+
protected $class;
17+
18+
/**
19+
* Create a new composed struct cast.
20+
*
21+
* @param string $class
22+
* @return void
23+
*/
24+
public function __construct($class)
25+
{
26+
$this->class = $class;
27+
}
28+
29+
/**
30+
* Transform the attribute from the underlying model values.
31+
*
32+
* @param \Illuminate\Database\Eloquent\Model $model
33+
* @param string $key
34+
* @param mixed $value
35+
* @param array $attributes
36+
* @return mixed
37+
*/
38+
public function get($model, $key, $value, $attributes)
39+
{
40+
$properties = [];
41+
42+
foreach ($attributes as $name => $attribute) {
43+
if (! Str::startsWith($name.'_')) {
44+
continue;
45+
}
46+
47+
$properties[Str::after($name.'_')] = $attribute;
48+
}
49+
50+
$class = $this->class;
51+
52+
return new $class($class::parseDatabase($properties));
53+
}
54+
55+
/**
56+
* Transform the attribute to its underlying model values.
57+
*
58+
* @param \Illuminate\Database\Eloquent\Model $model
59+
* @param string $key
60+
* @param mixed $value
61+
* @param array $attributes
62+
* @return mixed
63+
*/
64+
public function set($model, $key, $value, $attributes)
65+
{
66+
if (! $value instanceof Struct) {
67+
throw new InvalidArgumentException('The given value is not a struct instance, "'.get_class($value).'" given.');
68+
}
69+
70+
$class = $this->class;
71+
72+
$value = $class::serializeDatabase($value);
73+
74+
return $this->composeProperties($key, $value);
75+
}
76+
77+
/**
78+
* Compose properties of struct.
79+
*
80+
* @param array $columns
81+
* @param string $key
82+
* @param mixed $value
83+
* @param array $attributes
84+
* @return mixed
85+
*/
86+
public function composeProperties($prefix, $properties)
87+
{
88+
foreach ($properties as $name => $property) {
89+
$key = $prefix.'_'.$name;
90+
91+
if (! is_array($property)) {
92+
$columns[$key] = $property;
93+
94+
continue;
95+
}
96+
97+
$columns = array_merge(
98+
$this->composeProperties($key, $property),
99+
$columns
100+
);
101+
}
102+
103+
return $columns;
104+
}
105+
}

0 commit comments

Comments
 (0)