Completed
Pull Request — master (#3)
by James
02:59
created
src/Axolotl/Relationship/BelongsToOne.php 1 patch
Indentation   +106 added lines, -106 removed lines patch added patch discarded remove patch
@@ -13,110 +13,110 @@
 block discarded – undo
13 13
  * @subpackage Axolotl\Relationship
14 14
  */
15 15
 class BelongsToOne extends Root {
16
-	/**
17
-	 * Relationship's primary key name
18
-	 * on related model.
19
-	 *
20
-	 * @var string
21
-	 */
22
-	protected $local_key = '';
23
-
24
-	/**
25
-	 * HasMany constructor.
26
-	 *
27
-	 * @param Model  $model
28
-	 * @param string $class
29
-	 * @param string $type
30
-	 * @param string $local_key
31
-	 */
32
-	public function __construct( Model $model, $class, $type, $local_key ) {
33
-		$this->local_key = $local_key;
34
-
35
-		parent::__construct( $model, $class, $type );
36
-	}
37
-
38
-	/**
39
-	 * {@inheritDoc}
40
-	 *
41
-	 * @param EntityManager $database
42
-	 *
43
-	 * @throws LogicException
44
-	 */
45
-	public function attach_relation( EntityManager $database ) {
46
-		if ( $this->get_model()->is_filling() ) {
47
-			return;
48
-		}
49
-
50
-		$this->get_model()->set_filling( true );
51
-
52
-		$id = $this->make_target_id();
53
-
54
-		if ( $id ) {
55
-			$target = $database->find(
56
-				$this->get_class(),
57
-				$id
58
-			);
59
-
60
-			$this->set_target( $target );
61
-		}
62
-
63
-		$this->get_model()->set_filling( false );
64
-	}
65
-
66
-	/**
67
-	 * {@inheritDoc}
68
-	 *
69
-	 * @return string
70
-	 */
71
-	public function get_sha() {
72
-		return sha1(
73
-			__CLASS__ .
74
-			get_class( $this->model ) .
75
-			$this->class .
76
-			$this->type .
77
-			$this->local_key
78
-		);
79
-	}
80
-
81
-	/**
82
-	 * Get the relationship's local key.
83
-	 *
84
-	 * @return string
85
-	 */
86
-	public function get_local_key() {
87
-		return $this->local_key;
88
-	}
89
-
90
-	/**
91
-	 * Gets the ID value for the target model to search by.
92
-	 *
93
-	 * @return int|false
94
-	 *
95
-	 * @throws LogicException
96
-	 */
97
-	protected function make_target_id() {
98
-		$class = $this->get_class();
99
-
100
-		switch ( $this->get_relationship_type() ) {
101
-			case 'post_post':
102
-				return $this->get_model()
103
-					->get_underlying_wp_object()
104
-					->{$this->local_key};
105
-			case 'post_term':
106
-				$terms = wp_get_post_terms(
107
-					$this->get_model()
108
-						->get_underlying_wp_object()
109
-						->ID,
110
-					$class::get_taxonomy()
111
-				);
112
-
113
-				if ( ! $terms || is_wp_error( $terms ) ) {
114
-					return false;
115
-				}
116
-
117
-				return $terms[0]->term_id;
118
-			default:
119
-				throw new LogicException;
120
-		}
121
-	}
16
+    /**
17
+     * Relationship's primary key name
18
+     * on related model.
19
+     *
20
+     * @var string
21
+     */
22
+    protected $local_key = '';
23
+
24
+    /**
25
+     * HasMany constructor.
26
+     *
27
+     * @param Model  $model
28
+     * @param string $class
29
+     * @param string $type
30
+     * @param string $local_key
31
+     */
32
+    public function __construct( Model $model, $class, $type, $local_key ) {
33
+        $this->local_key = $local_key;
34
+
35
+        parent::__construct( $model, $class, $type );
36
+    }
37
+
38
+    /**
39
+     * {@inheritDoc}
40
+     *
41
+     * @param EntityManager $database
42
+     *
43
+     * @throws LogicException
44
+     */
45
+    public function attach_relation( EntityManager $database ) {
46
+        if ( $this->get_model()->is_filling() ) {
47
+            return;
48
+        }
49
+
50
+        $this->get_model()->set_filling( true );
51
+
52
+        $id = $this->make_target_id();
53
+
54
+        if ( $id ) {
55
+            $target = $database->find(
56
+                $this->get_class(),
57
+                $id
58
+            );
59
+
60
+            $this->set_target( $target );
61
+        }
62
+
63
+        $this->get_model()->set_filling( false );
64
+    }
65
+
66
+    /**
67
+     * {@inheritDoc}
68
+     *
69
+     * @return string
70
+     */
71
+    public function get_sha() {
72
+        return sha1(
73
+            __CLASS__ .
74
+            get_class( $this->model ) .
75
+            $this->class .
76
+            $this->type .
77
+            $this->local_key
78
+        );
79
+    }
80
+
81
+    /**
82
+     * Get the relationship's local key.
83
+     *
84
+     * @return string
85
+     */
86
+    public function get_local_key() {
87
+        return $this->local_key;
88
+    }
89
+
90
+    /**
91
+     * Gets the ID value for the target model to search by.
92
+     *
93
+     * @return int|false
94
+     *
95
+     * @throws LogicException
96
+     */
97
+    protected function make_target_id() {
98
+        $class = $this->get_class();
99
+
100
+        switch ( $this->get_relationship_type() ) {
101
+            case 'post_post':
102
+                return $this->get_model()
103
+                    ->get_underlying_wp_object()
104
+                    ->{$this->local_key};
105
+            case 'post_term':
106
+                $terms = wp_get_post_terms(
107
+                    $this->get_model()
108
+                        ->get_underlying_wp_object()
109
+                        ->ID,
110
+                    $class::get_taxonomy()
111
+                );
112
+
113
+                if ( ! $terms || is_wp_error( $terms ) ) {
114
+                    return false;
115
+                }
116
+
117
+                return $terms[0]->term_id;
118
+            default:
119
+                throw new LogicException;
120
+        }
121
+    }
122 122
 }
Please login to merge, or discard this patch.
src/Axolotl/Relationship/HasMany.php 1 patch
Indentation   +99 added lines, -99 removed lines patch added patch discarded remove patch
@@ -12,113 +12,113 @@
 block discarded – undo
12 12
  * @subpackage Axolotl\Relationship
13 13
  */
14 14
 class HasMany extends Root {
15
-	/**
16
-	 * Relationship's primary key name
17
-	 * on related model.
18
-	 *
19
-	 * @var string
20
-	 */
21
-	protected $foreign_key = '';
15
+    /**
16
+     * Relationship's primary key name
17
+     * on related model.
18
+     *
19
+     * @var string
20
+     */
21
+    protected $foreign_key = '';
22 22
 
23
-	/**
24
-	 * HasMany constructor.
25
-	 *
26
-	 * @param Model  $model
27
-	 * @param string $class
28
-	 * @param string $type
29
-	 * @param string $foreign_key
30
-	 */
31
-	public function __construct( Model $model, $class, $type, $foreign_key ) {
32
-		$this->foreign_key = $foreign_key;
23
+    /**
24
+     * HasMany constructor.
25
+     *
26
+     * @param Model  $model
27
+     * @param string $class
28
+     * @param string $type
29
+     * @param string $foreign_key
30
+     */
31
+    public function __construct( Model $model, $class, $type, $foreign_key ) {
32
+        $this->foreign_key = $foreign_key;
33 33
 
34
-		parent::__construct( $model, $class, $type );
35
-	}
34
+        parent::__construct( $model, $class, $type );
35
+    }
36 36
 
37
-	/**
38
-	 * {@inheritDoc}
39
-	 *
40
-	 * @param EntityManager $database
41
-	 *
42
-	 * @throws LogicException
43
-	 */
44
-	public function attach_relation( EntityManager $database ) {
45
-		if ( $this->get_model()->is_filling() ) {
46
-			return;
47
-		}
37
+    /**
38
+     * {@inheritDoc}
39
+     *
40
+     * @param EntityManager $database
41
+     *
42
+     * @throws LogicException
43
+     */
44
+    public function attach_relation( EntityManager $database ) {
45
+        if ( $this->get_model()->is_filling() ) {
46
+            return;
47
+        }
48 48
 
49
-		$this->get_model()->set_filling( true );
49
+        $this->get_model()->set_filling( true );
50 50
 
51
-		switch ( $this->get_type() ) {
52
-			case 'object':
53
-				$target = $database->find_by(
54
-					$this->get_class(),
55
-					$this->make_target_params()
56
-				);
57
-				break;
58
-			case 'table': // @todo implement
59
-			default:
60
-				throw new LogicException;
61
-		}
51
+        switch ( $this->get_type() ) {
52
+            case 'object':
53
+                $target = $database->find_by(
54
+                    $this->get_class(),
55
+                    $this->make_target_params()
56
+                );
57
+                break;
58
+            case 'table': // @todo implement
59
+            default:
60
+                throw new LogicException;
61
+        }
62 62
 
63
-		$this->set_target( $target );
63
+        $this->set_target( $target );
64 64
 
65
-		$this->get_model()->set_filling( false );
66
-	}
65
+        $this->get_model()->set_filling( false );
66
+    }
67 67
 
68
-	/**
69
-	 * {@inheritDoc}
70
-	 *
71
-	 * @return string
72
-	 */
73
-	public function get_sha() {
74
-		return sha1(
75
-			__CLASS__ .
76
-			get_class( $this->model ) .
77
-			$this->class .
78
-			$this->type .
79
-			$this->foreign_key
80
-		);
81
-	}
68
+    /**
69
+     * {@inheritDoc}
70
+     *
71
+     * @return string
72
+     */
73
+    public function get_sha() {
74
+        return sha1(
75
+            __CLASS__ .
76
+            get_class( $this->model ) .
77
+            $this->class .
78
+            $this->type .
79
+            $this->foreign_key
80
+        );
81
+    }
82 82
 
83
-	/**
84
-	 * Gets the relationship's foreign key.
85
-	 *
86
-	 * @return string
87
-	 */
88
-	public function get_foreign_key() {
89
-		return $this->foreign_key;
90
-	}
83
+    /**
84
+     * Gets the relationship's foreign key.
85
+     *
86
+     * @return string
87
+     */
88
+    public function get_foreign_key() {
89
+        return $this->foreign_key;
90
+    }
91 91
 
92
-	/**
93
-	 * Gets the params required for the EntityManager to find the target models.
94
-	 *
95
-	 * @return array
96
-	 *
97
-	 * @throws LogicException
98
-	 */
99
-	protected function make_target_params() {
100
-		switch ( $this->get_relationship_type() ) {
101
-			case 'post_post':
102
-				return array(
103
-					$this->foreign_key => $this->get_model()->get_primary_id(),
104
-					'nopaging'         => true,
105
-				);
106
-			case 'post_term':
107
-				return array();
108
-			case 'term_post':
109
-				$model = $this->get_model();
110
-				return array(
111
-					'nopaging'  => true,
112
-					'tax_query' => array(
113
-						array(
114
-							'taxonomy' => $model::get_taxonomy(),
115
-							'field'    => 'term_id',
116
-							'terms'    => $model->get_primary_id(),
117
-						),
118
-					),
119
-				);
120
-			default:
121
-				throw new LogicException;
122
-		}
123
-	}
92
+    /**
93
+     * Gets the params required for the EntityManager to find the target models.
94
+     *
95
+     * @return array
96
+     *
97
+     * @throws LogicException
98
+     */
99
+    protected function make_target_params() {
100
+        switch ( $this->get_relationship_type() ) {
101
+            case 'post_post':
102
+                return array(
103
+                    $this->foreign_key => $this->get_model()->get_primary_id(),
104
+                    'nopaging'         => true,
105
+                );
106
+            case 'post_term':
107
+                return array();
108
+            case 'term_post':
109
+                $model = $this->get_model();
110
+                return array(
111
+                    'nopaging'  => true,
112
+                    'tax_query' => array(
113
+                        array(
114
+                            'taxonomy' => $model::get_taxonomy(),
115
+                            'field'    => 'term_id',
116
+                            'terms'    => $model->get_primary_id(),
117
+                        ),
118
+                    ),
119
+                );
120
+            default:
121
+                throw new LogicException;
122
+        }
123
+    }
124 124
 }
Please login to merge, or discard this patch.
src/Axolotl/Relationship/Root.php 1 patch
Indentation   +122 added lines, -122 removed lines patch added patch discarded remove patch
@@ -18,126 +18,126 @@
 block discarded – undo
18 18
  * @subpackage Axolotl\Relationship
19 19
  */
20 20
 abstract class Root {
21
-	/**
22
-	 * Model the relationship is on.
23
-	 *
24
-	 * @var Model
25
-	 */
26
-	protected $model;
27
-
28
-	/**
29
-	 * Class name the Model is related to.
30
-	 *
31
-	 * @var string
32
-	 */
33
-	protected $class;
34
-
35
-	/**
36
-	 * Whether the Relationship is by object or model.
37
-	 *
38
-	 * @var string
39
-	 */
40
-	protected $type;
41
-
42
-	/**
43
-	 * Root relationship constructor.
44
-	 *
45
-	 * @param Model  $model
46
-	 * @param string $class
47
-	 * @param string $type
48
-	 */
49
-	public function __construct( Model $model, $class, $type ) {
50
-		$this->model = $model;
51
-		$this->class = $class;
52
-		$this->type  = $type;
53
-	}
54
-
55
-	/**
56
-	 * Returns the relationship's Model.
57
-	 *
58
-	 * @return Model
59
-	 */
60
-	public function get_model() {
61
-		return $this->model;
62
-	}
63
-
64
-	/**
65
-	 * Returns the relationship's related class.
66
-	 *
67
-	 * @return string
68
-	 */
69
-	public function get_class() {
70
-		return $this->class;
71
-	}
72
-
73
-	/**
74
-	 * Returns whether the relation is on the object or table.
75
-	 *
76
-	 * @return string
77
-	 */
78
-	public function get_type() {
79
-		return $this->type;
80
-	}
81
-
82
-	/**
83
-	 * Sets the relationship target on the model.
84
-	 *
85
-	 * @param Model|Collection $target
86
-	 */
87
-	public function set_target( $target ) {
88
-		$this->get_model()->set_related( $this->get_sha(), $target );
89
-	}
90
-
91
-	/**
92
-	 * Returns the relationship type between source and destination Model.
93
-	 *
94
-	 * @return string {source}_{dest}
95
-	 */
96
-	public function get_relationship_type() {
97
-		$relationship = '';
98
-
99
-		if ( $this->get_model() instanceof UsesWordPressPost ) {
100
-			$relationship .= 'post';
101
-		} elseif ( $this->get_model() instanceof UsesWordPressTerm ) {
102
-			$relationship .= 'term';
103
-		} elseif ( $this->get_model() instanceof UsesCustomTable ) {
104
-			$relationship .= 'table';
105
-		}
106
-
107
-		$relationship .= '_';
108
-
109
-		if ( is_subclass_of(
110
-			$this->get_class(),
111
-			'Intraxia\Jaxion\Contract\Axolotl\UsesWordPressPost'
112
-		) ) {
113
-			$relationship .= 'post';
114
-		} elseif ( is_subclass_of(
115
-			$this->get_class(),
116
-			'Intraxia\Jaxion\Contract\Axolotl\UsesWordPressTerm'
117
-		) ) {
118
-			$relationship .= 'term';
119
-		} elseif ( is_subclass_of(
120
-			$this->get_class(),
121
-			'Intraxia\Jaxion\Contract\Axolotl\UsesCustomTable'
122
-		) ) {
123
-			$relationship .= 'table';
124
-		}
125
-
126
-		return $relationship;
127
-	}
128
-
129
-	/**
130
-	 * Returns the query args to
131
-	 * fetch the provided relation.
132
-	 *
133
-	 * @param EntityManager $database
134
-	 */
135
-	abstract public function attach_relation( EntityManager $database );
136
-
137
-	/**
138
-	 * Generate the sha for the relation.
139
-	 *
140
-	 * @return string
141
-	 */
142
-	abstract public function get_sha();
21
+    /**
22
+     * Model the relationship is on.
23
+     *
24
+     * @var Model
25
+     */
26
+    protected $model;
27
+
28
+    /**
29
+     * Class name the Model is related to.
30
+     *
31
+     * @var string
32
+     */
33
+    protected $class;
34
+
35
+    /**
36
+     * Whether the Relationship is by object or model.
37
+     *
38
+     * @var string
39
+     */
40
+    protected $type;
41
+
42
+    /**
43
+     * Root relationship constructor.
44
+     *
45
+     * @param Model  $model
46
+     * @param string $class
47
+     * @param string $type
48
+     */
49
+    public function __construct( Model $model, $class, $type ) {
50
+        $this->model = $model;
51
+        $this->class = $class;
52
+        $this->type  = $type;
53
+    }
54
+
55
+    /**
56
+     * Returns the relationship's Model.
57
+     *
58
+     * @return Model
59
+     */
60
+    public function get_model() {
61
+        return $this->model;
62
+    }
63
+
64
+    /**
65
+     * Returns the relationship's related class.
66
+     *
67
+     * @return string
68
+     */
69
+    public function get_class() {
70
+        return $this->class;
71
+    }
72
+
73
+    /**
74
+     * Returns whether the relation is on the object or table.
75
+     *
76
+     * @return string
77
+     */
78
+    public function get_type() {
79
+        return $this->type;
80
+    }
81
+
82
+    /**
83
+     * Sets the relationship target on the model.
84
+     *
85
+     * @param Model|Collection $target
86
+     */
87
+    public function set_target( $target ) {
88
+        $this->get_model()->set_related( $this->get_sha(), $target );
89
+    }
90
+
91
+    /**
92
+     * Returns the relationship type between source and destination Model.
93
+     *
94
+     * @return string {source}_{dest}
95
+     */
96
+    public function get_relationship_type() {
97
+        $relationship = '';
98
+
99
+        if ( $this->get_model() instanceof UsesWordPressPost ) {
100
+            $relationship .= 'post';
101
+        } elseif ( $this->get_model() instanceof UsesWordPressTerm ) {
102
+            $relationship .= 'term';
103
+        } elseif ( $this->get_model() instanceof UsesCustomTable ) {
104
+            $relationship .= 'table';
105
+        }
106
+
107
+        $relationship .= '_';
108
+
109
+        if ( is_subclass_of(
110
+            $this->get_class(),
111
+            'Intraxia\Jaxion\Contract\Axolotl\UsesWordPressPost'
112
+        ) ) {
113
+            $relationship .= 'post';
114
+        } elseif ( is_subclass_of(
115
+            $this->get_class(),
116
+            'Intraxia\Jaxion\Contract\Axolotl\UsesWordPressTerm'
117
+        ) ) {
118
+            $relationship .= 'term';
119
+        } elseif ( is_subclass_of(
120
+            $this->get_class(),
121
+            'Intraxia\Jaxion\Contract\Axolotl\UsesCustomTable'
122
+        ) ) {
123
+            $relationship .= 'table';
124
+        }
125
+
126
+        return $relationship;
127
+    }
128
+
129
+    /**
130
+     * Returns the query args to
131
+     * fetch the provided relation.
132
+     *
133
+     * @param EntityManager $database
134
+     */
135
+    abstract public function attach_relation( EntityManager $database );
136
+
137
+    /**
138
+     * Generate the sha for the relation.
139
+     *
140
+     * @return string
141
+     */
142
+    abstract public function get_sha();
143 143
 }
Please login to merge, or discard this patch.
src/Axolotl/EntityManager.php 1 patch
Indentation   +241 added lines, -241 removed lines patch added patch discarded remove patch
@@ -22,245 +22,245 @@
 block discarded – undo
22 22
  * @subpackage Axolotl
23 23
  */
24 24
 class EntityManager implements EntityManagerContract {
25
-	/**
26
-	 * Post meta prefix.
27
-	 *
28
-	 * @var string
29
-	 */
30
-	protected $prefix;
31
-
32
-	/**
33
-	 * WP_Query instance.
34
-	 *
35
-	 * @var WP_Query
36
-	 */
37
-	protected $main;
38
-
39
-	/**
40
-	 * Global WPDB instance.
41
-	 *
42
-	 * @var wpdb
43
-	 */
44
-	protected $wpdb;
45
-
46
-	/**
47
-	 * EntityManager constructor.
48
-	 *
49
-	 * @param WP_Query $main
50
-	 * @param string   $prefix Post meta prefix.
51
-	 */
52
-	public function __construct( WP_Query $main, $prefix ) {
53
-		global $wpdb;
54
-
55
-		$this->wpdb   = $wpdb;
56
-		$this->main   = $main;
57
-		$this->prefix = $prefix;
58
-	}
59
-
60
-	/**
61
-	 * {@inheritDoc}
62
-	 *
63
-	 * @param string $class
64
-	 * @param int    $id
65
-	 *
66
-	 * @return Model|WP_Error
67
-	 *
68
-	 * @throws LogicException
69
-	 */
70
-	public function find( $class, $id ) {
71
-		$repository = $this->get_repository( $class );
72
-		$model      = $repository->find( $id );
73
-
74
-		if ( is_wp_error( $model ) ) {
75
-			return $model;
76
-		}
77
-
78
-		$this->handle_model( $repository, $model );
79
-
80
-		return $model;
81
-	}
82
-
83
-	/**
84
-	 * {@inheritDoc}
85
-	 *
86
-	 * @param string $class
87
-	 * @param array  $params
88
-	 *
89
-	 * @return Collection
90
-	 *
91
-	 * @throws LogicException
92
-	 */
93
-	public function find_by( $class, $params = array() ) {
94
-		$repository = $this->get_repository( $class );
95
-		$collection = $repository->find_by( $params );
96
-
97
-		foreach ( $collection as $model ) {
98
-			$this->handle_model( $repository, $model );
99
-		}
100
-
101
-		return $collection;
102
-	}
103
-
104
-	/**
105
-	 * {@inheritDoc}
106
-	 *
107
-	 * @param string $class
108
-	 * @param array  $data
109
-	 *
110
-	 * @return Model|WP_Error
111
-	 */
112
-	public function create( $class, $data = array() ) {
113
-		$repository = $this->get_repository( $class );
114
-		$model      = $repository->create( $data );
115
-
116
-		$this->handle_model( $repository, $model );
117
-
118
-		return $model;
119
-	}
120
-
121
-	/**
122
-	 * {@inheritDoc}
123
-	 *
124
-	 * @param Model $model
125
-	 *
126
-	 * @return Model|WP_Error
127
-	 */
128
-	public function persist( Model $model ) {
129
-		return $this->get_repository( get_class( $model ) )->persist( $model );
130
-	}
131
-
132
-	/**
133
-	 * {@inheritDoc}
134
-	 *
135
-	 * @param Model $model
136
-	 * @param bool  $force
137
-	 *
138
-	 * @return Model|WP_Error
139
-	 */
140
-	public function delete( Model $model, $force = false ) {
141
-		return $this->get_repository( get_class( $model ) )->delete( $model, $force );
142
-	}
143
-
144
-	/**
145
-	 * {@inheritDoc}
146
-	 */
147
-	public function free() {
148
-		AbstractRepository::free();
149
-	}
150
-
151
-	/**
152
-	 * Get the EntityManager prefix.
153
-	 *
154
-	 * @return string
155
-	 */
156
-	public function get_prefix() {
157
-		return $this->prefix;
158
-	}
159
-
160
-	/**
161
-	 * Get the main WP_Query instance.
162
-	 *
163
-	 * @return WP_Query
164
-	 */
165
-	public function get_main_query() {
166
-		return $this->main;
167
-	}
168
-
169
-	/**
170
-	 * Get the wpdb connection instance.
171
-	 *
172
-	 * @return wpdb
173
-	 */
174
-	public function get_wpdb() {
175
-		return $this->wpdb;
176
-	}
177
-
178
-	/**
179
-	 * Retrieves the repository for the given class.
180
-	 *
181
-	 * @param string $class
182
-	 *
183
-	 * @return Repository\AbstractRepository
184
-	 *
185
-	 * @throws LogicException
186
-	 */
187
-	protected function get_repository( $class ) {
188
-		// We can only use Axolotl models.
189
-		if ( ! is_subclass_of( $class, 'Intraxia\Jaxion\Axolotl\Model' ) ) {
190
-			throw new LogicException;
191
-		}
192
-
193
-		if ( is_subclass_of(
194
-			$class,
195
-			'Intraxia\Jaxion\Contract\Axolotl\UsesWordPressPost'
196
-		) ) {
197
-			return new WordPressPostRepository( $this, $class );
198
-		}
199
-
200
-		if ( is_subclass_of(
201
-			$class,
202
-			'Intraxia\Jaxion\Contract\Axolotl\UsesWordPressTerm'
203
-		) ) {
204
-			return new WordPressTermRepository( $this, $class );
205
-		}
206
-
207
-		if ( is_subclass_of(
208
-			$class,
209
-			'Intraxia\Jaxion\Contract\Axolotl\UsesCustomTable'
210
-		) ) {
211
-			throw new LogicException;
212
-		}
213
-
214
-		// If a model doesn't have a backing data source,
215
-		// the developer needs to fix this immediately.
216
-		throw new LogicException;
217
-	}
218
-
219
-	/**
220
-	 * Ensures the model is registered with the model and fills its relations.
221
-	 *
222
-	 * @param AbstractRepository $repository
223
-	 * @param Model              $model
224
-	 */
225
-	protected function handle_model( AbstractRepository $repository, Model $model ) {
226
-		$repository->register_model( $model );
227
-
228
-		if ( $model instanceof HasEagerRelationships ) {
229
-			$this->fill_related( $model, $model::get_eager_relationships() );
230
-		}
231
-	}
232
-
233
-	/**
234
-	 * Fills the Model with the provided relations.
235
-	 *
236
-	 * If no relations are provided, all relations are filled.
237
-	 *
238
-	 * @param Model $model
239
-	 * @param array $relations
240
-	 *
241
-	 * @throws LogicException
242
-	 */
243
-	protected function fill_related( Model $model, array $relations = array() ) {
244
-		if ( ! $relations ) {
245
-			$relations = $model->get_related_keys();
246
-		}
247
-
248
-		foreach ( $relations as $relation ) {
249
-			if ( ! in_array( $relation, $model->get_related_keys() ) ) {
250
-				throw new LogicException;
251
-			}
252
-
253
-			if ( $model->relation_is_filled( $relation ) ) {
254
-				continue;
255
-			}
256
-
257
-			/**
258
-			 * Model relationship.
259
-			 *
260
-			 * @var Relationship $relation
261
-			 */
262
-			$relation = $model->{"related_{$relation}"}();
263
-			$relation->attach_relation( $this );
264
-		}
265
-	}
25
+    /**
26
+     * Post meta prefix.
27
+     *
28
+     * @var string
29
+     */
30
+    protected $prefix;
31
+
32
+    /**
33
+     * WP_Query instance.
34
+     *
35
+     * @var WP_Query
36
+     */
37
+    protected $main;
38
+
39
+    /**
40
+     * Global WPDB instance.
41
+     *
42
+     * @var wpdb
43
+     */
44
+    protected $wpdb;
45
+
46
+    /**
47
+     * EntityManager constructor.
48
+     *
49
+     * @param WP_Query $main
50
+     * @param string   $prefix Post meta prefix.
51
+     */
52
+    public function __construct( WP_Query $main, $prefix ) {
53
+        global $wpdb;
54
+
55
+        $this->wpdb   = $wpdb;
56
+        $this->main   = $main;
57
+        $this->prefix = $prefix;
58
+    }
59
+
60
+    /**
61
+     * {@inheritDoc}
62
+     *
63
+     * @param string $class
64
+     * @param int    $id
65
+     *
66
+     * @return Model|WP_Error
67
+     *
68
+     * @throws LogicException
69
+     */
70
+    public function find( $class, $id ) {
71
+        $repository = $this->get_repository( $class );
72
+        $model      = $repository->find( $id );
73
+
74
+        if ( is_wp_error( $model ) ) {
75
+            return $model;
76
+        }
77
+
78
+        $this->handle_model( $repository, $model );
79
+
80
+        return $model;
81
+    }
82
+
83
+    /**
84
+     * {@inheritDoc}
85
+     *
86
+     * @param string $class
87
+     * @param array  $params
88
+     *
89
+     * @return Collection
90
+     *
91
+     * @throws LogicException
92
+     */
93
+    public function find_by( $class, $params = array() ) {
94
+        $repository = $this->get_repository( $class );
95
+        $collection = $repository->find_by( $params );
96
+
97
+        foreach ( $collection as $model ) {
98
+            $this->handle_model( $repository, $model );
99
+        }
100
+
101
+        return $collection;
102
+    }
103
+
104
+    /**
105
+     * {@inheritDoc}
106
+     *
107
+     * @param string $class
108
+     * @param array  $data
109
+     *
110
+     * @return Model|WP_Error
111
+     */
112
+    public function create( $class, $data = array() ) {
113
+        $repository = $this->get_repository( $class );
114
+        $model      = $repository->create( $data );
115
+
116
+        $this->handle_model( $repository, $model );
117
+
118
+        return $model;
119
+    }
120
+
121
+    /**
122
+     * {@inheritDoc}
123
+     *
124
+     * @param Model $model
125
+     *
126
+     * @return Model|WP_Error
127
+     */
128
+    public function persist( Model $model ) {
129
+        return $this->get_repository( get_class( $model ) )->persist( $model );
130
+    }
131
+
132
+    /**
133
+     * {@inheritDoc}
134
+     *
135
+     * @param Model $model
136
+     * @param bool  $force
137
+     *
138
+     * @return Model|WP_Error
139
+     */
140
+    public function delete( Model $model, $force = false ) {
141
+        return $this->get_repository( get_class( $model ) )->delete( $model, $force );
142
+    }
143
+
144
+    /**
145
+     * {@inheritDoc}
146
+     */
147
+    public function free() {
148
+        AbstractRepository::free();
149
+    }
150
+
151
+    /**
152
+     * Get the EntityManager prefix.
153
+     *
154
+     * @return string
155
+     */
156
+    public function get_prefix() {
157
+        return $this->prefix;
158
+    }
159
+
160
+    /**
161
+     * Get the main WP_Query instance.
162
+     *
163
+     * @return WP_Query
164
+     */
165
+    public function get_main_query() {
166
+        return $this->main;
167
+    }
168
+
169
+    /**
170
+     * Get the wpdb connection instance.
171
+     *
172
+     * @return wpdb
173
+     */
174
+    public function get_wpdb() {
175
+        return $this->wpdb;
176
+    }
177
+
178
+    /**
179
+     * Retrieves the repository for the given class.
180
+     *
181
+     * @param string $class
182
+     *
183
+     * @return Repository\AbstractRepository
184
+     *
185
+     * @throws LogicException
186
+     */
187
+    protected function get_repository( $class ) {
188
+        // We can only use Axolotl models.
189
+        if ( ! is_subclass_of( $class, 'Intraxia\Jaxion\Axolotl\Model' ) ) {
190
+            throw new LogicException;
191
+        }
192
+
193
+        if ( is_subclass_of(
194
+            $class,
195
+            'Intraxia\Jaxion\Contract\Axolotl\UsesWordPressPost'
196
+        ) ) {
197
+            return new WordPressPostRepository( $this, $class );
198
+        }
199
+
200
+        if ( is_subclass_of(
201
+            $class,
202
+            'Intraxia\Jaxion\Contract\Axolotl\UsesWordPressTerm'
203
+        ) ) {
204
+            return new WordPressTermRepository( $this, $class );
205
+        }
206
+
207
+        if ( is_subclass_of(
208
+            $class,
209
+            'Intraxia\Jaxion\Contract\Axolotl\UsesCustomTable'
210
+        ) ) {
211
+            throw new LogicException;
212
+        }
213
+
214
+        // If a model doesn't have a backing data source,
215
+        // the developer needs to fix this immediately.
216
+        throw new LogicException;
217
+    }
218
+
219
+    /**
220
+     * Ensures the model is registered with the model and fills its relations.
221
+     *
222
+     * @param AbstractRepository $repository
223
+     * @param Model              $model
224
+     */
225
+    protected function handle_model( AbstractRepository $repository, Model $model ) {
226
+        $repository->register_model( $model );
227
+
228
+        if ( $model instanceof HasEagerRelationships ) {
229
+            $this->fill_related( $model, $model::get_eager_relationships() );
230
+        }
231
+    }
232
+
233
+    /**
234
+     * Fills the Model with the provided relations.
235
+     *
236
+     * If no relations are provided, all relations are filled.
237
+     *
238
+     * @param Model $model
239
+     * @param array $relations
240
+     *
241
+     * @throws LogicException
242
+     */
243
+    protected function fill_related( Model $model, array $relations = array() ) {
244
+        if ( ! $relations ) {
245
+            $relations = $model->get_related_keys();
246
+        }
247
+
248
+        foreach ( $relations as $relation ) {
249
+            if ( ! in_array( $relation, $model->get_related_keys() ) ) {
250
+                throw new LogicException;
251
+            }
252
+
253
+            if ( $model->relation_is_filled( $relation ) ) {
254
+                continue;
255
+            }
256
+
257
+            /**
258
+             * Model relationship.
259
+             *
260
+             * @var Relationship $relation
261
+             */
262
+            $relation = $model->{"related_{$relation}"}();
263
+            $relation->attach_relation( $this );
264
+        }
265
+    }
266 266
 }
Please login to merge, or discard this patch.
src/Axolotl/Model.php 1 patch
Indentation   +826 added lines, -826 removed lines patch added patch discarded remove patch
@@ -23,830 +23,830 @@
 block discarded – undo
23 23
  * @since      0.1.0
24 24
  */
25 25
 abstract class Model implements Serializes {
26
-	/**
27
-	 * Memoized values for class methods.
28
-	 *
29
-	 * @var array
30
-	 */
31
-	private static $memo = array();
32
-
33
-	/**
34
-	 * Model attributes.
35
-	 *
36
-	 * @var array
37
-	 */
38
-	private $attributes = array(
39
-		'table'  => array(),
40
-		'object' => null,
41
-	);
42
-
43
-	/**
44
-	 * Model's original attributes.
45
-	 *
46
-	 * @var array
47
-	 */
48
-	private $original = array(
49
-		'table'  => array(),
50
-		'object' => null,
51
-	);
52
-
53
-	/**
54
-	 * Properties which are allowed to be set on the model.
55
-	 *
56
-	 * If this array is empty, any attributes can be set on the model.
57
-	 *
58
-	 * @var string[]
59
-	 */
60
-	protected $fillable = array();
61
-
62
-	/**
63
-	 * Properties which cannot be automatically filled on the model.
64
-	 *
65
-	 * If the model is unguarded, these properties can be filled.
66
-	 *
67
-	 * @var array
68
-	 */
69
-	protected $guarded = array();
70
-
71
-	/**
72
-	 * Properties which should not be serialized.
73
-	 *
74
-	 * @var array
75
-	 */
76
-	protected $hidden = array();
77
-
78
-	/**
79
-	 * Properties which should be serialized.
80
-	 *
81
-	 * @var array
82
-	 */
83
-	protected $visible = array();
84
-
85
-	/**
86
-	 * Relations saved on the Model.
87
-	 *
88
-	 * @var array
89
-	 */
90
-	protected $related = array();
91
-
92
-	/**
93
-	 * Whether the model's properties are guarded.
94
-	 *
95
-	 * When false, allows guarded properties to be filled.
96
-	 *
97
-	 * @var bool
98
-	 */
99
-	protected $is_guarded = true;
100
-
101
-	/**
102
-	 * Whether the Model is having its relations filled.
103
-	 *
104
-	 * @var bool
105
-	 */
106
-	protected $filling = false;
107
-
108
-	/**
109
-	 * Constructs a new model with provided attributes.
110
-	 *
111
-	 * If 'object' is passed as one of the attributes, the underlying post
112
-	 * will be overwritten.
113
-	 *
114
-	 * @param array <string, mixed> $attributes
115
-	 */
116
-	public function __construct( array $attributes = array() ) {
117
-		$this->maybe_boot();
118
-		$this->sync_original();
119
-
120
-		if ( $this->uses_wp_object() ) {
121
-			$this->create_wp_object();
122
-		}
123
-
124
-		$this->refresh( $attributes );
125
-	}
126
-
127
-	/**
128
-	 * Refreshes the model's current attributes with the provided array.
129
-	 *
130
-	 * The model's attributes will match what was provided in the array,
131
-	 * and any attributes not passed
132
-	 *
133
-	 * @param array $attributes
134
-	 */
135
-	public function refresh( array $attributes ) {
136
-		$this->clear();
137
-
138
-		foreach ( $attributes as $name => $value ) {
139
-			$this->set_attribute( $name, $value );
140
-		}
141
-	}
142
-
143
-	/**
144
-	 * Get the model's table attributes.
145
-	 *
146
-	 * Returns the array of for the model that will either need to be
147
-	 * saved in postmeta or a separate table.
148
-	 *
149
-	 * @return array
150
-	 */
151
-	public function get_table_attributes() {
152
-		return $this->attributes['table'];
153
-	}
154
-
155
-	/**
156
-	 * Get the model's original attributes.
157
-	 *
158
-	 * @return array
159
-	 */
160
-	public function get_original_table_attributes() {
161
-		return $this->original['table'];
162
-	}
163
-
164
-	/**
165
-	 * Retrieve an array of the attributes on the model
166
-	 * that have changed compared to the model's
167
-	 * original data.
168
-	 *
169
-	 * @return array
170
-	 */
171
-	public function get_changed_table_attributes() {
172
-		$changed = array();
173
-
174
-		foreach ( $this->get_table_attributes() as $attribute ) {
175
-			if ( $this->get_attribute( $attribute ) !==
176
-			     $this->get_original_attribute( $attribute )
177
-			) {
178
-				$changed[ $attribute ] = $this->get_attribute( $attribute );
179
-			}
180
-		}
181
-
182
-		return $changed;
183
-	}
184
-
185
-	/**
186
-	 * Get the model's underlying post.
187
-	 *
188
-	 * Returns the underlying WP_Post object for the model, representing
189
-	 * the data that will be save in the wp_posts table.
190
-	 *
191
-	 * @return false|WP_Post|WP_Term
192
-	 */
193
-	public function get_underlying_wp_object() {
194
-		if ( isset( $this->attributes['object'] ) ) {
195
-			return $this->attributes['object'];
196
-		}
197
-
198
-		return false;
199
-	}
200
-
201
-	/**
202
-	 * Get the model's original underlying post.
203
-	 *
204
-	 * @return WP_Post
205
-	 */
206
-	public function get_original_underlying_wp_object() {
207
-		return $this->original['object'];
208
-	}
209
-
210
-	/**
211
-	 * Get the model attributes on the WordPress object
212
-	 * that have changed compared to the model's
213
-	 * original attributes.
214
-	 *
215
-	 * @return array
216
-	 */
217
-	public function get_changed_wp_object_attributes() {
218
-		$changed = array();
219
-
220
-		foreach ( $this->get_wp_object_keys() as $key ) {
221
-			if ( $this->get_attribute( $key ) !==
222
-			     $this->get_original_attribute( $key )
223
-			) {
224
-				$changed[ $key ] = $this->get_attribute( $key );
225
-			}
226
-		}
227
-
228
-		return $changed;
229
-	}
230
-
231
-	/**
232
-	 * Magic __set method.
233
-	 *
234
-	 * Passes the name and value to set_attribute, which is where the magic happens.
235
-	 *
236
-	 * @param string $name
237
-	 * @param mixed  $value
238
-	 */
239
-	public function __set( $name, $value ) {
240
-		$this->set_attribute( $name, $value );
241
-	}
242
-
243
-	/**
244
-	 * Sets the model attributes.
245
-	 *
246
-	 * Checks whether the model attribute can be set, check if it
247
-	 * maps to the WP_Post property, otherwise, assigns it to the
248
-	 * table attribute array.
249
-	 *
250
-	 * @param string $name
251
-	 * @param mixed  $value
252
-	 *
253
-	 * @return $this
254
-	 *
255
-	 * @throws GuardedPropertyException
256
-	 */
257
-	public function set_attribute( $name, $value ) {
258
-		if ( 'object' === $name ) {
259
-			return $this->override_wp_object( $value );
260
-		}
261
-
262
-		if ( ! $this->is_fillable( $name ) ) {
263
-			throw new GuardedPropertyException;
264
-		}
265
-
266
-		if ( $method = $this->has_map_method( $name ) ) {
267
-			$this->attributes['object']->{$this->{$method}()} = $value;
268
-		} else {
269
-			$this->attributes['table'][ $name ] = $value;
270
-		}
271
-
272
-		return $this;
273
-	}
274
-
275
-	/**
276
-	 * Retrieves all the attribute keys for the model.
277
-	 *
278
-	 * @return array
279
-	 */
280
-	public function get_attribute_keys() {
281
-		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
282
-			return self::$memo[ get_called_class() ][ __METHOD__ ];
283
-		}
284
-
285
-		return self::$memo[ get_called_class() ][ __METHOD__ ]
286
-			= array_merge(
287
-				$this->fillable,
288
-				$this->guarded,
289
-				$this->get_compute_methods(),
290
-				$this->get_related_methods()
291
-			);
292
-	}
293
-
294
-	/**
295
-	 * Retrieves the attribute keys that aren't mapped to a post.
296
-	 *
297
-	 * @return array
298
-	 */
299
-	public function get_table_keys() {
300
-		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
301
-			return self::$memo[ get_called_class() ][ __METHOD__ ];
302
-		}
303
-
304
-		$keys = array();
305
-
306
-		foreach ( $this->get_attribute_keys() as $key ) {
307
-			if ( ! $this->has_map_method( $key ) &&
308
-			     ! $this->has_compute_method( $key ) &&
309
-			     ! $this->has_related_method( $key )
310
-			) {
311
-				$keys[] = $key;
312
-			}
313
-		}
314
-
315
-		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
316
-	}
317
-
318
-	/**
319
-	 * Retrieves the attribute keys that are mapped to a post.
320
-	 *
321
-	 * @return array
322
-	 */
323
-	public function get_wp_object_keys() {
324
-		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
325
-			return self::$memo[ get_called_class() ][ __METHOD__ ];
326
-		}
327
-
328
-		$keys = array();
329
-
330
-		foreach ( $this->get_attribute_keys() as $key ) {
331
-			if ( $this->has_map_method( $key ) ) {
332
-				$keys[] = $key;
333
-			}
334
-		}
335
-
336
-		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
337
-	}
338
-
339
-	/**
340
-	 * Returns the model's keys that are computed at call time.
341
-	 *
342
-	 * @return array
343
-	 */
344
-	public function get_computed_keys() {
345
-		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
346
-			return self::$memo[ get_called_class() ][ __METHOD__ ];
347
-		}
348
-
349
-		$keys = array();
350
-
351
-		foreach ( $this->get_attribute_keys() as $key ) {
352
-			if ( $this->has_compute_method( $key ) ) {
353
-				$keys[] = $key;
354
-			}
355
-		}
356
-
357
-		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
358
-	}
359
-
360
-	/**
361
-	 * Returns the model's keys that are related to other Models.
362
-	 *
363
-	 * @return array
364
-	 */
365
-	public function get_related_keys() {
366
-		if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
367
-			return self::$memo[ get_called_class() ][ __METHOD__ ];
368
-		}
369
-
370
-		$keys = array();
371
-
372
-		foreach ( $this->get_attribute_keys() as $key ) {
373
-			if ( $this->has_related_method( $key ) ) {
374
-				$keys[] = $key;
375
-			}
376
-		}
377
-
378
-		return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
379
-	}
380
-
381
-	/**
382
-	 * Serializes the model's public data into an array.
383
-	 *
384
-	 * @return array
385
-	 */
386
-	public function serialize() {
387
-		$attributes = array();
388
-
389
-		if ( $this->visible ) {
390
-			// If visible attributes are set, we'll only reveal those.
391
-			foreach ( $this->visible as $key ) {
392
-				$attributes[ $key ] = $this->get_attribute( $key );
393
-			}
394
-		} elseif ( $this->hidden ) {
395
-			// If hidden attributes are set, we'll grab everything and hide those.
396
-			foreach ( $this->get_attribute_keys() as $key ) {
397
-				if ( ! in_array( $key, $this->hidden ) ) {
398
-					$attributes[ $key ] = $this->get_attribute( $key );
399
-				}
400
-			}
401
-		} else {
402
-			// If nothing is hidden/visible, we'll grab and reveal everything.
403
-			foreach ( $this->get_attribute_keys() as $key ) {
404
-				$attributes[ $key ] = $this->get_attribute( $key );
405
-			}
406
-		}
407
-
408
-		return array_map( function ( $attribute ) {
409
-			if ( $attribute instanceof Serializes ) {
410
-				return $attribute->serialize();
411
-			}
412
-
413
-			return $attribute;
414
-		}, $attributes );
415
-	}
416
-
417
-	/**
418
-	 * Syncs the current attributes to the model's original.
419
-	 *
420
-	 * @return $this
421
-	 */
422
-	public function sync_original() {
423
-		$this->original = $this->attributes;
424
-
425
-		if ( $this->attributes['object'] ) {
426
-			$this->original['object'] = clone $this->attributes['object'];
427
-		}
428
-
429
-		return $this;
430
-	}
431
-
432
-	/**
433
-	 * Checks if a given attribute is mass-fillable.
434
-	 *
435
-	 * Returns true if the attribute can be filled, false if it can't.
436
-	 *
437
-	 * @param string $name
438
-	 *
439
-	 * @return bool
440
-	 */
441
-	private function is_fillable( $name ) {
442
-		// If this model isn't guarded, everything is fillable.
443
-		if ( ! $this->is_guarded ) {
444
-			return true;
445
-		}
446
-
447
-		// If it's in the fillable array, then it's fillable.
448
-		if ( in_array( $name, $this->fillable ) ) {
449
-			return true;
450
-		}
451
-
452
-		// If it's explicitly guarded, then it's not fillable.
453
-		if ( in_array( $name, $this->guarded ) ) {
454
-			return false;
455
-		}
456
-
457
-		// If fillable hasn't been defined, then everything else fillable.
458
-		return ! $this->fillable;
459
-	}
460
-
461
-	/**
462
-	 * Overrides the current WP_Post with a provided one.
463
-	 *
464
-	 * Resets the post's default values and stores it in the attributes.
465
-	 *
466
-	 * @param WP_Post $value
467
-	 *
468
-	 * @return $this
469
-	 */
470
-	private function override_wp_object( $value ) {
471
-		$this->attributes['object'] = $this->set_wp_object_constants( $value );
472
-
473
-		return $this;
474
-	}
475
-
476
-	/**
477
-	 * Create and set with a new blank post.
478
-	 *
479
-	 * Creates a new WP_Post object, assigns it the default attributes,
480
-	 * and stores it in the attributes.
481
-	 *
482
-	 * @throws LogicException
483
-	 */
484
-	private function create_wp_object() {
485
-		switch ( true ) {
486
-			case $this instanceof UsesWordPressPost:
487
-				$object = new WP_Post( (object) array() );
488
-				break;
489
-			case $this instanceof UsesWordPressTerm:
490
-				$object = new WP_Term( (object) array() );
491
-				break;
492
-			default:
493
-				throw new LogicException;
494
-				break;
495
-		}
496
-
497
-		$this->attributes['object'] = $this->set_wp_object_constants( $object );
498
-	}
499
-
500
-	/**
501
-	 * Enforces values on the post that can't change.
502
-	 *
503
-	 * Primarily, this is used to make sure the post_type always maps
504
-	 * to the model's "$type" property, but this can all be overridden
505
-	 * by the developer to enforce other values in the model.
506
-	 *
507
-	 * @param object $object
508
-	 *
509
-	 * @return object
510
-	 */
511
-	protected function set_wp_object_constants( $object ) {
512
-		if ( $this instanceof UsesWordPressPost ) {
513
-			$object->post_type = $this::get_post_type();
514
-		}
515
-
516
-		if ( $this instanceof UsesWordPressTerm ) {
517
-			$object->taxonomy = $this::get_taxonomy();
518
-		}
519
-
520
-		return $object;
521
-	}
522
-
523
-	/**
524
-	 * Magic __get method.
525
-	 *
526
-	 * Passes the name and value to get_attribute, which is where the magic happens.
527
-	 *
528
-	 * @param string $name
529
-	 *
530
-	 * @return mixed
531
-	 */
532
-	public function __get( $name ) {
533
-		return $this->get_attribute( $name );
534
-	}
535
-
536
-	/**
537
-	 * Retrieves the model attribute.
538
-	 *
539
-	 * @param string $name
540
-	 *
541
-	 * @return mixed
542
-	 *
543
-	 * @throws PropertyDoesNotExistException If property isn't found.
544
-	 */
545
-	public function get_attribute( $name ) {
546
-		if ( $method = $this->has_map_method( $name ) ) {
547
-			$value = $this->attributes['object']->{$this->{$method}()};
548
-		} elseif ( $method = $this->has_compute_method( $name ) ) {
549
-			$value = $this->{$method}();
550
-		} else if ( $method = $this->has_related_method( $name ) ) {
551
-			$value = $this->get_related( $this->{$method}()->get_sha() );
552
-		} else {
553
-			if ( ! isset( $this->attributes['table'][ $name ] ) ) {
554
-				throw new PropertyDoesNotExistException;
555
-			}
556
-
557
-			$value = $this->attributes['table'][ $name ];
558
-		}
559
-
560
-		return $value;
561
-	}
562
-
563
-	/**
564
-	 * Retrieve the model's original attribute value.
565
-	 *
566
-	 * @param string $name
567
-	 *
568
-	 * @return mixed
569
-	 *
570
-	 * @throws PropertyDoesNotExistException If property isn't found.
571
-	 */
572
-	public function get_original_attribute( $name ) {
573
-		$original = new static( $this->original );
574
-
575
-		return $original->get_attribute( $name );
576
-	}
577
-
578
-	/**
579
-	 * Fetches the Model's primary ID, depending on the model
580
-	 * implementation.
581
-	 *
582
-	 * @return int
583
-	 *
584
-	 * @throws LogicException
585
-	 */
586
-	public function get_primary_id() {
587
-		if ( $this instanceof UsesWordPressPost ) {
588
-			return $this->get_underlying_wp_object()->ID;
589
-		}
590
-
591
-		if ( $this instanceof UsesWordPressTerm ) {
592
-			return $this->get_underlying_wp_object()->term_id;
593
-		}
594
-
595
-		// Model w/o wp_object not yet supported.
596
-		throw new LogicException;
597
-	}
598
-
599
-	/**
600
-	 * Generates the table foreign key, depending on the model
601
-	 * implementation.
602
-	 *
603
-	 * @return string
604
-	 *
605
-	 * @throws LogicException
606
-	 */
607
-	public function get_foreign_key() {
608
-		if ( $this instanceof UsesWordPressPost ) {
609
-			return 'post_id';
610
-		}
611
-
612
-		// Model w/o wp_object not yet supported.
613
-		throw new LogicException;
614
-	}
615
-
616
-	/**
617
-	 * Gets the related Model or Collection for the given sha.
618
-	 *
619
-	 * @param string $sha
620
-	 *
621
-	 * @return Model|Collection
622
-	 */
623
-	public function get_related( $sha ) {
624
-		return $this->related[ $sha ];
625
-	}
626
-
627
-	/**
628
-	 * Sets the related Model or Collection for the given sha.
629
-	 *
630
-	 * @param string           $sha
631
-	 * @param Model|Collection $target
632
-	 *
633
-	 * @throws RuntimeException
634
-	 */
635
-	public function set_related( $sha, $target ) {
636
-		if ( ! ( $target instanceof Model ) && ! ( $target instanceof Collection ) ) {
637
-			throw new RuntimeException;
638
-		}
639
-
640
-		$this->related[ $sha ] = $target;
641
-	}
642
-
643
-	/**
644
-	 * Checks whether the attribute has a map method.
645
-	 *
646
-	 * This is used to determine whether the attribute maps to a
647
-	 * property on the underlying WP_Post object. Returns the
648
-	 * method if one exists, returns false if it doesn't.
649
-	 *
650
-	 * @param string $name
651
-	 *
652
-	 * @return false|string
653
-	 */
654
-	protected function has_map_method( $name ) {
655
-		if ( method_exists( $this, $method = "map_{$name}" ) ) {
656
-			return $method;
657
-		}
658
-
659
-		return false;
660
-	}
661
-
662
-	/**
663
-	 * Checks whether the attribute has a compute method.
664
-	 *
665
-	 * This is used to determine if the attribute should be computed
666
-	 * from other attributes.
667
-	 *
668
-	 * @param string $name
669
-	 *
670
-	 * @return false|string
671
-	 */
672
-	protected function has_compute_method( $name ) {
673
-		if ( method_exists( $this, $method = "compute_{$name}" ) ) {
674
-			return $method;
675
-		}
676
-
677
-		return false;
678
-	}
679
-
680
-	/**
681
-	 * Checks whether the attribute has a compute method.
682
-	 *
683
-	 * This is used to determine if the attribute should be computed
684
-	 * from other attributes.
685
-	 *
686
-	 * @param string $name
687
-	 *
688
-	 * @return false|string
689
-	 */
690
-	protected function has_related_method( $name ) {
691
-		if ( method_exists( $this, $method = "related_{$name}" ) ) {
692
-			return $method;
693
-		}
694
-
695
-		return false;
696
-	}
697
-
698
-	/**
699
-	 * Clears all the current attributes from the model.
700
-	 *
701
-	 * This does not touch the model's original attributes, and will
702
-	 * only clear fillable attributes, unless the model is unguarded.
703
-	 *
704
-	 * @return $this
705
-	 */
706
-	public function clear() {
707
-		$keys = $this->get_attribute_keys();
708
-
709
-		foreach ( $keys as $key ) {
710
-			try {
711
-				$this->set_attribute( $key, null );
712
-			} catch ( GuardedPropertyException $e ) {
713
-				// We won't clear out guarded attributes.
714
-			}
715
-		}
716
-
717
-		return $this;
718
-	}
719
-
720
-	/**
721
-	 * Unguards the model.
722
-	 *
723
-	 * Sets the model to be unguarded, allowing the filling of
724
-	 * guarded attributes.
725
-	 */
726
-	public function unguard() {
727
-		$this->is_guarded = false;
728
-	}
729
-
730
-	/**
731
-	 * Reguards the model.
732
-	 *
733
-	 * Sets the model to be guarded, preventing filling of
734
-	 * guarded attributes.
735
-	 */
736
-	public function reguard() {
737
-		$this->is_guarded = true;
738
-	}
739
-
740
-	/**
741
-	 * Retrieves all the compute methods on the model.
742
-	 *
743
-	 * @return array
744
-	 */
745
-	protected function get_compute_methods() {
746
-		$methods = get_class_methods( get_called_class() );
747
-		$methods = array_filter( $methods, function ( $method ) {
748
-			return strrpos( $method, 'compute_', - strlen( $method ) ) !== false;
749
-		} );
750
-		$methods = array_map( function ( $method ) {
751
-			return substr( $method, strlen( 'compute_' ) );
752
-		}, $methods );
753
-
754
-		return $methods;
755
-	}
756
-
757
-	/**
758
-	 * Retrieves all the related methods on the model.
759
-	 *
760
-	 * @return array
761
-	 */
762
-	protected function get_related_methods() {
763
-		$methods = get_class_methods( get_called_class() );
764
-		$methods = array_filter( $methods, function ( $method ) {
765
-			return strrpos( $method, 'related_', - strlen( $method ) ) !== false;
766
-		} );
767
-		$methods = array_map( function ( $method ) {
768
-			return substr( $method, strlen( 'related_' ) );
769
-		}, $methods );
770
-
771
-		return $methods;
772
-	}
773
-
774
-	/**
775
-	 * Returns whether this relation has already been filled on the model.
776
-	 *
777
-	 * @param string $relation
778
-	 *
779
-	 * @return bool
780
-	 */
781
-	public function relation_is_filled( $relation ) {
782
-		$sha = $this
783
-			->{$this->has_related_method( $relation )}()
784
-			->get_sha();
785
-
786
-		return isset( $this->related[ $sha ] );
787
-	}
788
-
789
-	/**
790
-	 * Returns whether the Model is currently having
791
-	 * its relationships filled.
792
-	 *
793
-	 * @return bool
794
-	 */
795
-	public function is_filling() {
796
-		return $this->filling;
797
-	}
798
-
799
-	/**
800
-	 * Sets whether the Model is having its relationships filled.
801
-	 *
802
-	 * @param bool $is_filling
803
-	 */
804
-	public function set_filling( $is_filling ) {
805
-		$this->filling = $is_filling;
806
-	}
807
-
808
-	/**
809
-	 * Returns a HasMany relationship for the model.
810
-	 *
811
-	 * @param string $class
812
-	 * @param string $type
813
-	 * @param string $foreign_key
814
-	 *
815
-	 * @return HasMany
816
-	 */
817
-	protected function has_many( $class, $type, $foreign_key ) {
818
-		return new HasMany( $this, $class, $type, $foreign_key );
819
-	}
820
-
821
-	/**
822
-	 * Returns a BelongsToOne relationship for the model.
823
-	 *
824
-	 * @param string $class
825
-	 * @param string $type
826
-	 * @param string $local_key
827
-	 *
828
-	 * @return HasMany
829
-	 */
830
-	protected function belongs_to_one( $class, $type, $local_key = '' ) {
831
-		return new BelongsToOne( $this, $class, $type, $local_key );
832
-	}
833
-
834
-	/**
835
-	 * Sets up the memo array for the creating model.
836
-	 */
837
-	private function maybe_boot() {
838
-		if ( ! isset( self::$memo[ get_called_class() ] ) ) {
839
-			self::$memo[ get_called_class() ] = array();
840
-		}
841
-	}
842
-
843
-	/**
844
-	 * Whether this Model uses an underlying WordPress object.
845
-	 *
846
-	 * @return bool
847
-	 */
848
-	protected function uses_wp_object() {
849
-		return $this instanceof UsesWordPressPost ||
850
-			$this instanceof UsesWordPressTerm;
851
-	}
26
+    /**
27
+     * Memoized values for class methods.
28
+     *
29
+     * @var array
30
+     */
31
+    private static $memo = array();
32
+
33
+    /**
34
+     * Model attributes.
35
+     *
36
+     * @var array
37
+     */
38
+    private $attributes = array(
39
+        'table'  => array(),
40
+        'object' => null,
41
+    );
42
+
43
+    /**
44
+     * Model's original attributes.
45
+     *
46
+     * @var array
47
+     */
48
+    private $original = array(
49
+        'table'  => array(),
50
+        'object' => null,
51
+    );
52
+
53
+    /**
54
+     * Properties which are allowed to be set on the model.
55
+     *
56
+     * If this array is empty, any attributes can be set on the model.
57
+     *
58
+     * @var string[]
59
+     */
60
+    protected $fillable = array();
61
+
62
+    /**
63
+     * Properties which cannot be automatically filled on the model.
64
+     *
65
+     * If the model is unguarded, these properties can be filled.
66
+     *
67
+     * @var array
68
+     */
69
+    protected $guarded = array();
70
+
71
+    /**
72
+     * Properties which should not be serialized.
73
+     *
74
+     * @var array
75
+     */
76
+    protected $hidden = array();
77
+
78
+    /**
79
+     * Properties which should be serialized.
80
+     *
81
+     * @var array
82
+     */
83
+    protected $visible = array();
84
+
85
+    /**
86
+     * Relations saved on the Model.
87
+     *
88
+     * @var array
89
+     */
90
+    protected $related = array();
91
+
92
+    /**
93
+     * Whether the model's properties are guarded.
94
+     *
95
+     * When false, allows guarded properties to be filled.
96
+     *
97
+     * @var bool
98
+     */
99
+    protected $is_guarded = true;
100
+
101
+    /**
102
+     * Whether the Model is having its relations filled.
103
+     *
104
+     * @var bool
105
+     */
106
+    protected $filling = false;
107
+
108
+    /**
109
+     * Constructs a new model with provided attributes.
110
+     *
111
+     * If 'object' is passed as one of the attributes, the underlying post
112
+     * will be overwritten.
113
+     *
114
+     * @param array <string, mixed> $attributes
115
+     */
116
+    public function __construct( array $attributes = array() ) {
117
+        $this->maybe_boot();
118
+        $this->sync_original();
119
+
120
+        if ( $this->uses_wp_object() ) {
121
+            $this->create_wp_object();
122
+        }
123
+
124
+        $this->refresh( $attributes );
125
+    }
126
+
127
+    /**
128
+     * Refreshes the model's current attributes with the provided array.
129
+     *
130
+     * The model's attributes will match what was provided in the array,
131
+     * and any attributes not passed
132
+     *
133
+     * @param array $attributes
134
+     */
135
+    public function refresh( array $attributes ) {
136
+        $this->clear();
137
+
138
+        foreach ( $attributes as $name => $value ) {
139
+            $this->set_attribute( $name, $value );
140
+        }
141
+    }
142
+
143
+    /**
144
+     * Get the model's table attributes.
145
+     *
146
+     * Returns the array of for the model that will either need to be
147
+     * saved in postmeta or a separate table.
148
+     *
149
+     * @return array
150
+     */
151
+    public function get_table_attributes() {
152
+        return $this->attributes['table'];
153
+    }
154
+
155
+    /**
156
+     * Get the model's original attributes.
157
+     *
158
+     * @return array
159
+     */
160
+    public function get_original_table_attributes() {
161
+        return $this->original['table'];
162
+    }
163
+
164
+    /**
165
+     * Retrieve an array of the attributes on the model
166
+     * that have changed compared to the model's
167
+     * original data.
168
+     *
169
+     * @return array
170
+     */
171
+    public function get_changed_table_attributes() {
172
+        $changed = array();
173
+
174
+        foreach ( $this->get_table_attributes() as $attribute ) {
175
+            if ( $this->get_attribute( $attribute ) !==
176
+                    $this->get_original_attribute( $attribute )
177
+            ) {
178
+                $changed[ $attribute ] = $this->get_attribute( $attribute );
179
+            }
180
+        }
181
+
182
+        return $changed;
183
+    }
184
+
185
+    /**
186
+     * Get the model's underlying post.
187
+     *
188
+     * Returns the underlying WP_Post object for the model, representing
189
+     * the data that will be save in the wp_posts table.
190
+     *
191
+     * @return false|WP_Post|WP_Term
192
+     */
193
+    public function get_underlying_wp_object() {
194
+        if ( isset( $this->attributes['object'] ) ) {
195
+            return $this->attributes['object'];
196
+        }
197
+
198
+        return false;
199
+    }
200
+
201
+    /**
202
+     * Get the model's original underlying post.
203
+     *
204
+     * @return WP_Post
205
+     */
206
+    public function get_original_underlying_wp_object() {
207
+        return $this->original['object'];
208
+    }
209
+
210
+    /**
211
+     * Get the model attributes on the WordPress object
212
+     * that have changed compared to the model's
213
+     * original attributes.
214
+     *
215
+     * @return array
216
+     */
217
+    public function get_changed_wp_object_attributes() {
218
+        $changed = array();
219
+
220
+        foreach ( $this->get_wp_object_keys() as $key ) {
221
+            if ( $this->get_attribute( $key ) !==
222
+                    $this->get_original_attribute( $key )
223
+            ) {
224
+                $changed[ $key ] = $this->get_attribute( $key );
225
+            }
226
+        }
227
+
228
+        return $changed;
229
+    }
230
+
231
+    /**
232
+     * Magic __set method.
233
+     *
234
+     * Passes the name and value to set_attribute, which is where the magic happens.
235
+     *
236
+     * @param string $name
237
+     * @param mixed  $value
238
+     */
239
+    public function __set( $name, $value ) {
240
+        $this->set_attribute( $name, $value );
241
+    }
242
+
243
+    /**
244
+     * Sets the model attributes.
245
+     *
246
+     * Checks whether the model attribute can be set, check if it
247
+     * maps to the WP_Post property, otherwise, assigns it to the
248
+     * table attribute array.
249
+     *
250
+     * @param string $name
251
+     * @param mixed  $value
252
+     *
253
+     * @return $this
254
+     *
255
+     * @throws GuardedPropertyException
256
+     */
257
+    public function set_attribute( $name, $value ) {
258
+        if ( 'object' === $name ) {
259
+            return $this->override_wp_object( $value );
260
+        }
261
+
262
+        if ( ! $this->is_fillable( $name ) ) {
263
+            throw new GuardedPropertyException;
264
+        }
265
+
266
+        if ( $method = $this->has_map_method( $name ) ) {
267
+            $this->attributes['object']->{$this->{$method}()} = $value;
268
+        } else {
269
+            $this->attributes['table'][ $name ] = $value;
270
+        }
271
+
272
+        return $this;
273
+    }
274
+
275
+    /**
276
+     * Retrieves all the attribute keys for the model.
277
+     *
278
+     * @return array
279
+     */
280
+    public function get_attribute_keys() {
281
+        if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
282
+            return self::$memo[ get_called_class() ][ __METHOD__ ];
283
+        }
284
+
285
+        return self::$memo[ get_called_class() ][ __METHOD__ ]
286
+            = array_merge(
287
+                $this->fillable,
288
+                $this->guarded,
289
+                $this->get_compute_methods(),
290
+                $this->get_related_methods()
291
+            );
292
+    }
293
+
294
+    /**
295
+     * Retrieves the attribute keys that aren't mapped to a post.
296
+     *
297
+     * @return array
298
+     */
299
+    public function get_table_keys() {
300
+        if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
301
+            return self::$memo[ get_called_class() ][ __METHOD__ ];
302
+        }
303
+
304
+        $keys = array();
305
+
306
+        foreach ( $this->get_attribute_keys() as $key ) {
307
+            if ( ! $this->has_map_method( $key ) &&
308
+                 ! $this->has_compute_method( $key ) &&
309
+                 ! $this->has_related_method( $key )
310
+            ) {
311
+                $keys[] = $key;
312
+            }
313
+        }
314
+
315
+        return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
316
+    }
317
+
318
+    /**
319
+     * Retrieves the attribute keys that are mapped to a post.
320
+     *
321
+     * @return array
322
+     */
323
+    public function get_wp_object_keys() {
324
+        if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
325
+            return self::$memo[ get_called_class() ][ __METHOD__ ];
326
+        }
327
+
328
+        $keys = array();
329
+
330
+        foreach ( $this->get_attribute_keys() as $key ) {
331
+            if ( $this->has_map_method( $key ) ) {
332
+                $keys[] = $key;
333
+            }
334
+        }
335
+
336
+        return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
337
+    }
338
+
339
+    /**
340
+     * Returns the model's keys that are computed at call time.
341
+     *
342
+     * @return array
343
+     */
344
+    public function get_computed_keys() {
345
+        if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
346
+            return self::$memo[ get_called_class() ][ __METHOD__ ];
347
+        }
348
+
349
+        $keys = array();
350
+
351
+        foreach ( $this->get_attribute_keys() as $key ) {
352
+            if ( $this->has_compute_method( $key ) ) {
353
+                $keys[] = $key;
354
+            }
355
+        }
356
+
357
+        return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
358
+    }
359
+
360
+    /**
361
+     * Returns the model's keys that are related to other Models.
362
+     *
363
+     * @return array
364
+     */
365
+    public function get_related_keys() {
366
+        if ( isset( self::$memo[ get_called_class() ][ __METHOD__ ] ) ) {
367
+            return self::$memo[ get_called_class() ][ __METHOD__ ];
368
+        }
369
+
370
+        $keys = array();
371
+
372
+        foreach ( $this->get_attribute_keys() as $key ) {
373
+            if ( $this->has_related_method( $key ) ) {
374
+                $keys[] = $key;
375
+            }
376
+        }
377
+
378
+        return self::$memo[ get_called_class() ][ __METHOD__ ] = $keys;
379
+    }
380
+
381
+    /**
382
+     * Serializes the model's public data into an array.
383
+     *
384
+     * @return array
385
+     */
386
+    public function serialize() {
387
+        $attributes = array();
388
+
389
+        if ( $this->visible ) {
390
+            // If visible attributes are set, we'll only reveal those.
391
+            foreach ( $this->visible as $key ) {
392
+                $attributes[ $key ] = $this->get_attribute( $key );
393
+            }
394
+        } elseif ( $this->hidden ) {
395
+            // If hidden attributes are set, we'll grab everything and hide those.
396
+            foreach ( $this->get_attribute_keys() as $key ) {
397
+                if ( ! in_array( $key, $this->hidden ) ) {
398
+                    $attributes[ $key ] = $this->get_attribute( $key );
399
+                }
400
+            }
401
+        } else {
402
+            // If nothing is hidden/visible, we'll grab and reveal everything.
403
+            foreach ( $this->get_attribute_keys() as $key ) {
404
+                $attributes[ $key ] = $this->get_attribute( $key );
405
+            }
406
+        }
407
+
408
+        return array_map( function ( $attribute ) {
409
+            if ( $attribute instanceof Serializes ) {
410
+                return $attribute->serialize();
411
+            }
412
+
413
+            return $attribute;
414
+        }, $attributes );
415
+    }
416
+
417
+    /**
418
+     * Syncs the current attributes to the model's original.
419
+     *
420
+     * @return $this
421
+     */
422
+    public function sync_original() {
423
+        $this->original = $this->attributes;
424
+
425
+        if ( $this->attributes['object'] ) {
426
+            $this->original['object'] = clone $this->attributes['object'];
427
+        }
428
+
429
+        return $this;
430
+    }
431
+
432
+    /**
433
+     * Checks if a given attribute is mass-fillable.
434
+     *
435
+     * Returns true if the attribute can be filled, false if it can't.
436
+     *
437
+     * @param string $name
438
+     *
439
+     * @return bool
440
+     */
441
+    private function is_fillable( $name ) {
442
+        // If this model isn't guarded, everything is fillable.
443
+        if ( ! $this->is_guarded ) {
444
+            return true;
445
+        }
446
+
447
+        // If it's in the fillable array, then it's fillable.
448
+        if ( in_array( $name, $this->fillable ) ) {
449
+            return true;
450
+        }
451
+
452
+        // If it's explicitly guarded, then it's not fillable.
453
+        if ( in_array( $name, $this->guarded ) ) {
454
+            return false;
455
+        }
456
+
457
+        // If fillable hasn't been defined, then everything else fillable.
458
+        return ! $this->fillable;
459
+    }
460
+
461
+    /**
462
+     * Overrides the current WP_Post with a provided one.
463
+     *
464
+     * Resets the post's default values and stores it in the attributes.
465
+     *
466
+     * @param WP_Post $value
467
+     *
468
+     * @return $this
469
+     */
470
+    private function override_wp_object( $value ) {
471
+        $this->attributes['object'] = $this->set_wp_object_constants( $value );
472
+
473
+        return $this;
474
+    }
475
+
476
+    /**
477
+     * Create and set with a new blank post.
478
+     *
479
+     * Creates a new WP_Post object, assigns it the default attributes,
480
+     * and stores it in the attributes.
481
+     *
482
+     * @throws LogicException
483
+     */
484
+    private function create_wp_object() {
485
+        switch ( true ) {
486
+            case $this instanceof UsesWordPressPost:
487
+                $object = new WP_Post( (object) array() );
488
+                break;
489
+            case $this instanceof UsesWordPressTerm:
490
+                $object = new WP_Term( (object) array() );
491
+                break;
492
+            default:
493
+                throw new LogicException;
494
+                break;
495
+        }
496
+
497
+        $this->attributes['object'] = $this->set_wp_object_constants( $object );
498
+    }
499
+
500
+    /**
501
+     * Enforces values on the post that can't change.
502
+     *
503
+     * Primarily, this is used to make sure the post_type always maps
504
+     * to the model's "$type" property, but this can all be overridden
505
+     * by the developer to enforce other values in the model.
506
+     *
507
+     * @param object $object
508
+     *
509
+     * @return object
510
+     */
511
+    protected function set_wp_object_constants( $object ) {
512
+        if ( $this instanceof UsesWordPressPost ) {
513
+            $object->post_type = $this::get_post_type();
514
+        }
515
+
516
+        if ( $this instanceof UsesWordPressTerm ) {
517
+            $object->taxonomy = $this::get_taxonomy();
518
+        }
519
+
520
+        return $object;
521
+    }
522
+
523
+    /**
524
+     * Magic __get method.
525
+     *
526
+     * Passes the name and value to get_attribute, which is where the magic happens.
527
+     *
528
+     * @param string $name
529
+     *
530
+     * @return mixed
531
+     */
532
+    public function __get( $name ) {
533
+        return $this->get_attribute( $name );
534
+    }
535
+
536
+    /**
537
+     * Retrieves the model attribute.
538
+     *
539
+     * @param string $name
540
+     *
541
+     * @return mixed
542
+     *
543
+     * @throws PropertyDoesNotExistException If property isn't found.
544
+     */
545
+    public function get_attribute( $name ) {
546
+        if ( $method = $this->has_map_method( $name ) ) {
547
+            $value = $this->attributes['object']->{$this->{$method}()};
548
+        } elseif ( $method = $this->has_compute_method( $name ) ) {
549
+            $value = $this->{$method}();
550
+        } else if ( $method = $this->has_related_method( $name ) ) {
551
+            $value = $this->get_related( $this->{$method}()->get_sha() );
552
+        } else {
553
+            if ( ! isset( $this->attributes['table'][ $name ] ) ) {
554
+                throw new PropertyDoesNotExistException;
555
+            }
556
+
557
+            $value = $this->attributes['table'][ $name ];
558
+        }
559
+
560
+        return $value;
561
+    }
562
+
563
+    /**
564
+     * Retrieve the model's original attribute value.
565
+     *
566
+     * @param string $name
567
+     *
568
+     * @return mixed
569
+     *
570
+     * @throws PropertyDoesNotExistException If property isn't found.
571
+     */
572
+    public function get_original_attribute( $name ) {
573
+        $original = new static( $this->original );
574
+
575
+        return $original->get_attribute( $name );
576
+    }
577
+
578
+    /**
579
+     * Fetches the Model's primary ID, depending on the model
580
+     * implementation.
581
+     *
582
+     * @return int
583
+     *
584
+     * @throws LogicException
585
+     */
586
+    public function get_primary_id() {
587
+        if ( $this instanceof UsesWordPressPost ) {
588
+            return $this->get_underlying_wp_object()->ID;
589
+        }
590
+
591
+        if ( $this instanceof UsesWordPressTerm ) {
592
+            return $this->get_underlying_wp_object()->term_id;
593
+        }
594
+
595
+        // Model w/o wp_object not yet supported.
596
+        throw new LogicException;
597
+    }
598
+
599
+    /**
600
+     * Generates the table foreign key, depending on the model
601
+     * implementation.
602
+     *
603
+     * @return string
604
+     *
605
+     * @throws LogicException
606
+     */
607
+    public function get_foreign_key() {
608
+        if ( $this instanceof UsesWordPressPost ) {
609
+            return 'post_id';
610
+        }
611
+
612
+        // Model w/o wp_object not yet supported.
613
+        throw new LogicException;
614
+    }
615
+
616
+    /**
617
+     * Gets the related Model or Collection for the given sha.
618
+     *
619
+     * @param string $sha
620
+     *
621
+     * @return Model|Collection
622
+     */
623
+    public function get_related( $sha ) {
624
+        return $this->related[ $sha ];
625
+    }
626
+
627
+    /**
628
+     * Sets the related Model or Collection for the given sha.
629
+     *
630
+     * @param string           $sha
631
+     * @param Model|Collection $target
632
+     *
633
+     * @throws RuntimeException
634
+     */
635
+    public function set_related( $sha, $target ) {
636
+        if ( ! ( $target instanceof Model ) && ! ( $target instanceof Collection ) ) {
637
+            throw new RuntimeException;
638
+        }
639
+
640
+        $this->related[ $sha ] = $target;
641
+    }
642
+
643
+    /**
644
+     * Checks whether the attribute has a map method.
645
+     *
646
+     * This is used to determine whether the attribute maps to a
647
+     * property on the underlying WP_Post object. Returns the
648
+     * method if one exists, returns false if it doesn't.
649
+     *
650
+     * @param string $name
651
+     *
652
+     * @return false|string
653
+     */
654
+    protected function has_map_method( $name ) {
655
+        if ( method_exists( $this, $method = "map_{$name}" ) ) {
656
+            return $method;
657
+        }
658
+
659
+        return false;
660
+    }
661
+
662
+    /**
663
+     * Checks whether the attribute has a compute method.
664
+     *
665
+     * This is used to determine if the attribute should be computed
666
+     * from other attributes.
667
+     *
668
+     * @param string $name
669
+     *
670
+     * @return false|string
671
+     */
672
+    protected function has_compute_method( $name ) {
673
+        if ( method_exists( $this, $method = "compute_{$name}" ) ) {
674
+            return $method;
675
+        }
676
+
677
+        return false;
678
+    }
679
+
680
+    /**
681
+     * Checks whether the attribute has a compute method.
682
+     *
683
+     * This is used to determine if the attribute should be computed
684
+     * from other attributes.
685
+     *
686
+     * @param string $name
687
+     *
688
+     * @return false|string
689
+     */
690
+    protected function has_related_method( $name ) {
691
+        if ( method_exists( $this, $method = "related_{$name}" ) ) {
692
+            return $method;
693
+        }
694
+
695
+        return false;
696
+    }
697
+
698
+    /**
699
+     * Clears all the current attributes from the model.
700
+     *
701
+     * This does not touch the model's original attributes, and will
702
+     * only clear fillable attributes, unless the model is unguarded.
703
+     *
704
+     * @return $this
705
+     */
706
+    public function clear() {
707
+        $keys = $this->get_attribute_keys();
708
+
709
+        foreach ( $keys as $key ) {
710
+            try {
711
+                $this->set_attribute( $key, null );
712
+            } catch ( GuardedPropertyException $e ) {
713
+                // We won't clear out guarded attributes.
714
+            }
715
+        }
716
+
717
+        return $this;
718
+    }
719
+
720
+    /**
721
+     * Unguards the model.
722
+     *
723
+     * Sets the model to be unguarded, allowing the filling of
724
+     * guarded attributes.
725
+     */
726
+    public function unguard() {
727
+        $this->is_guarded = false;
728
+    }
729
+
730
+    /**
731
+     * Reguards the model.
732
+     *
733
+     * Sets the model to be guarded, preventing filling of
734
+     * guarded attributes.
735
+     */
736
+    public function reguard() {
737
+        $this->is_guarded = true;
738
+    }
739
+
740
+    /**
741
+     * Retrieves all the compute methods on the model.
742
+     *
743
+     * @return array
744
+     */
745
+    protected function get_compute_methods() {
746
+        $methods = get_class_methods( get_called_class() );
747
+        $methods = array_filter( $methods, function ( $method ) {
748
+            return strrpos( $method, 'compute_', - strlen( $method ) ) !== false;
749
+        } );
750
+        $methods = array_map( function ( $method ) {
751
+            return substr( $method, strlen( 'compute_' ) );
752
+        }, $methods );
753
+
754
+        return $methods;
755
+    }
756
+
757
+    /**
758
+     * Retrieves all the related methods on the model.
759
+     *
760
+     * @return array
761
+     */
762
+    protected function get_related_methods() {
763
+        $methods = get_class_methods( get_called_class() );
764
+        $methods = array_filter( $methods, function ( $method ) {
765
+            return strrpos( $method, 'related_', - strlen( $method ) ) !== false;
766
+        } );
767
+        $methods = array_map( function ( $method ) {
768
+            return substr( $method, strlen( 'related_' ) );
769
+        }, $methods );
770
+
771
+        return $methods;
772
+    }
773
+
774
+    /**
775
+     * Returns whether this relation has already been filled on the model.
776
+     *
777
+     * @param string $relation
778
+     *
779
+     * @return bool
780
+     */
781
+    public function relation_is_filled( $relation ) {
782
+        $sha = $this
783
+            ->{$this->has_related_method( $relation )}()
784
+            ->get_sha();
785
+
786
+        return isset( $this->related[ $sha ] );
787
+    }
788
+
789
+    /**
790
+     * Returns whether the Model is currently having
791
+     * its relationships filled.
792
+     *
793
+     * @return bool
794
+     */
795
+    public function is_filling() {
796
+        return $this->filling;
797
+    }
798
+
799
+    /**
800
+     * Sets whether the Model is having its relationships filled.
801
+     *
802
+     * @param bool $is_filling
803
+     */
804
+    public function set_filling( $is_filling ) {
805
+        $this->filling = $is_filling;
806
+    }
807
+
808
+    /**
809
+     * Returns a HasMany relationship for the model.
810
+     *
811
+     * @param string $class
812
+     * @param string $type
813
+     * @param string $foreign_key
814
+     *
815
+     * @return HasMany
816
+     */
817
+    protected function has_many( $class, $type, $foreign_key ) {
818
+        return new HasMany( $this, $class, $type, $foreign_key );
819
+    }
820
+
821
+    /**
822
+     * Returns a BelongsToOne relationship for the model.
823
+     *
824
+     * @param string $class
825
+     * @param string $type
826
+     * @param string $local_key
827
+     *
828
+     * @return HasMany
829
+     */
830
+    protected function belongs_to_one( $class, $type, $local_key = '' ) {
831
+        return new BelongsToOne( $this, $class, $type, $local_key );
832
+    }
833
+
834
+    /**
835
+     * Sets up the memo array for the creating model.
836
+     */
837
+    private function maybe_boot() {
838
+        if ( ! isset( self::$memo[ get_called_class() ] ) ) {
839
+            self::$memo[ get_called_class() ] = array();
840
+        }
841
+    }
842
+
843
+    /**
844
+     * Whether this Model uses an underlying WordPress object.
845
+     *
846
+     * @return bool
847
+     */
848
+    protected function uses_wp_object() {
849
+        return $this instanceof UsesWordPressPost ||
850
+            $this instanceof UsesWordPressTerm;
851
+    }
852 852
 }
