Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
cangelis committed Nov 14, 2019
0 parents commit bf54476
Show file tree
Hide file tree
Showing 20 changed files with 1,314 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vendor/
composer.lock
186 changes: 186 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Data models

Data models are the wrapper classes to the JSON strings or php arrays (markup languages in the future). Models simplifies the manipulation and processing workflow for the JSON or array objects.

## Pros

- Avoid undefined index by design
- Dynamic access to the model properties so no need of mapping the class properties with JSON attributes
- IDE auto-completion using `@property` docblock
- Set has many and has one relationships between models
- Ability to assign default values for the attributes so the undefined attributes can be handled reliably
- Ability to add logic into the JSON data in the model
- Cast values to known types such as integer, string, float, boolean
- Cast values to Carbon object to work on date attributes easily
- Ability to implement custom cast types
- Manipulate the object and make it array or serialize to JSON back

## Usage

Imagine you have a JSON data for a blog post looks like this

```
$data = '{
"id": 1,
"author": "Can Gelis",
"created_at": "2019-05-11 22:00:00",
"comments": [
{
"id": 1,
"text": "Hello World!"
},
{
"id": 2,
"text": "What a wonderful world!"
}
],
"settings": {"comments_enable": 1}
}';
```

You can create the models looks like this

```php

use CanGelis\DataModels\JsonModel;
use CanGelis\DataModels\Cast\BooleanCast;
use CanGelis\DataModels\Cast\DateTimeCast;

/**
* Define docblock for ide auto-completion
*
* @property bool $comments_enable
*/
class Settings extends JsonModel {

protected $casts = ['comments_enable' => BooleanCast::class];

protected $defaults = ['comments_enable' => false];

}

/**
* Define docblock for ide auto-completion
*
* @property integer $id
* @property string $text
*/
class Comment extends JsonModel {}

/**
* Define docblock for ide auto-completion
*
* @property integer $id
* @property author $text
* @property Carbon\Carbon $created_at
* @property Settings $settings
* @property CanGelis\DataModels\DataCollection $comments
*/
class Post extends JsonModel {

protected $defaults = ['text' => 'No Text'];

protected $casts = ['created_at' => DateTimeCast::class];

protected $hasMany = ['comments' => Comment::class];

protected $hasOne = ['settings' => Settings::class];

}

```

Use the models

```php

$post = Post::fromString($data); // initialize from JSON String
$post = new Post(json_decode($data, true)); // or use arrays

$post->text // "No Text" in $defaults
$post->foo // returns null which doesn't have default value
$post->created_at // get Carbon object
$post->created_at->addDay(1) // Go to tomorrow
$post->created_at = Carbon::now() // update the creation time

$post->settings->comments_enable // returns true
$post->settings->comments_enable = false // manipulate the object
$post->settings->comments_enable // returns false
$post->settings->editable = false // introduce a new attribute

$post->comments->first() // returns the first comment
$post->comments[1] // get the second comment
foreach ($post->comments as $comment) {} // iterate on comments
$post->comments->add(['id' => 3, 'text' => 'Not too bad']) // add to the collection

$post->toArray() // see as array
$post->jsonSerialize() // serialize to json

/*
{"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}}
*/

```

## Custom Casts

If you prefer to implement more complex value casting logic, data models allow you to implement your custom ones.

Imagine you use Laravel Eloquent and want to cast an in a JSON attribute.

