Skip to content

Commit

Permalink
βœ…πŸ›πŸŽ¨ Add tests, fix bugs, and refactor Traits.
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieutu committed Aug 3, 2017
1 parent 7540199 commit 5976d94
Show file tree
Hide file tree
Showing 16 changed files with 457 additions and 96 deletions.
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"require": {
"php": ">=7.0",
"illuminate/support": "^5.4",
"illuminate/datatabase": "^5.4"
"illuminate/database": "^5.4"
},
"require-dev": {
"phpunit/phpunit": "6.*",
"mockery/mockery": "0.9.*"
"phpunit/phpunit": "~5.7",
"orchestra/testbench": "~3.0"
},
"license": "MIT",
"authors": [
Expand Down
2 changes: 1 addition & 1 deletion src/Contracts/JsonImportable.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ interface JsonImportable
{
public static function importFromJson($objectsToCreate);

public static function getJsonImportableRelations();
public function getJsonImportableRelations($object = []);
}
8 changes: 8 additions & 0 deletions src/Exceptions/UnknownAttributeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace MathieuTu\JsonImport\Exceptions;

class UnknownAttributeException extends \InvalidArgumentException
{

}
55 changes: 55 additions & 0 deletions src/Helpers/JsonExporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace MathieuTu\JsonImport\Helpers;

use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Support\Collection;
use MathieuTu\JsonImport\Contracts\JsonExportable;

class JsonExporter
{
private $exportable;

public function __construct(JsonExportable $exportable)
{
$this->exportable = $exportable;
}

public static function exportToJson(JsonExportable $exportable, $options = 0)
{
return self::exportToCollection($exportable)->toJson($options);
}

public static function exportToCollection(JsonExportable $exportable): Collection
{
$helper = new static($exportable);

return $helper->exportAttributes()->merge($helper->exportRelations());
}

public function exportAttributes(): Collection
{
return collect($this->exportable->getJsonExportableAttributes())
->mapWithKeys(function ($attribute) {
return [$attribute => $this->exportable->$attribute];
});
}

public function exportRelations(): Collection
{
return collect($this->exportable->getJsonExportableRelations())
->mapWithKeys(function ($relationName) {
return [$relationName => $this->exportable->$relationName()];
})->filter(function ($relationObject) {
return $relationObject instanceof HasOneOrMany
&& $relationObject->getRelated() instanceof JsonExportable;
})->map(function (HasOneOrMany $relationObject) {
$export = $relationObject->get()->map(function ($object) {
return self::exportToCollection($object);
});

return $relationObject instanceof HasOne ? $export->first() : $export;
});
}
}
105 changes: 105 additions & 0 deletions src/Helpers/JsonImporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

namespace MathieuTu\JsonImport\Helpers;


use BadMethodCallException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Support\Str;
use MathieuTu\JsonImport\Contracts\JsonImportable;
use MathieuTu\JsonImport\Exceptions\JsonDecodingException;
use MathieuTu\JsonImport\Exceptions\UnknownAttributeException;

class JsonImporter
{
private $importable;

public function __construct(JsonImportable $importable)
{
$this->importable = $importable;
}

public function importFromJson($objects)
{
$objects = $this->convertObjectsToArray($objects);

$objects = $this->wrap($objects);

foreach ($objects as $attributes) {
$object = $this->importAttributes($attributes);

$this->importRelations($object, $attributes);
}
}

protected function convertObjectsToArray($objects): array
{
if (is_string($objects)) {
$objects = json_decode($objects, true);
}

if (json_last_error() !== JSON_ERROR_NONE) {
throw new JsonDecodingException('Invalid json format.');
}
return $objects;
}

protected function wrap($objects): array
{
return is_array(array_values($objects)[0]) ? $objects : [$objects];
}

protected function importAttributes($attributes): JsonImportable
{
return $this->importable instanceof Model ? $object = $this->importable->create($attributes) : $this->importable;
}

protected function importRelations($object, $attributes)
{
foreach ($this->importable->getJsonImportableRelations($attributes) as $relationName) {

$this->importChildrenIfImportable($this->getRelationObject($object, $relationName), $this->wrap($attributes[$relationName]));
}
}

protected function importChildrenIfImportable(HasOneOrMany $relation, array $children)
{
$childClass = $relation->getRelated();

if ($childClass instanceof JsonImportable) {
$children = $this->addParentKeyToChildren($children, $relation);

$childClass->importFromJson($children);
}
}

protected function addParentKeyToChildren(array $children, HasOneOrMany $relation): array
{
return array_map(function ($object) use ($relation) {
$object[$relation->getForeignKeyName()] = $relation->getParentKey();

return $object;
}, $children);
}

protected function getRelationObject($object, $relationName)
{
$relationName = Str::camel($relationName);

try {
$relation = $object->$relationName();

if (!$relation instanceof HasOneOrMany) {
throw new BadMethodCallException();
}

return $relation;
} catch (BadMethodCallException $e) {
throw new UnknownAttributeException('Unknown attribute or relation "' . $relationName . '" in "' . get_class($object) . '".');
}


}

}
33 changes: 7 additions & 26 deletions src/Traits/JsonExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,21 @@

namespace MathieuTu\JsonImport\Traits;

use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Support\Collection;
use MathieuTu\JsonImport\Contracts\JsonExportable;
use MathieuTu\JsonImport\Helpers\RelationsInModelFinder;
use \MathieuTu\JsonImport\Helpers\JsonExporter as ExporterHelper;

trait JsonExporter
{
protected $jsonExportableRelations = [];
protected $jsonExportableAttributes = [];
protected $jsonExportableRelations;
protected $jsonExportableAttributes;

public function exportToJson($options = 0): string
{
return $this->exportToCollection()->toJson($options);
}

public function exportToCollection(): Collection
{
return $this->exportAttributes()->merge($this->exportRelations());
}

public function exportAttributes(): Collection
{
return collect($this->getJsonExportableAttributes())
->mapWithKeys(function ($attribute) {
return [$attribute => $this->$attribute];
});
return ExporterHelper::exportToJson($this, $options);
}

public function getJsonExportableAttributes(): array
Expand All @@ -35,16 +26,6 @@ public function getJsonExportableAttributes(): array
});
}