Please login to merge, or discard this patch.
src/Axolotl/Collection.php 1 patch
Indentation   +148 added lines, -148 removed lines patch added patch discarded remove patch
@@ -14,152 +14,152 @@
 block discarded – undo
14 14
  * @subpackage Axolotl
15 15
  */
16 16
 class Collection implements Countable, Iterator, Serializes {
17
-	/**
18
-	 * Collection elements.
19
-	 *
20
-	 * @var array
21
-	 */
22
-	protected $elements = array();
23
-
24
-	/**
25
-	 * Models registered to the collection.
26
-	 *
27
-	 * @var string
28
-	 */
29
-	protected $model;
30
-
31
-	/**
32
-	 * Where Collection is in loop.
33
-	 *
34
-	 * @var int
35
-	 */
36
-	protected $position = 0;
37
-
38
-	/**
39
-	 * Collection constructor.
40
-	 *
41
-	 * @param array $elements
42
-	 * @param array $config
43
-	 */
44
-	public function __construct( array $elements = array(), array $config = array() ) {
45
-		$this->parse_config( $config );
46
-
47
-		foreach ( $elements as $element ) {
48
-			$this->add( $element );
49
-		}
50
-	}
51
-
52
-	/**
53
-	 * Adds a new element to the Collection.
54
-	 *
55
-	 * @param mixed $element
56
-	 *
57
-	 * @throws RuntimeException
58
-	 */
59
-	public function add( $element ) {
60
-		if ( $this->model && is_array( $element ) ) {
61
-			$element = new $this->model( $element );
62
-		}
63
-
64
-		if ( $this->model && ! ( $element instanceof $this->model ) ) {
65
-			throw new RuntimeException;
66
-		}
67
-
68
-		$this->elements[] = $element;
69
-	}
70
-
71
-	/**
72
-	 * Fetches the element at the provided index.
73
-	 *
74
-	 * @param int $index
75
-	 *
76
-	 * @return mixed
77
-	 */
78
-	public function at( $index ) {
79
-		return $this->elements[ $index ];
80
-	}
81
-
82
-	/**
83
-	 * Return the current element.
84
-	 *
85
-	 * @return mixed
86
-	 */
87
-	public function current() {
88
-		return $this->at( $this->position );
89
-	}
90
-
91
-	/**
92
-	 * Move forward to next element.
93
-	 */
94
-	public function next() {
95
-		$this->position ++;
96
-	}
97
-
98
-	/**
99
-	 * Return the key of the current element.
100
-	 *
101
-	 * @return mixed
102
-	 */
103
-	public function key() {
104
-		return $this->position;
105
-	}
106
-
107
-	/**
108
-	 * Checks if current position is valid.
109
-	 *
110
-	 * @return bool
111
-	 */
112
-	public function valid() {
113
-		return isset( $this->elements[ $this->position ] );
114
-	}
115
-
116
-	/**
117
-	 * Rewind the Iterator to the first element.
118
-	 */
119
-	public function rewind() {
120
-		$this->position = 0;
121
-	}
122
-
123
-	/**
124
-	 * Count elements of an object.
125
-	 *
126
-	 * @return int
127
-	 */
128
-	public function count() {
129
-		return count( $this->elements );
130
-	}
131
-
132
-	/**
133
-	 * Parses the Collection config to set its properties.
134
-	 *
135
-	 * @param array $config
136
-	 *
137
-	 * @throws LogicException
138
-	 */
139
-	protected function parse_config( array $config ) {
140
-		if ( isset( $config['model'] ) ) {
141
-			$model = $config['model'];
142
-
143
-			if ( ! is_subclass_of( $model, 'Intraxia\Jaxion\Axolotl\Model' ) ) {
144
-				throw new LogicException;
145
-			}
146
-
147
-			$this->model = $model;
148
-		}
149
-	}
150
-
151
-	/**
152
-	 * {@inheritDoc}
153
-	 *
154
-	 * @return array
155
-	 */
156
-	public function serialize() {
157
-		return array_map(function( $element ) {
158
-			if ( $element instanceof Serializes ) {
159
-				return $element->serialize();
160
-			}
161
-
162
-			return $element;
163
-		}, $this->elements);
164
-	}
17
+    /**
18
+     * Collection elements.
19
+     *
20
+     * @var array
21
+     */
22
+    protected $elements = array();
23
+
24
+    /**
25
+     * Models registered to the collection.
26
+     *
27
+     * @var string
28
+     */
29
+    protected $model;
30
+
31
+    /**
32
+     * Where Collection is in loop.
33
+     *
34
+     * @var int
35
+     */
36
+    protected $position = 0;
37
+
38
+    /**
39
+     * Collection constructor.
40
+     *
41
+     * @param array $elements
42
+     * @param array $config
43
+     */
44
+    public function __construct( array $elements = array(), array $config = array() ) {
45
+        $this->parse_config( $config );
46
+
47
+        foreach ( $elements as $element ) {
48
+            $this->add( $element );
49
+        }
50
+    }
51
+
52
+    /**
53
+     * Adds a new element to the Collection.
54
+     *
55
+     * @param mixed $element
56
+     *
57
+     * @throws RuntimeException
58
+     */
59
+    public function add( $element ) {
60
+        if ( $this->model && is_array( $element ) ) {
61
+            $element = new $this->model( $element );
62
+        }
63
+
64
+        if ( $this->model && ! ( $element instanceof $this->model ) ) {
65
+            throw new RuntimeException;
66
+        }
67
+
68
+        $this->elements[] = $element;
69
+    }
70
+
71
+    /**
72
+     * Fetches the element at the provided index.
73
+     *
74
+     * @param int $index
75
+     *
76
+     * @return mixed
77
+     */
78
+    public function at( $index ) {
79
+        return $this->elements[ $index ];
80
+    }
81
+
82
+    /**
83
+     * Return the current element.
84
+     *
85
+     * @return mixed
86
+     */
87
+    public function current() {
88
+        return $this->at( $this->position );
89
+    }
90
+
91
+    /**
92
+     * Move forward to next element.
93
+     */
94
+    public function next() {
95
+        $this->position ++;
96
+    }
97
+
98
+    /**
99
+     * Return the key of the current element.
100
+     *
101
+     * @return mixed
102
+     */
103
+    public function key() {
104
+        return $this->position;
105
+    }
106
+
107
+    /**
108
+     * Checks if current position is valid.
109
+     *
110
+     * @return bool
111
+     */
112
+    public function valid() {
113
+        return isset( $this->elements[ $this->position ] );
114
+    }
115
+
116
+    /**
117
+     * Rewind the Iterator to the first element.
118
+     */
119
+    public function rewind() {
120
+        $this->position = 0;
121
+    }
122
+
123
+    /**
124
+     * Count elements of an object.
125
+     *
126
+     * @return int
127
+     */
128
+    public function count() {
129
+        return count( $this->elements );
130
+    }
131
+
132
+    /**
133
+     * Parses the Collection config to set its properties.
134
+     *
135
+     * @param array $config
136
+     *
137
+     * @throws LogicException
138
+     */
139
+    protected function parse_config( array $config ) {
140
+        if ( isset( $config['model'] ) ) {
141
+            $model = $config['model'];
142
+
143
+            if ( ! is_subclass_of( $model, 'Intraxia\Jaxion\Axolotl\Model' ) ) {
144
+                throw new LogicException;
145
+            }
146
+
147
+            $this->model = $model;
148
+        }
149
+    }
150
+
151
+    /**
152
+     * {@inheritDoc}
153
+     *
154
+     * @return array
155
+     */
156
+    public function serialize() {
157
+        return array_map(function( $element ) {
158
+            if ( $element instanceof Serializes ) {
159
+                return $element->serialize();
160
+            }
161
+
162
+            return $element;
163
+        }, $this->elements);
164
+    }
165 165
 }
