Completed
Push — master ( ee3e85...0843f6 )
by Chris
02:40
created

HasMany   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 173
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 12
Bugs 1 Features 2
Metric Value
wmc 16
c 12
b 1
f 2
lcom 1
cbo 3
dl 0
loc 173
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A eagerLoad() 0 13 1
A eager() 0 11 2
A eagerSelf() 0 12 2
A match() 0 11 3
A retrieve() 0 3 1
A associate() 0 11 2
B dissociate() 0 25 4
A purge() 0 9 1
1
<?php
2
namespace Darya\ORM\Relation;
3
4
use Darya\ORM\Record;
5
use Darya\ORM\Relation;
6
7
/**
8
 * Darya's has-many entity relation.
9
 * 
10
 * @author Chris Andrew <[email protected]>
11
 */
12
class HasMany extends Has {
13
	
14
	/**
15
	 * Eagerly load the related models of the given parent instances.
16
	 * 
17
	 * Retrieves the related models without matching them to their parents.
18
	 * 
19
	 * @param array $instances
20
	 * @return array
21
	 */
22
	public function eagerLoad(array $instances)
23
	{
24
		$this->verifyParents($instances);
25
		$ids = static::attributeList($instances, 'id');
26
		
27
		$filter = array_merge($this->filter(), array(
28
			$this->foreignKey => array_unique($ids)
29
		));
30
		
31
		$data = $this->storage()->read($this->target->table(), $filter);
32
		
33
		return $this->target->generate($data);
34
	}
35
	
36
	/**
37
	 * Eagerly load and match the related models for the given parent instances.
38
	 * 
39
	 * Returns the given instances with their related models loaded.
40
	 * 
41
	 * @param array $instances
42
	 * @return array
43
	 */
44
	public function eager(array $instances) {
45
		if ($this->parent instanceof $this->target) {
46
			return $this->eagerSelf($instances);
47
		}
48
		
49
		$related = $this->eagerLoad($instances);
50
		
51
		$instances = $this->match($instances, $related);
52
		
53
		return $instances;
54
	}
55
	
56
	/**
57
	 * Eagerly load the related models from the same table.
58
	 * 
59
	 * This continues to load the same relation recursively.
60
	 * 
61
	 * @param array $instances
62
	 * @return array
63
	 */
64
	protected function eagerSelf(array $instances)
65
	{
66
		$parents = $instances;
67
		
68
		while ($related = $this->eagerLoad($parents)) {
69
			$this->match($parents, $related);
70
			
71
			$parents = $related;
72
		}
73
		
74
		return $instances;
75
	}
76
	
77
	/**
78
	 * Match the given related models to their parent instances.
79
	 * 
80
	 * @param Record[] $instances
81
	 * @param Record[] $related
82
	 * @return Record[]
83
	 */
84
	protected function match(array $instances, array $related) {
85
		$list = $this->adjacencyList($related);
86
		
87
		foreach ($instances as $instance) {
88
			$key = $instance->id();
89
			$value = isset($list[$key]) ? $list[$key] : array();
90
			$instance->relation($this->name)->set($value);
91
		}
92
		
93
		return $instances;
94
	}
95
	
96
	/**
97
	 * Retrieve the related models.
98
	 * 
99
	 * @return Record[]
100
	 */
101
	public function retrieve() {
102
		return $this->all();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->all(); (Darya\ORM\Record[]) is incompatible with the return type of the parent method Darya\ORM\Relation\Has::retrieve of type Darya\ORM\Record|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
103
	}
104
	
105
	/**
106
	 * Associate the given models.
107
	 * 
108
	 * Returns the number of models successfully associated.
109
	 * 
110
	 * TODO: Stop this from assuming an ID on the instances. Somehow. Maybe save
111
	 *       if it doesn't have one yet, or don't use IDs at the risk of saving
112
	 *       more relations than necessary (all of them...).
113
	 * 
114
	 * @param Record[]|Record $instances
115
	 * @return int
116
	 */
117
	public function associate($instances) {
118
		$ids = array();
119
		
120
		foreach (static::arrayify($instances) as $instance) {
121
			$this->replace($instance);
122
			
123
			$ids[] = $instance->id();
124
		}
125
		
126
		return $this->save($ids);
127
	}
128
	
129
	/**
130
	 * Dissociate the given models.
131
	 * 
132
	 * Returns the number of models successfully dissociated.
133
	 * 
134
	 * TODO: Consider constraints
135
	 * 
136
	 * @param Record[]|Record $instances [optional]
137
	 * @return int
138
	 */
139
	public function dissociate($instances = array()) {
140
		$ids = array();
141
		
142
		$successful = 0;
143
		
144
		foreach (static::arrayify($instances) as $instance) {
145
			$this->verify($instance);
146
			$instance->set($this->foreignKey, 0);
147
			
148
			if ($instance->save()) {
149
				$ids[] = $instance->id();
150
				$successful++;
151
			}
152
		}
153
		
154
		$relatedIds = array();
155
		
156
		foreach ($this->related as $related) {
157
			$relatedIds[] = $related->id();
158
		}
159
		
160
		$this->reduce(array_diff($relatedIds, $ids));
161
		
162
		return $successful;
163
	}
164
	
165
	/**
166
	 * Dissociate all currently associated models.
167
	 * 
168
	 * Returns the number of models successfully dissociated.
169
	 * 
170
	 * TODO: Consider constraints
171
	 * 
172
	 * @return int
173
	 */
174
	public function purge() {
175
		$this->related = array();
176
		
177
		return (int) $this->storage()->update($this->target->table(), array(
178
			$this->foreignKey => 0
179
		), array(
180
			$this->foreignKey => $this->parent->get($this->localKey)
181
		));
182
	}
183
	
184
}
185