Skip to content

Commit 87ff998

Browse files
committed
Move replication logic to db.replicator
This gives more room for logic and reduces the memory footprint
1 parent d915d72 commit 87ff998

File tree

3 files changed

+161
-87
lines changed

3 files changed

+161
-87
lines changed
Lines changed: 5 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
<?php namespace October\Rain\Database\Concerns;
22

3-
use October\Rain\Support\Arr;
4-
use Illuminate\Database\Eloquent\Model as EloquentModel;
5-
use Illuminate\Database\Eloquent\Collection as CollectionBase;
6-
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
3+
use App;
74

85
/**
96
* HasReplication for a model
@@ -19,7 +16,7 @@ trait HasReplication
1916
*/
2017
public function replicateWithRelations(array $except = null)
2118
{
22-
return $this->replicateRelationsInternal($except);
19+
return App::makeWith('db.replicator', ['model' => $this])->replicate($except);
2320
}
2421

2522
/**
@@ -32,98 +29,20 @@ public function replicateWithRelations(array $except = null)
3229
*/
3330
public function duplicateWithRelations(array $except = null)
3431
{
35-
return $this->replicateRelationsInternal($except, ['isDuplicate' => true]);
32+
return App::makeWith('db.replicator', ['model' => $this])->duplicate($except);
3633
}
3734

3835
/**
39-
* replicateRelationsInternal
36+
* newReplicationInstance returns a new instance used by the replicator
4037
*/
41-
protected function replicateRelationsInternal(array $except = null, array $options = [])
38+
public function newReplicationInstance($attributes)
4239
{
43-
extract(array_merge([
44-
'isDuplicate' => false
45-
], $options));
46-
47-
$defaults = [
48-
$this->getKeyName(),
49-
$this->getCreatedAtColumn(),
50-
$this->getUpdatedAtColumn(),
51-
];
52-
53-
$isMultisite = $this->isClassInstanceOf(\October\Contracts\Database\MultisiteInterface::class);
54-
if ($isMultisite) {
55-
$defaults[] = 'site_root_id';
56-
}
57-
58-
$attributes = Arr::except(
59-
$this->attributes, $except ? array_unique(array_merge($except, $defaults)) : $defaults
60-
);
61-
6240
$instance = $this->newInstance();
6341

6442
$instance->setRawAttributes($attributes);
6543

6644
$instance->fireModelEvent('replicating', false);
6745

68-
$definitions = $this->getRelationDefinitions();
69-
70-
foreach ($definitions as $type => $relations) {
71-
foreach ($relations as $name => $options) {
72-
if ($this->isRelationReplicable($name, $isMultisite, $isDuplicate)) {
73-
$this->replicateRelationInternal($instance->$name(), $this->$name);
74-
}
75-
}
76-
}
77-
7846
return $instance;
7947
}
80-
81-
/**
82-
* replicateRelationInternal on the model instance with the supplied ones
83-
*/
84-
protected function replicateRelationInternal($relationObject, $models)
85-
{
86-
if ($models instanceof CollectionBase) {
87-
$models = $models->all();
88-
}
89-
elseif ($models instanceof EloquentModel) {
90-
$models = [$models];
91-
}
92-
else {
93-
$models = (array) $models;
94-
}
95-
96-
foreach (array_filter($models) as $model) {
97-
if ($relationObject instanceof HasOneOrMany) {
98-
$relationObject->add($model->replicateWithRelations());
99-
}
100-
else {
101-
$relationObject->add($model);
102-
}
103-
}
104-
}
105-
106-
/**
107-
* isRelationReplicable determines whether the specified relation should be replicated
108-
* when replicateWithRelations() is called instead of save() on the model. Default: true.
109-
*/
110-
protected function isRelationReplicable(string $name, bool $isMultisite, bool $isDuplicate): bool
111-
{
112-
$relationType = $this->getRelationType($name);
113-
if ($relationType === 'morphTo') {
114-
return false;
115-
}
116-
117-
// Relation is shared via propagation
118-
if (!$isDuplicate && $isMultisite && $this->isAttributePropagatable($name)) {
119-
return false;
120-
}
121-
122-
$definition = $this->getRelationDefinition($name);
123-
if (!array_key_exists('replicate', $definition)) {
124-
return true;
125-
}
126-
127-
return (bool) $definition['replicate'];
128-
}
12948
}

src/Database/DatabaseServiceProvider.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ protected function registerConnectionServices()
7272
return new DatabaseTransactionsManager;
7373
});
7474

75+
$this->app->bind('db.replicator', Replicator::class);
76+
7577
$this->app->singleton('db.dongle', function ($app) {
7678
return new Dongle($this->getDefaultDatabaseDriver(), $app['db']);
7779
});
@@ -84,6 +86,6 @@ protected function getDefaultDatabaseDriver(): string
8486
{
8587
$defaultConnection = $this->app['db']->getDefaultConnection();
8688

87-
return $this->app['config']['database.connections.' . $defaultConnection . '.driver'];
89+
return $this->app['config']["database.connections.{$defaultConnection}.driver"];
8890
}
8991
}