abstract public function getFillable(): array;

public function exportRelations(): Collection
{
return collect($this->getJsonExportableRelations())
->mapWithKeys(function ($relation) {
return [$relation => $this->$relation->map->exportToCollection()];
});
}

public function getJsonExportableRelations(): array
{
return $this->jsonExportableRelations ?? RelationsInModelFinder::hasOneOrMany($this);
Expand Down
73 changes: 7 additions & 66 deletions src/Traits/JsonImporter.php
Original file line number Diff line number Diff line change
@@ -1,81 +1,22 @@
<?php

namespace MathieuTu\JsonImport\Traits;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use MathieuTu\JsonImport\Contracts\JsonImportable;
use MathieuTu\JsonImport\Exceptions\JsonDecodingException;

use \MathieuTu\JsonImport\Helpers\JsonImporter as ImporterHelper;
trait JsonImporter
{
protected $jsonImportableRelations = [];

public function importFromJson($objects)
{
$objects = $this->convertObjectsToArray($objects);

foreach ($objects as $attributes) {
$object = $this->importAttributes($attributes);

$this->importRelations($object, $attributes);
}
}

protected function convertObjectsToArray($objects): mixed
{
if (is_string($objects)) {
$objects = json_decode($objects, true);
}
protected $jsonImportableRelations;

if (json_last_error() !== JSON_ERROR_NONE) {
throw new JsonDecodingException('Invalid json format.');
}
return $objects;
}

protected function importAttributes($attributes): Model
public static function importFromJson($objects)
{
return $this instanceof Model ? $object = $this->create($attributes) : $this;
}
$importer = new ImporterHelper(new static);

protected function importRelations($object, $attributes)
{
foreach ($this->getJsonImportableRelations($object) as $relationName) {
$this->importChildrenIfImportable($object->$relationName, $attributes[$relationName]);
}
$importer->importFromJson($objects);
}

public function getJsonImportableRelations($object): array
public function getJsonImportableRelations($attributes = []): array
{
return $this->jsonImportableRelations
?? $this->jsonExportableRelations
?? array_diff(array_keys($object), $this->getFillable());
}

abstract public function getFillable(): array;

protected function importChildrenIfImportable(HasOneOrMany $relation, array $children)
{
if (!$relation instanceof HasOneOrMany) {
return;
}

$childClass = $relation->getRelated();

if ($childClass instanceof JsonImportable) {
$children = $this->addParentKeyToChildren($children, $relation);

$childClass->importFromJson($children);
}
}

protected function addParentKeyToChildren(array $children, HasOneOrMany $relation): array
{
foreach ($children as $object) {
$object[$relation->getForeignKeyName()] = $relation->getParentKey();
}

return $children;
?? array_diff(array_keys($attributes), $this->getFillable());
}
}
21 changes: 21 additions & 0 deletions tests/Stubs/Bar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace MathieuTu\JsonImport\Tests\Stubs;

use MathieuTu\JsonImport\Contracts\JsonExportable;
use MathieuTu\JsonImport\Contracts\JsonImportable;
use MathieuTu\JsonImport\Traits\JsonExporter;
use MathieuTu\JsonImport\Traits\JsonImporter;

class Bar extends Model implements JsonExportable, JsonImportable
{
use JsonExporter, JsonImporter;

protected $fillable = ['name', 'foo_id'];

public function baz()
{
return $this->hasOne(Baz::class);
}

}
22 changes: 22 additions & 0 deletions tests/Stubs/Baz.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace MathieuTu\JsonImport\Tests\Stubs;

use MathieuTu\JsonImport\Contracts\JsonExportable;
use MathieuTu\JsonImport\Contracts\JsonImportable;
use MathieuTu\JsonImport\Traits\JsonExporter;
use MathieuTu\JsonImport\Traits\JsonImporter;

class Baz extends Model implements JsonExportable, JsonImportable
{
use JsonExporter, JsonImporter;

protected $fillable = ['name', 'bar_id'];

public function doNots()
{
return $this->hasMany(DoNotExport::class);
}

}

12 changes: 12 additions & 0 deletions tests/Stubs/DoNotExport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
namespace MathieuTu\JsonImport\Tests\Stubs;

use MathieuTu\JsonImport\Contracts\JsonExportable;
use MathieuTu\JsonImport\Traits\JsonExporter;

class DoNotExport extends Model
{
// This one isn't JsonExportable
protected $fillable = ['name', 'baz_id'];

}
Loading

0 comments on commit 5976d94

Please sign in to comment.