99namespace simialbi \yii2 \rest ;
1010
1111use yii \base \InvalidConfigException ;
12- use yii \base \NotSupportedException ;
1312use yii \db \ActiveQueryInterface ;
1413use yii \db \ActiveQueryTrait ;
1514use yii \db \ActiveRelationTrait ;
@@ -57,6 +56,126 @@ public function createCommand($db = null)
5756 return parent ::createCommand ($ db );
5857 }
5958
59+ /**
60+ * {@inheritdoc}
61+ * @throws InvalidConfigException
62+ */
63+ public function prepare ($ builder )
64+ {
65+ if (!empty ($ this ->joinWith )) {
66+ $ this ->buildJoinWith ();
67+ $ this ->joinWith = null ;
68+ }
69+
70+ if ($ this ->primaryModel === null ) {
71+ // eager loading
72+ $ query = Query::create ($ this );
73+ } else {
74+ // lazy loading of a relation
75+ $ where = $ this ->where ;
76+
77+ if ($ this ->via instanceof self) {
78+ // via junction table
79+ $ viaModels = $ this ->via ->findJunctionRows ([$ this ->primaryModel ]);
80+ $ this ->filterByModels ($ viaModels );
81+ } elseif (is_array ($ this ->via )) {
82+ // via relation
83+ /* @var $viaQuery ActiveQuery */
84+ list ($ viaName , $ viaQuery ) = $ this ->via ;
85+ if ($ viaQuery ->multiple ) {
86+ if ($ this ->primaryModel ->isRelationPopulated ($ viaName )) {
87+ $ viaModels = $ this ->primaryModel ->$ viaName ;
88+ } else {
89+ $ viaModels = $ viaQuery ->all ();
90+ $ this ->primaryModel ->populateRelation ($ viaName , $ viaModels );
91+ }
92+ } else {
93+ if ($ this ->primaryModel ->isRelationPopulated ($ viaName )) {
94+ $ model = $ this ->primaryModel ->$ viaName ;
95+ } else {
96+ $ model = $ viaQuery ->one ();
97+ $ this ->primaryModel ->populateRelation ($ viaName , $ model );
98+ }
99+ $ viaModels = $ model === null ? [] : [$ model ];
100+ }
101+ $ this ->filterByModels ($ viaModels );
102+ } else {
103+ $ this ->filterByModels ([$ this ->primaryModel ]);
104+ }
105+
106+ $ query = Query::create ($ this );
107+ $ this ->andWhere ($ where );
108+ }
109+
110+ return $ query ;
111+ }
112+
113+ /**
114+ * Builds join with clauses
115+ */
116+ private function buildJoinWith ()
117+ {
118+ $ join = $ this ->join ;
119+ $ this ->join = [];
120+ $ model = new $ this ->modelClass ();
121+ foreach ($ this ->joinWith as $ with ) {
122+ $ this ->joinWithRelations ($ model , $ with );
123+ foreach ($ with as $ name => $ callback ) {
124+ $ this ->innerJoin (is_int ($ name ) ? $ callback : [$ name => $ callback ]);
125+ unset($ with [$ name ]);
126+ }
127+ }
128+ if (!empty ($ join )) {
129+ // append explicit join to joinWith()
130+ // https://github.com/yiisoft/yii2/issues/2880
131+ $ this ->join = empty ($ this ->join ) ? $ join : array_merge ($ this ->join , $ join );
132+ }
133+ }
134+
135+ /**
136+ * Modifies the current query by adding join fragments based on the given relations.
137+ * @param ActiveRecord $model the primary model
138+ * @param array $with the relations to be joined
139+ */
140+ protected function joinWithRelations ($ model , $ with )
141+ {
142+ foreach ($ with as $ name => $ callback ) {
143+ if (is_int ($ name )) {
144+ $ name = $ callback ;
145+ $ callback = null ;
146+ }
147+ $ primaryModel = $ model ;
148+ $ parent = $ this ;
149+ if (!isset ($ relations [$ name ])) {
150+ $ relations [$ name ] = $ relation = $ primaryModel ->getRelation ($ name );
151+ /* @var $relation static */
152+ if ($ callback !== null ) {
153+ call_user_func ($ callback , $ relation );
154+ }
155+ if (!empty ($ relation ->joinWith )) {
156+ $ relation ->buildJoinWith ();
157+ }
158+ $ this ->joinWithRelation ($ parent , $ relation );
159+ }
160+ }
161+ }
162+
163+ /**
164+ * Joins a parent query with a child query.
165+ * The current query object will be modified accordingly.
166+ *
167+ * @param ActiveQuery $parent
168+ * @param ActiveQuery $child
169+ */
170+ private function joinWithRelation ($ parent , $ child )
171+ {
172+ if (!empty ($ child ->join )) {
173+ foreach ($ child ->join as $ join ) {
174+ $ this ->join [] = $ join ;
175+ }
176+ }
177+ }
178+
60179 /**
61180 * {@inheritdoc}
62181 */
@@ -72,7 +191,7 @@ public function all($db = null)
72191 public function one ($ db = null )
73192 {
74193 $ row = parent ::one ($ db );
75- if (! empty ( $ row) ) {
194+ if ($ row !== false ) {
76195 $ models = $ this ->populate (isset ($ row [0 ]) ? $ row : [$ row ]);
77196
78197 return reset ($ models ) ?: null ;
@@ -81,22 +200,122 @@ public function one($db = null)
81200 return null ;
82201 }
83202
203+
204+ /**
205+ * {@inheritdoc}
206+ * @throws InvalidConfigException
207+ */
208+ public function populate ($ rows )
209+ {
210+ if (empty ($ rows )) {
211+ return [];
212+ }
213+
214+ $ models = $ this ->createModels ($ rows );
215+ if (!empty ($ this ->join ) && $ this ->indexBy === null ) {
216+ $ models = $ this ->removeDuplicatedModels ($ models );
217+ }
218+ if (!empty ($ this ->with )) {
219+ $ this ->findWith ($ this ->with , $ models );
220+ }
221+ if (!$ this ->asArray ) {
222+ foreach ($ models as $ model ) {
223+ $ model ->afterFind ();
224+ }
225+ } elseif ($ this ->indexBy !== null ) {
226+ $ models = ArrayHelper::index ($ models , $ this ->indexBy );
227+ }
228+
229+ return $ models ;
230+ }
231+
84232 /**
85233 * {@inheritDoc}
86- * @throws NotSupportedException
87234 */
88- public function via ( $ relationName , callable $ callable = null )
235+ protected function createModels ( $ rows )
89236 {
90- throw new NotSupportedException ('Via relations are not supported in rest applications ' );
237+ if ($ this ->asArray ) {
238+ return $ rows ;
239+ } else {
240+ $ models = [];
241+ /* @var $class ActiveRecord */
242+ $ class = $ this ->modelClass ;
243+ foreach ($ rows as $ row ) {
244+ $ model = $ class ::instantiate ($ row );
245+ /** @var $modelClass ActiveRecord */
246+ $ modelClass = get_class ($ model );
247+ $ modelClass ::populateRecord ($ model , $ row );
248+ if (!empty ($ this ->join )) {
249+ foreach ($ this ->join as $ join ) {
250+ if (isset ($ join [1 ], $ row [$ join [1 ]])) {
251+ $ relation = $ model ->getRelation ($ join [1 ]);
252+ $ rows = (ArrayHelper::isAssociative ($ row [$ join [1 ]])) ? [$ row [$ join [1 ]]] : $ row [$ join [1 ]];
253+ $ relations = $ relation ->populate ($ rows );
254+ $ model ->populateRelation ($ join [1 ], $ relation ->multiple ? $ relations : $ relations [0 ]);
255+ }
256+ }
257+ }
258+ $ models [] = $ model ;
259+ }
260+ return $ models ;
261+ }
91262 }
92263
93264 /**
94- * {@inheritdoc}
95- * @throws InvalidConfigException
265+ * Removes duplicated models by checking their primary key values.
266+ * This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
267+ *
268+ * @param array $models the models to be checked
269+ *
270+ * @return array the distinctive models
271+ * @throws InvalidConfigException if model primary key is empty
96272 */
97- public function prepare ( $ builder )
273+ private function removeDuplicatedModels ( $ models )
98274 {
99- return Query::create ($ this );
275+ $ hash = [];
276+ /* @var $class ActiveRecord */
277+ $ class = $ this ->modelClass ;
278+ $ pks = $ class ::primaryKey ();
279+
280+ if (count ($ pks ) > 1 ) {
281+ // composite primary key
282+ foreach ($ models as $ i => $ model ) {
283+ $ key = [];
284+ foreach ($ pks as $ pk ) {
285+ if (!isset ($ model [$ pk ])) {
286+ // do not continue if the primary key is not part of the result set
287+ break 2 ;
288+ }
289+ $ key [] = $ model [$ pk ];
290+ }
291+ $ key = serialize ($ key );
292+ if (isset ($ hash [$ key ])) {
293+ unset($ models [$ i ]);
294+ } else {
295+ $ hash [$ key ] = true ;
296+ }
297+ }
298+ } elseif (empty ($ pks )) {
299+ /** @var $class string */
300+ throw new InvalidConfigException ("Primary key of ' {$ class }' can not be empty. " );
301+ } else {
302+ // single column primary key
303+ $ pk = reset ($ pks );
304+ foreach ($ models as $ i => $ model ) {
305+ if (!isset ($ model [$ pk ])) {
306+ // do not continue if the primary key is not part of the result set
307+ break ;
308+ }
309+ $ key = $ model [$ pk ];
310+ if (isset ($ hash [$ key ])) {
311+ unset($ models [$ i ]);
312+ } elseif ($ key !== null ) {
313+ $ hash [$ key ] = true ;
314+ }
315+ }
316+ }
317+
318+ return array_values ($ models );
100319 }
101320
102321 /**
@@ -135,107 +354,7 @@ public function prepare($builder)
135354 */
136355 public function joinWith ($ with )
137356 {
138- if (is_array ($ with )) {
139- $ this ->join = array_merge ((array )$ this ->join , $ with );
140- } else {
141- $ this ->join [] = $ with ;
142- }
143-
144- return $ this ;
145- }
146-
147- /**
148- * {@inheritDoc}
149- */
150- public function with ()
151- {
152- $ this ->joinWith (func_get_args ());
153-
357+ $ this ->joinWith [] = (array )$ with ;
154358 return $ this ;
155359 }
156-
157- /**
158- * {@inheritdoc}
159- * @throws InvalidConfigException
160- */
161- public function populate ($ rows )
162- {
163- if (empty ($ rows )) {
164- return [];
165- }
166-
167- if ($ this ->asArray ) {
168- if ($ this ->indexBy ) {
169- return ArrayHelper::index ($ rows , $ this ->indexBy );
170- }
171-
172- return $ rows ;
173- }
174-
175- $ models = $ this ->createModels ($ rows );
176- foreach ($ models as $ model ) {
177- $ model ->afterFind ();
178- }
179-
180- return $ models ;
181- }
182-
183- /**
184- * {@inheritDoc}
185- */
186- protected function createModels ($ rows )
187- {
188- $ models = [];
189- /** @var ActiveRecord $class */
190- $ class = $ this ->modelClass ;
191- $ namespace = null ;
192- foreach ($ rows as $ row ) {
193- $ model = $ class ::instantiate ($ row );
194- $ relations = $ model ->getRelations ();
195- /** @var ActiveRecord $modelClass */
196- $ modelClass = get_class ($ model );
197- $ modelClass ::populateRecord ($ model , $ row );
198- $ models [] = $ model ;
199- if (is_array ($ this ->join )) {
200- foreach ($ this ->join as $ join ) {
201- $ relationRows = ArrayHelper::remove ($ row , $ join , []);
202- if (empty ($ relationRows )) {
203- continue ;
204- }
205- if (isset ($ relations [$ join ])) {
206- $ relationClass = $ relations [$ join ];
207- if (!class_exists ($ relationClass )) {
208- if (null === $ namespace && strpos ($ relationClass , '\\' ) === false ) {
209- $ r = new \ReflectionClass ($ this ->modelClass );
210- $ namespace = $ r ->getNamespaceName ();
211- }
212-
213- if (class_exists ($ namespace . '\\' . $ relationClass )) {
214- $ relationClass = $ namespace . '\\' . $ relationClass ;
215- }
216- }
217- if (class_exists ($ relationClass )) {
218- /** @var ActiveRecord $relationClass */
219- if (ArrayHelper::isAssociative ($ relationRows )) {
220- $ relationModel = $ relationClass ::instantiate ($ relationRows );
221- $ relationClass ::populateRecord ($ relationModel , $ relationRows );
222- $ model ->populateRelation ($ join , $ relationModel );
223- } else {
224- $ populatedRows = [];
225- foreach ($ relationRows as $ relationRow ) {
226- $ relationModel = $ relationClass ::instantiate ($ relationRow );
227- $ relationClass ::populateRecord ($ relationModel , $ relationRow );
228- $ populatedRows [] = $ relationModel ;
229- }
230- $ model ->populateRelation ($ join , $ populatedRows );
231- }
232- }
233- } else {
234- $ model ->populateRelation ($ join , ArrayHelper::remove ($ row , $ join , []));
235- }
236- }
237- }
238- }
239- return $ models ;
240- }
241360}
0 commit comments