src/Database/Replicator.php

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php namespace October\Rain\Database;
2+
3+
use October\Rain\Support\Arr;
4+
use Illuminate\Database\Eloquent\Model as EloquentModel;
5+
use Illuminate\Database\Eloquent\Collection as CollectionBase;
6+
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
7+
8+
/**
9+
* Replicator service to duplicating and replicating model records
10+
*/
11+
class Replicator
12+
{
13+
/**
14+
* @var \Model model context
15+
*/
16+
protected $model;
17+
18+
/**
19+
* @var bool isDuplicating the record or just returning a non-existing instance
20+
*/
21+
protected $isDuplicating = false;
22+
23+
/**
24+
* @var bool isMultisite context
25+
*/
26+
protected $isMultisite = false;
27+
28+
/**
29+
* __construct
30+
*/
31+
public function __construct($model)
32+
{
33+
$this->model = $model;
34+
$this->isMultisite = $model->isClassInstanceOf(\October\Contracts\Database\MultisiteInterface::class);
35+
}
36+
37+
/**
38+
* replicate replicates the model into a new, non-existing instance,
39+
* including replicating relations.
40+
*
41+
* @param array|null $except
42+
* @return static
43+
*/
44+
public function replicate(array $except = null)
45+
{
46+
$this->isDuplicating = false;
47+
48+
return $this->replicateRelationsInternal($except);
49+
}
50+
51+
/**
52+
* duplicate replicates a model with special multisite duplication logic.
53+
* To avoid duplication of has many relations, the logic only propagates relations on
54+
* the parent model since they are shared via site_root_id beyond this point.
55+
*
56+
* @param array|null $except
57+
* @return static
58+
*/
59+
public function duplicate(array $except = null)
60+
{
61+
$this->isDuplicating = true;
62+
63+
return $this->replicateRelationsInternal($except);
64+
}
65+
66+
/**
67+
* replicateRelationsInternal
68+
*/
69+
protected function replicateRelationsInternal(array $except = null)
70+
{
71+
$defaults = [
72+
$this->model->getKeyName(),
73+
$this->model->getCreatedAtColumn(),
74+
$this->model->getUpdatedAtColumn(),
75+
];
76+
77+
if ($this->isMultisite) {
78+
$defaults[] = 'site_root_id';
79+
}
80+
81+
$attributes = Arr::except(
82+
$this->model->attributes,
83+
$except ? array_unique(array_merge($except, $defaults)) : $defaults
84+
);
85+
86+
$instance = $this->model->newReplicationInstance($attributes);
87+
88+
$definitions = $this->model->getRelationDefinitions();
89+
90+
foreach ($definitions as $type => $relations) {
91+
foreach ($relations as $name => $options) {
92+
if ($this->isRelationReplicable($name)) {
93+
$this->replicateRelationInternal($instance->$name(), $this->model->$name);
94+
}
95+
}
96+
}
97+
98+
return $instance;
99+
}
100+
101+
/**
102+
* replicateRelationInternal on the model instance with the supplied ones
103+
*/
104+
protected function replicateRelationInternal($relationObject, $models)
105+
{
106+
if ($models instanceof CollectionBase) {
107+
$models = $models->all();
108+
}
109+
elseif ($models instanceof EloquentModel) {
110+
$models = [$models];
111+
}
112+
else {
113+
$models = (array) $models;
114+
}
115+
116+
foreach (array_filter($models) as $model) {
117+
if ($relationObject instanceof HasOneOrMany) {
118+
$relationObject->add($model->replicateWithRelations());
119+
}
120+
else {
121+
$relationObject->add($model);
122+
}
123+
}
124+
}
125+
126+
/**
127+
* isRelationReplicable determines whether the specified relation should be replicated
128+
* when replicateWithRelations() is called instead of save() on the model. Default: true.
129+
*/
130+
protected function isRelationReplicable(string $name): bool
131+
{
132+
$relationType = $this->model->getRelationType($name);
133+
if ($relationType === 'morphTo') {
134+
return false;
135+
}
136+
137+
// Relation is shared via propagation
138+
if (
139+
!$this->isDuplicating &&
140+
$this->isMultisite &&
141+
$this->model->isAttributePropagatable($name)
142+
) {
143+
return false;
144+
}
145+
146+
$definition = $this->model->getRelationDefinition($name);
147+
if (!array_key_exists('replicate', $definition)) {
148+
return true;
149+
}
150+
151+
return (bool) $definition['replicate'];
152+
}
153+
}

0 commit comments

Comments
 (0)