Skip to content

Commit

Permalink
Getting recusrive cloning working with functional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
weotch committed Jul 27, 2015
1 parent 8dc3f9c commit 03309df
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 13 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/vendor/
composer.lock
.DS_Store
.DS_Store
node_modules
42 changes: 30 additions & 12 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
{
"name": "bkwld/cloner",
"description": "A trait for Laravel Eloquent models that allows for recursive (via relationships) cloning of a model, including files (via BKWLD/upchuck)",
"require-dev": {
"illuminate/database": "^4.0"
},
"license": "MIT",
"authors": [
{
"name": "Robert Reinhard",
"email": "[email protected]"
}
]
"name": "bkwld/cloner",
"description": "A trait for Laravel Eloquent models that allows for recursive (via relationships) cloning of a model, including files (via BKWLD/upchuck)",
"require": {
"php": ">=5.4.0",
"illuminate/support": "~4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.7",
"illuminate/database": "~4.0"
},
"suggest": {
"bkwld/upchuck": "Required for replicating of files."
},
"autoload": {
"psr-4": {
"Bkwld\\Cloner\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Bkwld\\Cloner\\Stubs\\": "stubs/"
}
},
"license": "MIT",
"authors": [
{
"name": "Robert Reinhard",
"email": "[email protected]"
}
]
}
24 changes: 24 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
var gulp = require('gulp')
, phpunit = require('gulp-phpunit')
;

// PHPunit
gulp.task('phpunit', function() {
var options = {
debug: false
, clear: true
};
gulp
.src('phpunit.xml')
.pipe(phpunit('./vendor/phpunit/phpunit/phpunit', options))
.on('error', function(){})
;
});

// Watch files
gulp.task('watch', function () {
gulp.watch('**/*.php', ['phpunit']);
});

// Default task
gulp.task('default', ['watch']);
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "Cloner",
"version": "1.0.0",
"license": "MIT",
"devDependencies": {
"gulp": "^3.9.0",
"gulp-phpunit": "^0.8.1"
}
}
18 changes: 18 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
5 changes: 5 additions & 0 deletions src/AttachmentAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php namespace Bkwld\Cloner;

interface AttachmentAdapter {

}
41 changes: 41 additions & 0 deletions src/Cloneable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php namespace Bkwld\Cloner;

trait Cloneable {

/**
* Return the list of attributes on this model that should be cloned
*
* @return array
*/
public function getCloneExemptAttributes() {
if (!isset($this->clone_except_attributes)) return [];
return $this->clone_except_attributes;
}

/**
* Return the list of relations on this model that should be cloned
*
* @return array
*/
public function getCloneableRelations() {
if (!isset($this->cloneable_relations)) return [];
return $this->cloneable_relations;
}

/**
* A no-op callback that gets fired when a model is cloning but before it gets
* committed to the database
*
* @return void
*/
public function onCloning() {}

/**
* A no-op callback that gets fired when a model is cloned and saved to the
* database
*
* @return void
*/
public function onCloned() {}

}
95 changes: 95 additions & 0 deletions src/Cloner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php namespace Bkwld\Cloner;

/**
* Core class that traverses a model's relationships and replicates model
* attributes
*/
class Cloner {

/**
* @var AttachmentAdapter
*/
private $attachment_adapter;

/**
* DI
*
* @param AttachmentAdapter $attachment_adapter
*/
public function __construct(
AttachmentAdapter $attachment_adapter = null) {
$this->attachment_adapter = $attachment_adapter;
}

/**
* Clone a model instance and all of it's files and relations
*
* @param Illuminate\Database\Eloquent\Model $model
* @param Illuminate\Database\Eloquent\Relations\Relation $relation
* @return Illuminate\Database\Eloquent\Model The new model instance
*/
public function duplicate($model, $relation = null) {

// Duplicate the model
$clone = $model->replicate($model->getCloneExemptAttributes());
$clone->onCloning();
if ($relation) $relation->save($clone);
else $clone->save();
$clone->onCloned();

// Loop though all of it's cloneable relationshsips and duplicate the
// relationship
foreach($model->getCloneableRelations() as $relation_name) {
$this->duplicateRelation($model, $relation_name, $clone);
}

// Return the duplicated model
return $clone;

}

/**
* Duplicate relationships to the clone
*
* @param Illuminate\Database\Eloquent\Model $model
* @param string $relation_name
* @param Illuminate\Database\Eloquent\Model $clone
* @return void
*/
public function duplicateRelation($model, $relation_name, $clone) {
$relation = call_user_func([$model, $relation_name]);
if (is_a($relation, 'Illuminate\Database\Eloquent\Relations\BelongsToMany')) {
$this->duplicatePivotedRelation($relation, $relation_name, $clone);
} else $this->duplicateDirectRelation($relation, $relation_name, $clone);
}

/**
* Duplicate a many-to-many style relation where we are just attaching the
* relation to the dupe
*
* @param Illuminate\Database\Eloquent\Relations\Relation $relation
* @param string $relation_name
* @param Illuminate\Database\Eloquent\Model $clone
* @return void
*/
public function duplicatePivotedRelation($relation, $relation_name, $clone) {
$relation->get()->each(function($foreign) use ($clone, $relation_name) {
$clone->$relation_name()->attach($foreign);
});
}

/**
* Duplicate a one-to-many style relation where the foreign model is ALSO
* cloned and then associated
*
* @param Illuminate\Database\Eloquent\Relations\Relation $relation
* @param string $relation_name
* @param Illuminate\Database\Eloquent\Model $clone
* @return void
*/
public function duplicateDirectRelation($relation, $relation_name, $clone) {
$relation->get()->each(function($foreign) use ($clone, $relation_name) {
$this->duplicate($foreign, $clone->$relation_name());
});
}
}
19 changes: 19 additions & 0 deletions stubs/Article.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php namespace Bkwld\Cloner\Stubs;

use Bkwld\Cloner\Cloneable as Cloneable;
use Illuminate\Database\Eloquent\Model;

class Article extends Model {
use Cloneable;

public $clone_except_attributes;
public $cloneable_relations = ['photos', 'authors'];

public function photos() {
return $this->hasMany('Bkwld\Cloner\Stubs\Photo');
}

public function authors() {
return $this->belongsToMany('Bkwld\Cloner\Stubs\Author');
}
}
14 changes: 14 additions & 0 deletions stubs/Author.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php namespace Bkwld\Cloner\Stubs;

use Bkwld\Cloner\Cloneable as Cloneable;
use Illuminate\Database\Eloquent\Model;

class Author extends Model {
use Cloneable;

public $clone_except_attributes;

public function articles() {
return $this->belongsToMany('Bkwld\Cloner\Stubs\Article');
}
}
14 changes: 14 additions & 0 deletions stubs/Photo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php namespace Bkwld\Cloner\Stubs;

use Bkwld\Cloner\Cloneable as Cloneable;
use Illuminate\Database\Eloquent\Model;

class Photo extends Model {
use Cloneable;

public $clone_except_attributes;

public function article() {
return $this->belongsTo('Bkwld\Cloner\Stubs\Article');
}
}
3 changes: 3 additions & 0 deletions stubs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Models to run automated tests against.

I went this approach so that I could test the trait.
Loading

0 comments on commit 03309df

Please sign in to comment.