Please login to merge, or discard this patch.
src/Axolotl/ServiceProvider.php 1 patch
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -12,17 +12,17 @@
 block discarded – undo
12 12
  * @subpackage Axolotl
13 13
  */
14 14
 class ServiceProvider implements ServiceProviderContract {
15
-	/**
16
-	 * {@inheritdoc}
17
-	 *
18
-	 * @param Container $container
19
-	 */
20
-	public function register( Container $container ) {
21
-		$container->define(
22
-			array( 'database' => 'Intraxia\Jaxion\Contract\Axolotl\EntityManager' ),
23
-			function ( $app ) {
24
-				return new EntityManager( new WP_Query, $app->fetch( 'slug' ) );
25
-			}
26
-		);
27
-	}
15
+    /**
16
+     * {@inheritdoc}
17
+     *
18
+     * @param Container $container
19
+     */
20
+    public function register( Container $container ) {
21
+        $container->define(
22
+            array( 'database' => 'Intraxia\Jaxion\Contract\Axolotl\EntityManager' ),
23
+            function ( $app ) {
24
+                return new EntityManager( new WP_Query, $app->fetch( 'slug' ) );
25
+            }
26
+        );
27
+    }
28 28
 }
Please login to merge, or discard this patch.
src/Utility/Str.php 1 patch
Indentation   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -11,39 +11,39 @@
 block discarded – undo
