├── .gitignore
├── .travis.yml
├── phpunit.xml
├── src
└── CanGelis
│ └── DataModels
│ ├── Cast
│ ├── FloatCast.php
│ ├── IntegerCast.php
│ ├── StringCast.php
│ ├── BooleanCast.php
│ ├── DateCast.php
│ ├── Iso8601Cast.php
│ ├── DateTimeCast.php
│ └── AbstractCast.php
│ ├── DataCollection.php
│ ├── JsonModel.php
│ ├── XmlModel.php
│ └── DataModel.php
├── composer.json
├── LICENSE.md
├── tests
├── Json
│ ├── AttributeTest.php
│ ├── HasOneTest.php
│ └── HasManyTest.php
├── DefaultValueTest.php
├── Xml
│ ├── XmlHasManyTest.php
│ ├── XmlAttributeTest.php
│ └── XmlHasOneTest.php
├── ObjectModificationTest.php
└── CastTest.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | composer.lock
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.0
5 | - 7.1
6 | - 7.2
7 | - 7.3
8 |
9 | before_script:
10 | - composer install
11 |
12 | script: vendor/bin/phpunit
13 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/CanGelis/DataModels/Cast/FloatCast.php:
--------------------------------------------------------------------------------
1 | toDateString();
24 | }
25 | return $value;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/CanGelis/DataModels/Cast/Iso8601Cast.php:
--------------------------------------------------------------------------------
1 | toIso8601String();
24 | }
25 | return $value;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/CanGelis/DataModels/Cast/DateTimeCast.php:
--------------------------------------------------------------------------------
1 | toDateTimeString();
24 | }
25 | return $value;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cangelis/data-models",
3 | "description": "Data models is the beautiful way of working with structured data such as JSON and PHP arrays",
4 | "keywords": ["json", "mapper", "data", "dto", "xml", "array"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Can Geliş",
9 | "email": "geliscan@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": ">=7.0"
14 | },
15 | "require-dev": {
16 | "phpunit/phpunit": "^7||^6||^5",
17 | "nesbot/carbon": "^1||^2"
18 | },
19 | "suggest": {
20 | "ext-json": "*",
21 | "ext-simplexml": "*"
22 | },
23 | "autoload": {
24 | "psr-0": {
25 | "CanGelis\\": "src/"
26 | }
27 | },
28 | "minimum-stability": "dev"
29 | }
30 |
--------------------------------------------------------------------------------
/src/CanGelis/DataModels/Cast/AbstractCast.php:
--------------------------------------------------------------------------------
1 | 1]);
15 | $post->title = 'Foo';
16 | $this->assertEquals(json_encode(['id' => 1, 'title' => 'Foo']), (string) $post);
17 | }
18 |
19 | public function testAttributeIsModified()
20 | {
21 | $post = new JsonModel(['id' => 1]);
22 | $post->id = 2;
23 | $this->assertEquals(json_encode(['id' => 2]), (string) $post);
24 | }
25 |
26 | public function testAttributeIsUnset()
27 | {
28 | $post = new JsonModel(['id' => 1]);
29 | unset($post->id);
30 | $this->assertEquals(json_encode([]), (string) $post);
31 | }
32 |
33 | public function testIsset()
34 | {
35 | $post = new JsonModel(['id' => 1]);
36 | $this->assertTrue(isset($post->id));
37 | $this->assertFalse(isset($post->foo));
38 | }
39 | }
--------------------------------------------------------------------------------
/tests/DefaultValueTest.php:
--------------------------------------------------------------------------------
1 | FloatCast::class
18 | ];
19 |
20 | protected $defaults = [
21 | 'author' => 'Can Gelis',
22 | 'rate' => '0.0'
23 | ];
24 | }
25 |
26 | class DefaultValueTest extends TestCase
27 | {
28 | public function testDefaultValueIsReturnedWhenItDoesntExist()
29 | {
30 | $comment = new Comment([]);
31 | $this->assertEquals('Can Gelis', $comment->author);
32 | }
33 |
34 | public function testDefaultValueIsNotReturnedWhenTheValueExists()
35 | {
36 | $comment = new Comment(['author' => 'Foo Bar']);
37 | $this->assertEquals('Foo Bar', $comment->author);
38 | }
39 |
40 | public function testDefaultValueIsNotReturnedWhenTheValuesIsNull()
41 | {
42 | $comment = new Comment(['author' => null]);
43 | $this->assertNull($comment->author);
44 | }
45 |
46 | public function testReturnsNullWhenItIsNotDefault()
47 | {
48 | $comment = new Comment([]);
49 | $this->assertNull($comment->text);
50 | }
51 |
52 | public function testDefaultIsCasted()
53 | {
54 | $comment = new Comment([]);
55 | $this->assertEquals(0.0, $comment->rate);
56 | $this->assertEquals('double', gettype($comment->rate));
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Json/HasOneTest.php:
--------------------------------------------------------------------------------
1 | Settings::class];
24 |
25 | }
26 |
27 | class HasOneTest extends TestCase
28 | {
29 | public function testRelatedModelReturnsAsExpecteWhenInputIsArray()
30 | {
31 | $user = new Team(['settings' => ['foo' => 'bar']]);
32 | $this->assertEquals('bar', $user->settings->foo);
33 | }
34 |
35 | public function testRelatedModelReturnsAsExpecteWhenInputIsADataModel()
36 | {
37 | $user = new Team([]);
38 | $user->settings = new Settings(['foo' => 'bar']);
39 | $this->assertEquals('bar', $user->settings->foo);
40 | }
41 |
42 | public function testRelationIsModified()
43 | {
44 | $team = new Team(['settings' => ['foo' => 'bar']]);
45 | $team->settings->foo = 'baz';
46 | $this->assertEquals(json_encode(['settings' => ['foo' => 'baz']]), (string) $team);
47 | }
48 |
49 | /**
50 | * @expectedException \InvalidArgumentException
51 | */
52 | public function testThrowErrorWhenSetValueIsUnexpected()
53 | {
54 | $user = new Team([]);
55 | $user->settings = 'foo';
56 | }
57 |
58 | public function testRelatedObjectChangeAsExpected()
59 | {
60 | $user = new Team([]);
61 | $user->settings = new Settings(['foo' => 'bar']);
62 | $user->settings->baz = 'bazzer';
63 | $this->assertEquals('bar', $user->settings->foo);
64 | $this->assertEquals('bazzer', $user->settings->baz);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Xml/XmlHasManyTest.php:
--------------------------------------------------------------------------------
1 | XmlPlayer::class];
18 |
19 | }
20 |
21 | class XmlHasManyTest extends TestCase
22 | {
23 | public function testArrayOfInputIsSetAsExpected()
24 | {
25 | $team = XmlTeam::fromString('');
26 | $team->players = [['name' => 'Beckham'], ['name' => 'Zidane']];
27 | $this->assertContains('BeckhamZidane', (string) $team);
28 | $team->players->add(XmlPlayer::fromArray(['name' => 'Raul']));
29 | $this->assertContains('BeckhamZidaneRaul', (string) $team);
30 | }
31 |
32 | public function testAddingHasManyXmlInputWorksAsExpected()
33 | {
34 | $team = XmlTeam::fromString('Beckham');
35 | $this->assertContains('Beckham', (string) $team);
36 | $team->players->add(XmlPlayer::fromArray(['name' => 'Zidane']));
37 | $this->assertContains('BeckhamZidane', (string) $team);
38 | }
39 |
40 | public function testSettingCollection()
41 | {
42 | $team = XmlTeam::fromString('');
43 | $team->players = new DataCollection([XmlPlayer::fromArray(['name' => 'Zidane']), XmlPlayer::fromArray(['name' => 'Beckham'])]);
44 | $this->assertContains('ZidaneBeckham', (string) $team);
45 | }
46 | }
--------------------------------------------------------------------------------
/tests/ObjectModificationTest.php:
--------------------------------------------------------------------------------
1 | toArray();
15 | }
16 | return $value;
17 | }
18 |
19 | public function cast($value)
20 | {
21 | return new DataCollection($value);
22 | }
23 | }
24 |
25 | class Menu extends JsonModel {
26 |
27 | protected $casts = [
28 | 'sub_menus' => DataCollectionCaster::class
29 | ];
30 |
31 | protected $hasOne = [
32 | 'one_menu' => Menu::class
33 | ];
34 |
35 | protected $hasMany = [
36 | 'many_menus' => Menu::class
37 | ];
38 |
39 | }
40 |
41 | class ObjectModificationTest extends TestCase
42 | {
43 | public function testObjectIsModifiedAfterAccessed()
44 | {
45 | $menu = new Menu([
46 | 'id' => 1,
47 | 'sub_menus' => [
48 | new Menu(['id' => 2])
49 | ]
50 | ]);
51 | $menu->sub_menus->add(new Menu(['id' => 3]));
52 | $this->assertEquals(2, $menu->sub_menus->count());
53 | $this->assertEquals(2, $menu->sub_menus[0]->id);
54 | $this->assertEquals(3, $menu->sub_menus[1]->id);
55 | }
56 |
57 | public function testHasOneRelationModificationTakesAffect()
58 | {
59 | $menu = new Menu(['id' => 1, 'one_menu' => ['id' => 2]]);
60 | $menu->one_menu->id = 3;
61 | $this->assertEquals(3, $menu->toArray()['one_menu']['id']);
62 | }
63 |
64 | public function testHasManyRelationModificationTakesAffect()
65 | {
66 | $menu = new Menu(['id' => 1, 'many_menus' => [['id' => 2]]]);
67 | $menu->many_menus->add(new Menu(['id' => 5]));
68 | $menu->many_menus->add(new Menu(['id' => 7]));
69 | $this->assertEquals(3, count($menu->toArray()['many_menus']));
70 | $this->assertEquals(2, $menu->toArray()['many_menus'][0]['id']);
71 | $this->assertEquals(5, $menu->toArray()['many_menus'][1]['id']);
72 | $this->assertEquals(7, $menu->toArray()['many_menus'][2]['id']);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/Xml/XmlAttributeTest.php:
--------------------------------------------------------------------------------
1 | ');
17 | $this->assertEquals('Foo', $post->title);
18 | }
19 |
20 | public function testAttributeCanBeModifiedAsExpected()
21 | {
22 | $post = XmlPost::fromString('');
23 | $this->assertEquals('Foo', $post->title);
24 | $post->title = 'Bar';
25 | $this->assertEquals('', (string) $post);
26 | }
27 |
28 | public function testNewAttributeCanBeAdded()
29 | {
30 | $post = XmlPost::fromString('');
31 | $this->assertEquals('Foo', $post->title);
32 | $post->body = 'Bar';
33 | $this->assertEquals('', (string) $post);
34 | }
35 |
36 | public function testNewChildCanBeAdded()
37 | {
38 | $post = XmlPost::fromString('');
39 | $post->created_by = 'Foo Bar';
40 | $this->assertEquals('Foo Bar', (string) $post);
41 | }
42 |
43 | public function testChildIsModified()
44 | {
45 | $post = XmlPost::fromString('Foo Bar');
46 | $post->created_by = 'Baz Bazzer';
47 | $this->assertEquals('Baz Bazzer', (string) $post);
48 | }
49 |
50 | public function testAttributeCanBeUnset()
51 | {
52 | $post = XmlPost::fromString('');
53 | unset($post->title);
54 | $this->assertEquals('', (string) $post);
55 | }
56 |
57 | public function testChildCanBeUnset()
58 | {
59 | $post = XmlPost::fromString('Bar');
60 | unset($post->name);
61 | $this->assertEquals('', (string) $post);
62 | }
63 |
64 | public function testIsset()
65 | {
66 | $post = XmlPost::fromString('Bar');
67 | $this->assertTrue(isset($post->title));
68 | $this->assertTrue(isset($post->name));
69 | // not defined as an attribute so this should return false
70 | $this->assertFalse(isset($post->bar));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/CanGelis/DataModels/DataCollection.php:
--------------------------------------------------------------------------------
1 | items = $items;
20 | }
21 |
22 | /**
23 | * @inheritDoc
24 | */
25 | public function offsetExists($offset)
26 | {
27 | return isset($this->items[$offset]);
28 | }
29 |
30 | /**
31 | * @inheritDoc
32 | */
33 | public function offsetGet($offset)
34 | {
35 | return $this->items[$offset];
36 | }
37 |
38 | /**
39 | * @inheritDoc
40 | */
41 | public function offsetSet($offset, $value)
42 | {
43 | if (is_null($offset)) {
44 | $this->items[] = $value;
45 | } else {
46 | $this->items[$offset] = $value;
47 | }
48 | }
49 |
50 | /**
51 | * @inheritDoc
52 | */
53 | public function offsetUnset($offset)
54 | {
55 | unset($this->items[$offset]);
56 | }
57 |
58 | /**
59 | * @inheritDoc
60 | */
61 | public function toJson()
62 | {
63 | return json_encode($this->items);
64 | }
65 |
66 | /**
67 | * @inheritDoc
68 | */
69 | public function toArray()
70 | {
71 | return array_map(function ($item) {
72 | if ($item instanceof DataModel) {
73 | return $item->toArray();
74 | }
75 | return $item;
76 | }, $this->items);
77 | }
78 |
79 | /**
80 | * Add an item to the collection.
81 | *
82 | * @param \CanGelis\DataModels\DataModel $item
83 | *
84 | * @return $this
85 | */
86 | public function add(DataModel $item)
87 | {
88 | $this->items[] = $item;
89 |
90 | return $this;
91 | }
92 |
93 | /**
94 | * @inheritDoc
95 | */
96 | public function getIterator()
97 | {
98 | return new \ArrayIterator($this->items);
99 | }
100 |
101 | /**
102 | * @inheritDoc
103 | */
104 | public function count()
105 | {
106 | return count($this->items);
107 | }
108 |
109 | /**
110 | * Get the first item
111 | *
112 | * @param callable|null $callback
113 | * @param mixed $default
114 | *
115 | * @return mixed
116 | */
117 | public function first(callable $callback = null, $default = null)
118 | {
119 | if (is_null($callback)) {
120 | $callback = function ($item) {
121 | return true;
122 | };
123 | }
124 |
125 | foreach ($this->items as $item) {
126 | if ($callback($item)) {
127 | return $item;
128 | }
129 | }
130 |
131 | return $default;
132 | }
133 |
134 | /**
135 | * @inheritDoc
136 | */
137 | public function filter(callable $callback)
138 | {
139 | return array_filter($this->items, $callback);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/tests/Xml/XmlHasOneTest.php:
--------------------------------------------------------------------------------
1 | DetailedXmlSettings::class
18 | ];
19 |
20 | protected $attributes = ['blog_url'];
21 |
22 | }
23 |
24 | class XmlUser extends XmlModel {
25 |
26 | protected $root = 'user';
27 |
28 | protected $hasOne = [
29 | 'settings' => XmlSettings::class,
30 | 'different_settings' => XmlSettings::class
31 | ];
32 | }
33 |
34 | class XmlHasOneTest extends TestCase
35 | {
36 | public function testRelationIsSetWhenInputIsArray()
37 | {
38 | $user = XmlUser::fromString('');
39 | $user->settings = ['url' => 'https://foo.bar'];
40 | $this->assertEquals($user->settings->url, 'https://foo.bar');
41 | $this->assertContains('https://foo.bar', (string)$user);
42 | $user->settings->foo = 'Bar';
43 | $this->assertContains('https://foo.barBar', (string)$user);
44 | }
45 |
46 | public function testRelationIsSetWhenInputIsXmlModel()
47 | {
48 | $user = XmlUser::fromString('');
49 | $settings = XmlSettings::fromString('Can');
50 | $user->settings = $settings;
51 | $this->assertEquals($user->settings->name, 'Can');
52 | $this->assertContains('Can', (string)$user);
53 | $user->settings->surname = 'Gelis';
54 | $this->assertContains('CanGelis', (string)$user);
55 | }
56 |
57 | public function testRelationIsSetWhenInputIsXmlElement()
58 | {
59 | $user = XmlUser::fromString('');
60 | $xmlSettings = new SimpleXMLElement('BarBazzer');
61 | $user->settings = $xmlSettings;
62 | $this->assertEquals($user->settings->foo, 'Bar');
63 | $this->assertContains('BazzerBar', (string)$user);
64 | $user->settings->bazzer = 'Fooer';
65 | $this->assertContains('BazzerBarFooer', (string)$user);
66 | }
67 |
68 | public function testMultipleHasOneRelationships()
69 | {
70 | $user = XmlUser::fromString('');
71 | $user->settings = ['foo' => 'bar'];
72 | $user->settings->detailed_settings = ['baz' => 'bazzer'];
73 | $this->assertContains('barbazzer', (string)$user);
74 | }
75 |
76 | public function testHasOneAttributesAreSetAsExpected()
77 | {
78 | $user = XmlUser::fromString('');
79 | $user->settings = ['blog_url' => 'http://foo.bar', 'foo' => 'bar'];
80 | $this->assertContains('bar', (string) $user);
81 | }
82 |
83 | public function testRelationNameIsUsedForHasOneRelationships()
84 | {
85 | $user = XmlUser::fromString('');
86 | $user->different_settings = ['foo' => 'bar'];
87 | $this->assertContains('bar', (string) $user);
88 | }
89 |
90 | public function testNoDuplicationInTheModifiedRelationship()
91 | {
92 | $user = XmlUser::fromString('bar');
93 | $user->different_settings->baz = "bazzer";
94 | $this->assertContains('barbazzer', (string) $user);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/Json/HasManyTest.php:
--------------------------------------------------------------------------------
1 | Post::class
20 | ];
21 |
22 | }
23 |
24 | class HasManyTest extends TestCase {
25 |
26 | public function testReturnCollectionWhenDataIsArray()
27 | {
28 | $user = new User(['posts' => [['foo' => 'bar'], ['foo' => 'baz']]]);
29 | $this->assertInstanceOf(DataCollection::class, $user->posts);
30 | $this->assertInstanceOf(Post::class, $user->posts->first());
31 | $this->assertEquals(2, $user->posts->count());
32 | }
33 |
34 | public function testRelationsAreModified()
35 | {
36 | $user = new User(['posts' => [['foo' => 'bar']]]);
37 | $user->posts[0]->foo = 'baz';
38 | $this->assertEquals(json_encode(['posts' => [['foo' => 'baz']]]), (string) $user);
39 | }
40 |
41 | public function testReturnEmptyCollectionWhenAttributeDoesNotExist()
42 | {
43 | $user = new User([]);
44 | $this->assertInstanceOf(DataCollection::class, $user->posts);
45 | $this->assertEquals(0, $user->posts->count());
46 | }
47 |
48 | public function testReturnEmptyCollectionWhenAttributeIsNotAnArray()
49 | {
50 | $user = new User(['posts' => null]);
51 | $this->assertInstanceOf(DataCollection::class, $user->posts);
52 | $this->assertEquals(0, $user->posts->count());
53 | }
54 |
55 | public function testArrayValuesIsSetAsExceptedWhenItIsArrayOfArray()
56 | {
57 | $user = new User([]);
58 | $user->posts = [['foo' => 'bar']];
59 | $this->assertInstanceOf(DataCollection::class, $user->posts);
60 | $this->assertEquals('bar', $user->posts->first()->foo);
61 | $this->assertEquals(1, $user->posts->count());
62 | }
63 |
64 | public function testModelValuesAreSetAsExpectedWhenItIsArrayOfObjects()
65 | {
66 | $user = new User([]);
67 | $user->posts = [new Post(['foo' => 'bar'])];
68 | $this->assertInstanceOf(DataCollection::class, $user->posts);
69 | $this->assertEquals('bar', $user->posts->first()->foo);
70 | $this->assertEquals(1, $user->posts->count());
71 | $this->assertEquals(['foo' => 'bar'], $user->toArray()['posts'][0]);
72 | }
73 |
74 | public function testModelValuesAreSetAsExpectedWhenItIsArrayOfMixedTypes()
75 | {
76 | $user = new User([]);
77 | $user->posts = [new Post(['foo' => 'bar']), ['foo' => 'baz']];
78 | $this->assertInstanceOf(DataCollection::class, $user->posts);
79 | $this->assertEquals('bar', $user->posts[0]->foo);
80 | $this->assertEquals('baz', $user->posts[1]->foo);
81 | $this->assertEquals(2, $user->posts->count());
82 | $this->assertEquals(['foo' => 'bar'], $user->toArray()['posts'][0]);
83 | $this->assertEquals(['foo' => 'baz'], $user->toArray()['posts'][1]);
84 | }
85 |
86 | public function testModelValuesAreSetAsExpectedWhenValuesAreProvidedAsCollection()
87 | {
88 | $user = new User([]);
89 | $user->posts = new DataCollection([new Post(['foo' => 'bar']), new Post(['foo' => 'baz'])]);
90 | $this->assertInstanceOf(DataCollection::class, $user->posts);
91 | $this->assertEquals('bar', $user->posts[0]->foo);
92 | $this->assertEquals('baz', $user->posts[1]->foo);
93 | $this->assertEquals(2, $user->posts->count());
94 | $this->assertEquals(['foo' => 'bar'], $user->toArray()['posts'][0]);
95 | $this->assertEquals(['foo' => 'baz'], $user->toArray()['posts'][1]);
96 | }
97 |
98 | public function testCollectionIsAdded()
99 | {
100 | $user = new User([]);
101 | $user->posts = [new Post(['foo' => 'bar'])];
102 | $user->posts->add(new Post(['foo' => 'baz']));
103 | $this->assertEquals('baz', $user->posts[1]->foo);
104 | }
105 |
106 | /**
107 | * @expectedException \InvalidArgumentException
108 | */
109 | public function testHasManyThrowsErrorWhenUnexpectedValueIsProvided()
110 | {
111 | $user = new User([]);
112 | $user->posts = ['foo'];
113 | }
114 |
115 | /**
116 | * @expectedException \InvalidArgumentException
117 | */
118 | public function testHasManyThrowsErrorWhenNoCollectionIsProvided()
119 | {
120 | $user = new User([]);
121 | $user->posts = 'foo';
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/CanGelis/DataModels/JsonModel.php:
--------------------------------------------------------------------------------
1 | data = $data;
20 | }
21 |
22 | /**
23 | * Make an instance from a string
24 | *
25 | * @param string $json
26 | *
27 | * @return static
28 | */
29 | public static function fromString($json)
30 | {
31 | return new static(json_decode($json, true));
32 | }
33 |
34 | /**
35 | * Make an array
36 | *
37 | * @return array
38 | */
39 | public function toArray()
40 | {
41 | $data = $this->data;
42 |
43 | // apply modified relationships
44 | foreach ($this->relations as $relationAttribute => $relation) {
45 | list($relationType, $attribute) = explode("-", $relationAttribute);
46 | $data[$attribute] = $relation->toArray();
47 | }
48 |
49 | foreach ($this->attributeValues as $attribute => $value) {
50 | $data[$attribute] = $this->uncastValue($attribute, $value);
51 | }
52 |
53 | return $data;
54 | }
55 |
56 | /**
57 | * @inheritDoc
58 | */
59 | public function jsonSerialize()
60 | {
61 | return $this->toArray();
62 | }
63 |
64 | /**
65 | * @inheritDoc
66 | */
67 | public function __toString()
68 | {
69 | return $this->toJson();
70 | }
71 |
72 | /**
73 | * @inheritDoc
74 | */
75 | public function toJson()
76 | {
77 | return json_encode($this->toArray());
78 | }
79 |
80 | /**
81 | * @inheritDoc
82 | */
83 | public function __isset($name)
84 | {
85 | return isset($this->data[$name]);
86 | }
87 |
88 | /**
89 | * @inheritDoc
90 | */
91 | public function __unset($name)
92 | {
93 | unset($this->data[$name]);
94 | unset($this->relations[$name]);
95 | unset($this->attributeValues[$name]);
96 | }
97 |
98 | /**
99 | * Make item a data model
100 | *
101 | * @param array|\CanGelis\DataModels\DataModel $item
102 | * @param string $class
103 | *
104 | * @return \CanGelis\DataModels\DataModel
105 | */
106 | protected function getItemAsObject($item, $class)
107 | {
108 | if (is_array($item)) {
109 | return new $class($item);
110 | }
111 |
112 | if (is_object($item) && get_class($item) == $class) {
113 | return $item;
114 | }
115 |
116 | throw new \InvalidArgumentException('Expected array or ' . $class . ' but ' . gettype($item) . ' given');
117 | }
118 |
119 | /**
120 | * @inheritDoc
121 | */
122 | protected function setHasOne($relation, $value)
123 | {
124 | return $this->getItemAsObject($value, $this->hasOne[$relation]);
125 | }
126 |
127 | /**
128 | * @inheritDoc
129 | */
130 | protected function setHasMany($relation, $value)
131 | {
132 | if (is_array($value)) {
133 | $collection = $this->makeCollection([]);
134 | foreach ($value as $item) {
135 | $collection->add($this->getItemAsObject($item, $this->hasMany[$relation]));
136 | }
137 | return $collection;
138 | }
139 |
140 | if ($value instanceof DataCollection) {
141 | return $value;
142 | }
143 |
144 | throw new \InvalidArgumentException('Expected array or DataCollection but ' . gettype($value) . ' given');
145 | }
146 |
147 | /**
148 | * @inheritDoc
149 | */
150 | protected function resolveHasManyRelationship($relation)
151 | {
152 | $items = [];
153 |
154 | if (array_key_exists($relation, $this->data) && is_array($this->data[$relation])) {
155 | $items = $this->data[$relation];
156 | }
157 |
158 | unset($this->data[$relation]);
159 |
160 | return $this->makeCollection(
161 | array_map(function ($item) use ($relation) {
162 | return $this->getItemAsObject($item, $this->hasMany[$relation]);
163 | }, $items)
164 | );
165 | }
166 |
167 | /**
168 | * @inheritDoc
169 | */
170 | protected function resolveHasOneRelationship($relation)
171 | {
172 | if (is_array($this->data[$relation])) {
173 | $model = new $this->hasOne[$relation]($this->data[$relation]);
174 | unset($this->data[$relation]);
175 | return $model;
176 | }
177 |
178 | return null;
179 | }
180 |
181 | /**
182 | * @inheritDoc
183 | */
184 | protected function hasAttribute($attribute)
185 | {
186 | return array_key_exists($attribute, $this->data);
187 | }
188 |
189 | /**
190 | * @inheritDoc
191 | */
192 | protected function getAttribute($attribute)
193 | {
194 | return $this->data[$attribute];
195 | }
196 |
197 | /**
198 | * @inheritDoc
199 | */
200 | protected function onLoadAttribute($attribute)
201 | {
202 | unset($this->data[$attribute]);
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/tests/CastTest.php:
--------------------------------------------------------------------------------
1 | FloatCast::class,
26 | 'age' => IntegerCast::class,
27 | 'has_license' => BooleanCast::class,
28 | 'license_number' => StringCast::class,
29 | 'birth_date' => DateCast::class,
30 | 'created_at' => DateTimeCast::class,
31 | 'updated_at' => Iso8601Cast::class
32 | ];
33 |
34 | }
35 |
36 | class CastTest extends TestCase
37 | {
38 | public function testBoolean()
39 | {
40 | $player = new Player(['has_license' => 'false']);
41 | $this->assertEquals('boolean', gettype($player->has_license));
42 | $this->assertFalse($player->has_license);
43 | $player = new Player(['has_license' => null]);
44 | $this->assertEquals('boolean', gettype($player->has_license));
45 | $this->assertFalse($player->has_license);
46 | $player = new Player(['has_license' => false]);
47 | $this->assertEquals('boolean', gettype($player->has_license));
48 | $this->assertFalse($player->has_license);
49 | $player = new Player(['has_license' => 0]);
50 | $this->assertEquals('boolean', gettype($player->has_license));
51 | $this->assertFalse($player->has_license);
52 | $player = new Player(['has_license' => 'true']);
53 | $this->assertEquals('boolean', gettype($player->has_license));
54 | $this->assertTrue($player->has_license);
55 | $player = new Player(['has_license' => true]);
56 | $this->assertEquals('boolean', gettype($player->has_license));
57 | $this->assertTrue($player->has_license);
58 | $player = new Player(['has_license' => 1]);
59 | $this->assertEquals('boolean', gettype($player->has_license));
60 | $this->assertTrue($player->has_license);
61 | }
62 |
63 | public function testInteger()
64 | {
65 | $player = new Player(['age' => 10]);
66 | $this->assertEquals('integer', gettype($player->age));
67 | $this->assertEquals(10, $player->age);
68 | $player = new Player(['age' => '10']);
69 | $this->assertEquals('integer', gettype($player->age));
70 | $this->assertEquals(10, $player->age);
71 | $player = new Player(['age' => '10.0']);
72 | $this->assertEquals('integer', gettype($player->age));
73 | $this->assertEquals(10, $player->age);
74 | $player = new Player(['age' => 10.0]);
75 | $this->assertEquals('integer', gettype($player->age));
76 | $this->assertEquals(10, $player->age);
77 | }
78 |
79 | public function testString()
80 | {
81 | $player = new Player(['license_number' => 1234]);
82 | $this->assertEquals('string', gettype($player->license_number));
83 | $this->assertEquals('1234', $player->license_number);
84 | $player = new Player(['license_number' => '1234']);
85 | $this->assertEquals('string', gettype($player->license_number));
86 | $this->assertEquals('1234', $player->license_number);
87 | $player = new Player(['license_number' => null]);
88 | $this->assertEquals('string', gettype($player->license_number));
89 | $this->assertEquals('', $player->license_number);
90 | }
91 |
92 | public function testFloat()
93 | {
94 | $player = new Player(['rate' => 10]);
95 | $this->assertEquals('double', gettype($player->rate));
96 | $this->assertEquals(10.0, $player->rate);
97 | $player = new Player(['rate' => '10']);
98 | $this->assertEquals('double', gettype($player->rate));
99 | $this->assertEquals(10.0, $player->rate);
100 | $player = new Player(['rate' => '10.1']);
101 | $this->assertEquals('double', gettype($player->rate));
102 | $this->assertEquals(10.1, $player->rate);
103 | $player = new Player(['rate' => 10.1]);
104 | $this->assertEquals('double', gettype($player->rate));
105 | $this->assertEquals(10.1, $player->rate);
106 | $player = new Player(['rate' => null]);
107 | $this->assertEquals('double', gettype($player->rate));
108 | $this->assertEquals(0.0, $player->rate);
109 | }
110 |
111 | public function testDate()
112 | {
113 | $player = new Player(['birth_date' => '1990-07-18']);
114 | $this->assertEquals(Carbon::class, get_class($player->birth_date));
115 | $player->birth_date = $now = Carbon::now();
116 | $this->assertEquals(Carbon::class, get_class($player->birth_date));
117 | $this->assertEquals($now->year, $player->birth_date->year);
118 | $this->assertEquals($now->month, $player->birth_date->month);
119 | $this->assertEquals($now->day, $player->birth_date->day);
120 | $this->assertEquals($now->toDateString(), $player->toArray()['birth_date']);
121 | }
122 |
123 | public function testDateTime()
124 | {
125 | $player = new Player(['created_at' => '2019-01-03 12:13:14']);
126 | $this->assertEquals(Carbon::class, get_class($player->created_at));
127 | $player->created_at = $now = Carbon::now();
128 | $this->assertEquals(Carbon::class, get_class($player->created_at));
129 | $this->assertEquals($now->year, $player->created_at->year);
130 | $this->assertEquals($now->month, $player->created_at->month);
131 | $this->assertEquals($now->day, $player->created_at->day);
132 | $this->assertEquals($now->toDateTimeString(), $player->toArray()['created_at']);
133 | }
134 |
135 | public function testIso8601()
136 | {
137 | $player = new Player(['updated_at' => '2018-11-11T12:58:27+09:00']);
138 | $this->assertEquals(Carbon::class, get_class($player->updated_at));
139 | $player->updated_at = $now = Carbon::now();
140 | $this->assertEquals(Carbon::class, get_class($player->updated_at));
141 | $this->assertEquals($now->year, $player->updated_at->year);
142 | $this->assertEquals($now->month, $player->updated_at->month);
143 | $this->assertEquals($now->day, $player->updated_at->day);
144 | $this->assertEquals($now->offsetHours, $player->updated_at->offsetHours);
145 | $this->assertEquals($now->toIso8601String(), $player->toArray()['updated_at']);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/CanGelis/DataModels/XmlModel.php:
--------------------------------------------------------------------------------
1 | root;
33 | }
34 |
35 | if (is_null($data)) {
36 | $data = new \SimpleXMLElement('<' . $root . '>' . $root . '>');
37 | }
38 |
39 | $this->data = clone $data;
40 | }
41 |
42 | /**
43 | * Initialize from XML string
44 | *
45 | * @param string $data
46 | * @param string $root
47 | *
48 | * @return static
49 | */
50 | public static function fromString($data, $root = null)
51 | {
52 | return new static(simplexml_load_string($data), $root);
53 | }
54 |
55 | /**
56 | * Make an instance from an array
57 | *
58 | * @param array $data
59 | * @param string $root
60 | *
61 | * @return static
62 | */
63 | public static function fromArray(array $data, $root = null)
64 | {
65 | $instance = new static(null, $root);
66 | foreach ($data as $key => $value) {
67 | $instance->{$key} = $value;
68 | }
69 | return $instance;
70 | }
71 |
72 | /**
73 | * Get the xml element
74 | *
75 | * @return \SimpleXMLElement
76 | */
77 | public function toXMLElement()
78 | {
79 | $xmlElement = clone $this->data;
80 |
81 | // resolve dynamically loaded attribute values
82 | foreach ($this->attributeValues as $attribute => $value) {
83 | $value = $this->uncastValue($attribute, $value);
84 | if (in_array($attribute, $this->attributes)) {
85 | $xmlElement[$attribute] = $value;
86 | } else {
87 | $xmlElement->addChild($attribute, $value);
88 | }
89 | }
90 |
91 | // resolve dynamic has many relations
92 | foreach ($this->relations as $relationAttribute => $value) {
93 | list($relationType, $relation) = explode("-", $relationAttribute);
94 | if ($relationType == 'hasOne') {
95 | static::addChild($xmlElement, $value->toXMLElement());
96 | } else {
97 | $parent = new \SimpleXMLElement('<' . $relation . '>' . $relation . '>');
98 | static::addChild($xmlElement, $parent);
99 | foreach ($value as $xmlModel) {
100 | static::addChild($xmlElement->{$relation}, $xmlModel->toXMLElement());
101 | }
102 | }
103 | }
104 |
105 | return $xmlElement;
106 | }
107 |
108 | /**
109 | * @inheritDoc
110 | */
111 | public function __toString()
112 | {
113 | return $this->toXMLElement()->asXML();
114 | }
115 |
116 | /**
117 | * Add a child to an xml element
118 | *
119 | * @param \SimpleXMLElement $root
120 | * @param \SimpleXMLElement $child
121 | */
122 | public static function addChild(\SimpleXMLElement $root, \SimpleXMLElement $child)
123 | {
124 | $node = $root->addChild($child->getName(), (string) $child);
125 | foreach($child->attributes() as $attr => $value) {
126 | $node->addAttribute($attr, $value);
127 | }
128 | foreach($child->children() as $ch) {
129 | static::addChild($node, $ch);
130 | }
131 | }
132 |
133 | /**
134 | * @inheritDoc
135 | */
136 | public function __unset($name)
137 | {
138 | if (!in_array($name, $this->attributes)) {
139 | unset($this->data->{$name});
140 | } else {
141 | unset($this->data[$name]);
142 | }
143 | unset($this->relations[$name]);
144 | unset($this->attributeValues[$name]);
145 | }
146 |
147 | /**
148 | * @inheritDoc
149 | */
150 | public function __isset($attribute)
151 | {
152 | if (!in_array($attribute, $this->attributes)) {
153 | return isset($this->data->{$attribute});
154 | }
155 |
156 | return isset($this->data[$attribute]);
157 | }
158 |
159 | /**
160 | * @inheritDoc
161 | */
162 | protected function resolveHasOneRelationship($relation)
163 | {
164 | if (isset($this->data->{$relation})) {
165 | $model = new $this->hasOne[$relation]($this->data->{$relation}, $relation);
166 | unset($this->data->{$relation});
167 | return $model;
168 | }
169 |
170 | return null;
171 | }
172 |
173 | /**
174 | * @inheritDoc
175 | */
176 | protected function resolveHasManyRelationship($relation)
177 | {
178 | $items = [];
179 | foreach ($this->data->{$relation}->children() as $child) {
180 | $items[] = new $this->hasMany[$relation]($child, $child->getName());
181 | }
182 |
183 | unset($this->data->{$relation});
184 |
185 | return $this->makeCollection($items);
186 | }
187 |
188 | /**
189 | * @inheritDoc
190 | */
191 | protected function setHasOne($relation, $value)
192 | {
193 | $relatedClass = $this->hasOne[$relation];
194 | if (is_array($value)) {
195 | return $relatedClass::fromArray($value, $relation);
196 | }
197 |
198 | if ($value instanceof XmlModel) {
199 | return $value;
200 | }
201 |
202 | if ($value instanceof \SimpleXMLElement) {
203 | return new $relatedClass($value, $relation);
204 | }
205 | }
206 |
207 | /**
208 | * @inheritDoc
209 | */
210 | protected function setHasMany($relation, $values)
211 | {
212 | unset($this->data->{$relation});
213 | $collection = $this->makeCollection([]);
214 |
215 | foreach ($values as $value) {
216 | $class = $this->hasMany[$relation];
217 |
218 | if (is_array($value)) {
219 | $collection->add($class::fromArray($value));
220 | }
221 |
222 | if ($value instanceof XmlModel) {
223 | $collection->add($value);
224 | }
225 |
226 | if ($value instanceof \SimpleXMLElement) {
227 | $collection->add(new $class($value, $value->getName()));
228 | }
229 | }
230 |
231 | return $collection;
232 | }
233 |
234 | /**
235 | * Get the attribute
236 | *
237 | * @param $attribute
238 | *
239 | * @return mixed|\SimpleXMLElement
240 | */
241 | protected function getAttribute($attribute)
242 | {
243 | if (!in_array($attribute, $this->attributes)) {
244 | return isset($this->data->{$attribute}) ? (string)$this->data->{$attribute} : null;
245 | }
246 |
247 | return isset($this->data[$attribute]) ? (string)$this->data[$attribute] : null;
248 | }
249 |
250 | /**
251 | * @inheritDoc
252 | */
253 | protected function hasAttribute($attribute)
254 | {
255 | if (!in_array($attribute, $this->attributes)) {
256 | return isset($this->data->{$attribute});
257 | }
258 |
259 | return isset($this->data[$attribute]);
260 | }
261 |
262 | /**
263 | * @inheritDoc
264 | */
265 | protected function onLoadAttribute($attribute)
266 | {
267 | if (!in_array($attribute, $this->attributes)) {
268 | unset($this->data->{$attribute});
269 | } else {
270 | unset($this->data[$attribute]);
271 | }
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Data models
2 |
3 | [](https://travis-ci.org/cangelis/data-models)
4 |
5 | Data models is the beautiful way of working with structured data such as JSON, XML and php arrays. They are basically wrapper classes to the JSON and XML strings or php arrays. Models simplify the manipulation and processing workflow of the JSON, XML or php arrays.
6 |
7 | ## Pros
8 |
9 | - Straightforward to get started (this page will tell you all the features)
10 | - Avoid undefined index by design
11 | - Dynamic access to the model properties so no need of mapping the class properties with JSON or XML attributes
12 | - IDE auto-completion using `@property` docblock and make the API usage documented by default
13 | - Has many and has one relationships between models
14 | - Ability to assign default values for the attributes so the undefined attributes can be handled reliably
15 | - Ability to add logic into the data in the model
16 | - Cast values to known types such as integer, string, float, boolean
17 | - Cast values to Carbon object to work on date attributes easily
18 | - Ability to implement custom cast types
19 | - Manipulate and work on the object models instead of arrays and make them array or serialize to JSON back
20 |
21 | ## Install
22 |
23 | composer require cangelis/data-models:^2.0
24 |
25 | ## JSON Usage
26 |
27 | Imagine you have a JSON data for a blog post looks like this
28 |
29 | ```
30 | $data = '{
31 | "id": 1,
32 | "author": "Can Gelis",
33 | "created_at": "2019-05-11 22:00:00",
34 | "comments": [
35 | {
36 | "id": 1,
37 | "text": "Hello World!"
38 | },
39 | {
40 | "id": 2,
41 | "text": "What a wonderful world!"
42 | }
43 | ],
44 | "settings": {"comments_enable": 1}
45 | }';
46 | ```
47 |
48 | You can create the models looks like this
49 |
50 | ```php
51 |
52 | use CanGelis\DataModels\JsonModel;
53 | use CanGelis\DataModels\Cast\BooleanCast;
54 | use CanGelis\DataModels\Cast\DateTimeCast;
55 |
56 | /**
57 | * Define docblock for ide auto-completion
58 | *
59 | * @property bool $comments_enable
60 | */
61 | class Settings extends JsonModel {
62 |
63 | protected $casts = ['comments_enable' => BooleanCast::class];
64 |
65 | protected $defaults = ['comments_enable' => false];
66 |
67 | }
68 |
69 | /**
70 | * Define docblock for ide auto-completion
71 | *
72 | * @property integer $id
73 | * @property string $text
74 | */
75 | class Comment extends JsonModel {}
76 |
77 | /**
78 | * Define docblock for ide auto-completion
79 | *
80 | * @property integer $id
81 | * @property author $text
82 | * @property Carbon\Carbon $created_at
83 | * @property Settings $settings
84 | * @property CanGelis\DataModels\DataCollection $comments
85 | */
86 | class Post extends JsonModel {
87 |
88 | protected $defaults = ['text' => 'No Text'];
89 |
90 | protected $casts = ['created_at' => DateTimeCast::class];
91 |
92 | protected $hasMany = ['comments' => Comment::class];
93 |
94 | protected $hasOne = ['settings' => Settings::class];
95 |
96 | }
97 |
98 | ```
99 |
100 | Use the models
101 |
102 | ```php
103 |
104 | $post = Post::fromString($data); // initialize from JSON String
105 | $post = new Post(json_decode($data, true)); // or use arrays
106 |
107 | $post->text // "No Text" in $defaults
108 | $post->foo // returns null which doesn't have default value
109 | $post->created_at // get Carbon object
110 | $post->created_at->addDay(1) // Go to tomorrow
111 | $post->created_at = Carbon::now() // update the creation time
112 |
113 | $post->settings->comments_enable // returns true
114 | $post->settings->comments_enable = false // manipulate the object
115 | $post->settings->comments_enable // returns false
116 | $post->settings->editable = false // introduce a new attribute
117 |
118 | $post->comments->first() // returns the first comment
119 | $post->comments[1] // get the second comment
120 | foreach ($post->comments as $comment) {} // iterate on comments
121 | $post->comments->add(new Comment(['id' => 3, 'text' => 'Not too bad'])) // add to the collection
122 |
123 | $post->toArray() // see as array
124 | $post->toJson() // serialize to json
125 |
126 | /*
127 | {"id":1,"author":"Can Gelis","created_at":"2019-11-14 16:09:32","comments":[{"id":1,"text":"Hello World!"},{"id":2,"text":"What a wonderful world!"},{"id":3,"text":"Not too bad"}],"settings":{"comments_enable":false,"editable":false}}
128 | */
129 |
130 | ```
131 |
132 | ## XML Usage
133 |
134 | It is pretty straightforward and very similar to JSON models.
135 |
136 | Imagine an XML data:
137 |
138 | ```php
139 | $data = '
140 |
141 | Beckham1975-05-02
142 | Zidane1972-06-23
143 |
144 |
145 | Istanbul
146 | Turkey
147 |
148 | ';
149 | ```
150 |
151 | You can setup a relationship looks like this:
152 |
153 | ```php
154 | use CanGelis\DataModels\XmlModel;
155 | use CanGelis\DataModels\Cast\DateCast;
156 |
157 | class Player extends XmlModel {
158 |
159 | // root tag name
160 | protected $root = 'Player';
161 |
162 | protected $casts = ['BirthDate' => DateCast::class];
163 |
164 | }
165 |
166 | class Address extends Xmlmodel {
167 |
168 | protected $root = 'Address';
169 |
170 | }
171 |
172 | class Team extends XmlModel {
173 |
174 | protected $root = 'Team';
175 |
176 | protected $hasMany = [
177 | 'Players' => Player::class
178 | ];
179 |
180 | protected $hasOne = [
181 | 'TeamLocation' => Address::class
182 | ];
183 |
184 | // the attributes in this array will be
185 | // behave as XML attributes see the example
186 | protected $attributes = ['Color'];
187 |
188 | }
189 | ```
190 |
191 | Once you setup the relationships and your data, you start using the data.
192 |
193 | ```php
194 | $team = Team::fromString($data);
195 |
196 | echo $team->TeamLocation->City; // returns Istanbul
197 | $team->TeamLocation->City = 'Madrid'; // update the city
198 |
199 | echo $team->Players->count(); // number of players
200 | echo $team->Players[0]->Name; // gets first player's name
201 |
202 | echo $team->Color; // gets the Color XML attribute
203 | $team->Color = '#000000'; // update the XML Attribute
204 |
205 | echo get_class($team->Players[0]->BirthDate); // returns Carbon\Carbon
206 | $team->Players->add(Player::fromArray(['Name' => 'Ronaldinho'])); // add a new player
207 |
208 | echo (string) $team; // make an xml string
209 | ```
210 |
211 | The resulting XML will be;
212 |
213 | ```xml
214 |
215 |
216 | Turkey
217 | Madrid
218 |
219 |
220 | Beckham1975-05-02
221 | Zidane1972-06-23
222 | Ronaldinho
223 |
224 |
225 | ```
226 |
227 |
228 | ## Available Casts
229 |
230 | Here are the available casts.
231 |
232 | ```php
233 |
234 | CanGelis\DataModels\Cast\BooleanCast
235 | CanGelis\DataModels\Cast\FloatCast
236 | CanGelis\DataModels\Cast\IntegerCast
237 | CanGelis\DataModels\Cast\StringCast
238 | // these require nesbot/carbon package to work
239 | CanGelis\DataModels\Cast\DateCast
240 | CanGelis\DataModels\Cast\DateTimeCast
241 | CanGelis\DataModels\Cast\Iso8601Cast
242 |
243 | ```
244 |
245 | ## Custom Casts
246 |
247 | If you prefer to implement more complex value casting logic, data models allow you to implement your custom ones.
248 |
249 | Imagine you use Laravel Eloquent and want to cast an in a JSON attribute.
250 |
251 | ```php
252 |
253 | // data = {"id": 1, "user": 1}
254 |
255 | class EloquentUserCast extends AbstractCast {
256 |
257 | /**
258 | * The value is casted when it is accessed
259 | * So this is a good place to convert the value in the
260 | * JSON into what we'd like to see
261 | *
262 | * @param mixed $value
263 | *
264 | * @return mixed
265 | */
266 | public function cast($value)
267 | {
268 | if (!$value instanceof User) {
269 | return User::find($value);
270 | }
271 | return $value;
272 | }
273 |
274 | /**
275 | * This method is called when the object is serialized back to
276 | * array or JSON
277 | * So this is good place to make the values
278 | * json compatible such as integer, string or bool
279 | *
280 | * @param mixed $value
281 | *
282 | * @return mixed
283 | */
284 | public function uncast($value)
285 | {
286 | if ($value instanceof User) {
287 | return $value->id;
288 | }
289 | return $value;
290 | }
291 | }
292 |
293 | class Post {
294 |
295 | protected $casts = ['user' => EloquentUserCast::class];
296 |
297 | }
298 |
299 | $post->user = User::find(2); // set the Eloquent model directly
300 | $post->user = 2; // set only the id instead
301 | $post->user // returns instance of User
302 | $post->toArray()
303 |
304 | ['id' => 1, 'user' => 2]
305 |
306 | ```
307 | ## Contribution
308 |
309 | Feel free to contribute!
--------------------------------------------------------------------------------
/src/CanGelis/DataModels/DataModel.php:
--------------------------------------------------------------------------------
1 | hasMany)) {
60 | return $this->getHasManyValue($attribute);
61 | }
62 |
63 | // resolve has one relationship
64 | if (array_key_exists($attribute, $this->hasOne)) {
65 | return $this->getHasOneValue($attribute);
66 | }
67 |
68 | // return if it was accessed before
69 | if (array_key_exists($attribute, $this->attributeValues)) {
70 | return $this->attributeValues[$attribute];
71 | }
72 |
73 | if ($this->hasAttribute($attribute)) {
74 | return $this->loadAttribute($attribute, $this->getAttribute($attribute));
75 | }
76 |
77 | if (array_key_exists($attribute, $this->getDefaults())) {
78 | return $this->loadAttribute($attribute, $this->getDefaults()[$attribute]);
79 | }
80 |
81 | return $this->loadAttribute($attribute, null);
82 | }
83 |
84 | /**
85 | * Set the value
86 | *
87 | * @param string $attribute
88 | * @param mixed $value
89 | *
90 | * @throws \InvalidArgumentException
91 | */
92 | public function __set($attribute, $value)
93 | {
94 | if (array_key_exists($attribute, $this->hasOne)) {
95 | $this->setHasOneValue($attribute, $value);
96 | } elseif (array_key_exists($attribute, $this->hasMany)) {
97 | $this->setHasManyValue($attribute, $value);
98 | } else {
99 | $this->loadAttribute($attribute, $value);
100 | }
101 | }
102 |
103 | /**
104 | * Load the attribute
105 | *
106 | * @param string $attribute
107 | * @param mixed $value
108 | *
109 | * @return mixed
110 | */
111 | protected function loadAttribute($attribute, $value)
112 | {
113 | $this->attributeValues[$attribute] = $this->castValue($attribute, $value);
114 | // if the value is already in the source data
115 | // it should be unset since we load the value into attributeValues
116 | // to avoid duplication
117 | $this->onLoadAttribute($attribute);
118 |
119 | return $this->attributeValues[$attribute];
120 | }
121 |
122 | /**
123 | * Get has many relationship value
124 | *
125 | * @param mixed $relation
126 | *
127 | * @return \CanGelis\DataModels\DataCollection
128 | */
129 | protected function getHasManyValue($relation)
130 | {
131 | if ($this->isHasManyRelationLoaded($relation)) {
132 | return $this->getLoadedHasManyRelationValue($relation);
133 | }
134 |
135 | return $this->relations['hasMany-' . $relation] = $this->resolveHasManyRelationShip($relation);
136 | }
137 |
138 | /**
139 | * Get the already loaded has many relation value
140 | *
141 | * @param string $relation
142 | *
143 | * @return mixed
144 | */
145 | protected function getLoadedHasManyRelationValue($relation)
146 | {
147 | return $this->relations['hasMany-' . $relation];
148 | }
149 |
150 | /**
151 | * Get the already loaded has one relation value
152 | *
153 | * @param string $relation
154 | *
155 | * @return mixed
156 | */
157 | protected function getLoadedHasOneRelationValue($relation)
158 | {
159 | return $this->relations['hasOne-' . $relation];
160 | }
161 |
162 | /**
163 | * Returns true if the given has many relation is already loaded
164 | *
165 | * @param string $relation
166 | *
167 | * @return bool
168 | */
169 | protected function isHasManyRelationLoaded($relation)
170 | {
171 | return isset($this->relations['hasMany-' . $relation]);
172 | }
173 |
174 | /**
175 | * Returns true if the given has one relation is already loaded
176 | *
177 | * @param string $relation
178 | *
179 | * @return bool
180 | */
181 | protected function isHasOneRelationLoaded($relation)
182 | {
183 | return isset($this->relations['hasOne-' . $relation]);
184 | }
185 |
186 | /**
187 | * Get the has one relationship value
188 | *
189 | * @param mixed $attribute
190 | *
191 | * @return \CanGelis\DataModels\DataModel|null
192 | */
193 | protected function getHasOneValue($attribute)
194 | {
195 | if ($this->isHasOneRelationLoaded($attribute)) {
196 | return $this->getLoadedHasOneRelationValue($attribute);
197 | }
198 |
199 | return $this->relations['hasOne-' . $attribute] = $this->resolveHasOneRelationship($attribute);
200 | }
201 |
202 | /**
203 | * Set has one value
204 | *
205 | * @param string $attribute
206 | * @param array|\CanGelis\DataModels\DataModel $value
207 | */
208 | protected function setHasOneValue($attribute, $value)
209 | {
210 | $this->relations['hasOne-' . $attribute] = $this->setHasOne($attribute, $value);
211 | }
212 |
213 | /**
214 | * Set has many value
215 | *
216 | * @param string $attribute
217 | * @param \CanGelis\DataModels\DataCollection $value
218 | */
219 | protected function setHasManyValue($attribute, $value)
220 | {
221 | $this->relations['hasMany-' . $attribute] = $this->setHasMany($attribute, $value);
222 | }
223 |
224 | /**
225 | * Default values for the attributes that doesn't exist
226 | * in the data, don't hesitate to override this if you have
227 | * more complex defaults logic
228 | *
229 | * @return array
230 | */
231 | protected function getDefaults()
232 | {
233 | return $this->defaults;
234 | }
235 |
236 | /**
237 | * Cast an attribute value
238 | *
239 | * @param string $attribute
240 | * @param string $value
241 | *
242 | * @return mixed
243 | */
244 | protected function castValue($attribute, $value)
245 | {
246 | if (!array_key_exists($attribute, $this->casts)) {
247 | return $value;
248 | }
249 |
250 | /**
251 | * @var \CanGelis\DataModels\Cast\AbstractCast $caster
252 | */
253 | $caster = new $this->casts[$attribute]();
254 |
255 | return $caster->cast($value);
256 | }
257 |
258 | /**
259 | * Revert casted value back to the serialiazable form
260 | *
261 | * @param string $attribute
262 | * @param mixed $value
263 | *
264 | * @return mixed
265 | */
266 | protected function uncastValue($attribute, $value)
267 | {
268 | if (!array_key_exists($attribute, $this->casts)) {
269 | return $value;
270 | }
271 |
272 | /**
273 | * @var \CanGelis\DataModels\Cast\AbstractCast $caster
274 | */
275 | $caster = new $this->casts[$attribute]();
276 |
277 | return $caster->uncast($value);
278 | }
279 |
280 | /**
281 | * Make a new collection
282 | *
283 | * @param array $items
284 | *
285 | * @return \CanGelis\DataModels\DataCollection
286 | */
287 | protected function makeCollection($items)
288 | {
289 | return new DataCollection($items);
290 | }
291 |
292 | /**
293 | * Resolve has many relationship
294 | *
295 | * @param string $relation
296 | *
297 | * @return \CanGelis\DataModels\DataCollection
298 | */
299 | abstract protected function resolveHasManyRelationship($relation);
300 |
301 | /**
302 | * Resolve has one relationship
303 | *
304 | * @param string $relation
305 | *
306 | * @return \CanGelis\DataModels\DataModel
307 | */
308 | abstract protected function resolveHasOneRelationship($relation);
309 |
310 | /**
311 | * Set the has one value
312 | *
313 | * @param string $relation
314 | * @param mixed $value
315 | *
316 | * @return \CanGelis\DataModels\DataModel
317 | */
318 | abstract protected function setHasOne($relation, $value);
319 |
320 | /**
321 | * Set has many relation value
322 | *
323 | * @param string $relation
324 | * @param mixed $value
325 | *
326 | * @return \CanGelis\DataModels\DataCollection
327 | */
328 | abstract protected function setHasMany($relation, $value);
329 |
330 | /**
331 | * Returns true if the attribute exists
332 | *
333 | * @param string $attribute
334 | *
335 | * @return bool
336 | */
337 | abstract protected function hasAttribute($attribute);
338 |
339 | /**
340 | * Get the attribute value
341 | *
342 | * @param $attribute
343 | *
344 | * @return mixed
345 | */
346 | abstract protected function getAttribute($attribute);
347 |
348 | /**
349 | * Called when an attribute is loaded
350 | * When the value is loaded it can be deleted from the
351 | * source data so no duplication will occur during export
352 | *
353 | * @param string $attribute
354 | *
355 | * @return mixed
356 | */
357 | abstract protected function onLoadAttribute($attribute);
358 | }
359 |
--------------------------------------------------------------------------------