```php

// data = {"id": 1, "user": 1}

class EloquentUserCast extends AbstractCast {

/**
* The value is casted when it is accessed
* So this is a good place to convert the value in the
* JSON into what we'd like to see
*
* @param mixed $value
*
* @return mixed
*/
public function cast($value)
{
if (!$value instanceof User) {
return User::find($value);
}
return $value;
}

/**
* This method is called when the object is serialized back to
* array or JSON
* So this is good place to make the values
* json compatible such as integer, string or bool
*
* @param mixed $value
*
* @return mixed
*/
public function uncast($value)
{
if ($value instanceof User) {
return $value->id;
}
return $value;
}
}

class Post {

protected $casts = ['user' => EloquentUserCast::class];

}

$post->user = User::find(2); // set the Eloquent model directly
$post->user = 2; // set only the id instead
$post->user // returns instance of User
$post->toArray()

['id' => 1, 'user' => 2]

```
28 changes: 28 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "cangelis/data-models",
"description": "Yet another HTML to PDF Converter based on wkhtmltopdf",
"keywords": ["json", "mapper", "data", "dto", "xml"],
"license": "MIT",
"authors": [
{
"name": "Can Geliş",
"email": "[email protected]"
}
],
"require": {
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "^6"
},
"suggest": {
"nesbot/carbon": "Allows you to cast date attributes",
"ext-json": "*"
},
"autoload": {
"psr-0": {
"CanGelis\\": "src/"
}
},
"minimum-stability": "dev"
}
8 changes: 8 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Data models test suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>
31 changes: 31 additions & 0 deletions src/CanGelis/DataModels/Cast/AbstractCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace CanGelis\DataModels\Cast;

abstract class AbstractCast
{
/**
* The value is casted when it is accessed
* So this is a good place to convert a date string into
* a Carbon instance
*
* @param mixed $value
*
* @return mixed
*/
abstract public function cast($value);

/**
* This method is used when the value is set
* So this is good place to make the values
* json compatible such as integer, string or bool
*
* @param mixed $value
*
* @return mixed
*/
public function uncast($value)
{
return $value;
}
}
14 changes: 14 additions & 0 deletions src/CanGelis/DataModels/Cast/BooleanCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace CanGelis\DataModels\Cast;

class BooleanCast extends AbstractCast
{
/**
* @inheritDoc
*/
public function cast($value)
{
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
}
27 changes: 27 additions & 0 deletions src/CanGelis/DataModels/Cast/DateCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace CanGelis\DataModels\Cast;

use Carbon\Carbon;

class DateCast extends AbstractCast
{
/**
* @inheritDoc
*/
public function cast($value)
{
return Carbon::createFromTimeString($value);
}

/**
* @inheritDoc
*/
public function uncast($value)
{
if ($value instanceof Carbon) {
return $value->toDateString();
}
return $value;
}
}
27 changes: 27 additions & 0 deletions src/CanGelis/DataModels/Cast/DateTimeCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace CanGelis\DataModels\Cast;

use Carbon\Carbon;

class DateTimeCast extends AbstractCast
{
/**
* @inheritDoc
*/
public function cast($value)
{
return Carbon::createFromTimeString($value);
}

/**
* @inheritDoc
*/
public function uncast($value)
{
if ($value instanceof Carbon) {
return $value->toDateTimeString();
}
return $value;
}
}
14 changes: 14 additions & 0 deletions src/CanGelis/DataModels/Cast/FloatCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace CanGelis\DataModels\Cast;

class FloatCast extends AbstractCast
{
/**
* @inheritDoc
*/
public function cast($value)
{
return (float) $value;
}
}
14 changes: 14 additions & 0 deletions src/CanGelis/DataModels/Cast/IntegerCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace CanGelis\DataModels\Cast;

class IntegerCast extends AbstractCast
{
/**
* @inheritDoc
*/
public function cast($value)
{
return (int) $value;
}
}
27 changes: 27 additions & 0 deletions src/CanGelis/DataModels/Cast/Iso8601Cast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace CanGelis\DataModels\Cast;

use Carbon\Carbon;

class Iso8601Cast extends AbstractCast
{
/**
* @inheritDoc
*/
public function cast($value)
{
return Carbon::createFromTimeString($value);
}

/**
* @inheritDoc
*/
public function uncast($value)
{
if ($value instanceof Carbon) {
return $value->toIso8601String();
}
return $value;
}
}
Loading

0 comments on commit bf54476

Please sign in to comment.