11 11
  * @subpackage Utility
12 12
  */
13 13
 class Str {
14
-	/**
15
-	 * Determine if a given string starts with a given substring.
16
-	 *
17
-	 * @param  string       $haystack
18
-	 * @param  string|array $needles
19
-	 *
20
-	 * @return bool
21
-	 */
22
-	public static function starts_with( $haystack, $needles ) {
23
-		foreach ( (array) $needles as $needle ) {
24
-			if ( '' !== $needle && 0 === strpos( $haystack, $needle ) ) {
25
-				return true;
26
-			}
27
-		}
14
+    /**
15
+     * Determine if a given string starts with a given substring.
16
+     *
17
+     * @param  string       $haystack
18
+     * @param  string|array $needles
19
+     *
20
+     * @return bool
21
+     */
22
+    public static function starts_with( $haystack, $needles ) {
23
+        foreach ( (array) $needles as $needle ) {
24
+            if ( '' !== $needle && 0 === strpos( $haystack, $needle ) ) {
25
+                return true;
26
+            }
27
+        }
28 28
 
29
-		return false;
30
-	}
29
+        return false;
30
+    }
31 31
 
32
-	/**
33
-	 * Determine if a given string ends with a given substring.
34
-	 *
35
-	 * @param  string       $haystack
36
-	 * @param  string|array $needles
37
-	 *
38
-	 * @return bool
39
-	 */
40
-	public static function ends_with( $haystack, $needles ) {
41
-		foreach ( (array) $needles as $needle ) {
42
-			if ( substr( $haystack, - strlen( $needle ) ) === (string) $needle ) {
43
-				return true;
44
-			}
45
-		}
32
+    /**
33
+     * Determine if a given string ends with a given substring.
34
+     *
35
+     * @param  string       $haystack
36
+     * @param  string|array $needles
37
+     *
38
+     * @return bool
39
+     */
40
+    public static function ends_with( $haystack, $needles ) {
41
+        foreach ( (array) $needles as $needle ) {
42
+            if ( substr( $haystack, - strlen( $needle ) ) === (string) $needle ) {
43
+                return true;
44
+            }
45
+        }
46 46
 
47
-		return false;
48
-	}
47
+        return false;
48
+    }
49 49
 }
Please login to merge, or discard this patch.