1+ <?php
2+
3+ namespace Marsflow \CachedDataTable ;
4+
5+ use Marsflow \CachedDataTable \CachedQueryDataTable ;
6+
7+ use Illuminate \Database \Eloquent \Builder ;
8+ use Illuminate \Database \Eloquent \Relations \BelongsTo ;
9+ use Illuminate \Database \Eloquent \Relations \BelongsToMany ;
10+ use Illuminate \Database \Eloquent \Relations \HasOneOrMany ;
11+ use Illuminate \Database \Eloquent \Relations \HasOneThrough ;
12+ use Illuminate \Database \Eloquent \Relations \MorphTo ;
13+ use Illuminate \Database \Eloquent \Relations \Relation ;
14+ use Yajra \DataTables \Exceptions \Exception ;
15+ use Yajra \DataTables \QueryDataTable ;
16+
17+ class CachedEloquentDataTable extends CachedQueryDataTable
18+ {
19+
20+ /**
21+ * @var \Illuminate\Database\Eloquent\Builder
22+ */
23+ protected $ query ;
24+
25+ /**
26+ * Can the DataTable engine be created with these parameters.
27+ *
28+ * @param mixed $source
29+ * @return bool
30+ */
31+ public static function canCreate ($ source )
32+ {
33+ return $ source instanceof Builder || $ source instanceof Relation;
34+ }
35+
36+ /**
37+ * EloquentEngine constructor.
38+ *
39+ * @param mixed $model
40+ */
41+ public function __construct ($ model )
42+ {
43+ $ builder = $ model instanceof Builder ? $ model : $ model ->getQuery ();
44+ parent ::__construct ($ builder ->getQuery ());
45+
46+ $ this ->query = $ builder ;
47+ }
48+
49+ /**
50+ * Add columns in collection.
51+ *
52+ * @param array $names
53+ * @param bool|int $order
54+ * @return $this
55+ */
56+ public function addColumns (array $ names , $ order = false )
57+ {
58+ foreach ($ names as $ name => $ attribute ) {
59+ if (is_int ($ name )) {
60+ $ name = $ attribute ;
61+ }
62+
63+ $ this ->addColumn ($ name , function ($ model ) use ($ attribute ) {
64+ return $ model ->getAttribute ($ attribute );
65+ }, is_int ($ order ) ? $ order ++ : $ order );
66+ }
67+
68+ return $ this ;
69+ }
70+
71+ /**
72+ * If column name could not be resolved then use primary key.
73+ *
74+ * @return string
75+ */
76+ protected function getPrimaryKeyName ()
77+ {
78+ return $ this ->query ->getModel ()->getKeyName ();
79+ }
80+
81+ /**
82+ * Compile query builder where clause depending on configurations.
83+ *
84+ * @param mixed $query
85+ * @param string $columnName
86+ * @param string $keyword
87+ * @param string $boolean
88+ */
89+ protected function compileQuerySearch ($ query , $ columnName , $ keyword , $ boolean = 'or ' )
90+ {
91+ $ parts = explode ('. ' , $ columnName );
92+ $ column = array_pop ($ parts );
93+ $ relation = implode ('. ' , $ parts );
94+
95+ if ($ this ->isNotEagerLoaded ($ relation )) {
96+ return parent ::compileQuerySearch ($ query , $ columnName , $ keyword , $ boolean );
97+ }
98+
99+ if ($ this ->isMorphRelation ($ relation )) {
100+ $ query ->{$ boolean . 'WhereHasMorph ' }($ relation , '* ' , function (Builder $ query ) use ($ column , $ keyword ) {
101+ parent ::compileQuerySearch ($ query , $ column , $ keyword , '' );
102+ });
103+ } else {
104+ $ query ->{$ boolean . 'WhereHas ' }($ relation , function (Builder $ query ) use ($ column , $ keyword ) {
105+ parent ::compileQuerySearch ($ query , $ column , $ keyword , '' );
106+ });
107+ }
108+ }
109+
110+ /**
111+ * Resolve the proper column name be used.
112+ *
113+ * @param string $column
114+ * @return string
115+ */
116+ protected function resolveRelationColumn ($ column )
117+ {
118+ $ parts = explode ('. ' , $ column );
119+ $ columnName = array_pop ($ parts );
120+ $ relation = implode ('. ' , $ parts );
121+
122+ if ($ this ->isNotEagerLoaded ($ relation )) {
123+ return $ column ;
124+ }
125+
126+ return $ this ->joinEagerLoadedColumn ($ relation , $ columnName );
127+ }
128+
129+ /**
130+ * Check if a relation is a morphed one or not.
131+ *
132+ * @param string $relation
133+ * @return bool
134+ */
135+ protected function isMorphRelation ($ relation )
136+ {
137+ $ isMorph = false ;
138+ if ($ relation !== null && $ relation !== '' ) {
139+ $ relationParts = explode ('. ' , $ relation );
140+ $ firstRelation = array_shift ($ relationParts );
141+ $ model = $ this ->query ->getModel ();
142+ $ isMorph = method_exists ($ model , $ firstRelation ) && $ model ->$ firstRelation () instanceof MorphTo;
143+ }
144+
145+ return $ isMorph ;
146+ }
147+
148+ /**
149+ * Check if a relation was not used on eager loading.
150+ *
151+ * @param string $relation
152+ * @return bool
153+ */
154+ protected function isNotEagerLoaded ($ relation )
155+ {
156+ return ! $ relation
157+ || ! array_key_exists ($ relation , $ this ->query ->getEagerLoads ())
158+ || $ relation === $ this ->query ->getModel ()->getTable ();
159+ }
160+
161+ /**
162+ * Join eager loaded relation and get the related column name.
163+ *
164+ * @param string $relation
165+ * @param string $relationColumn
166+ * @return string
167+ * @throws \Yajra\DataTables\Exceptions\Exception
168+ */
169+ protected function joinEagerLoadedColumn ($ relation , $ relationColumn )
170+ {
171+ $ table = '' ;
172+ $ lastQuery = $ this ->query ;
173+ foreach (explode ('. ' , $ relation ) as $ eachRelation ) {
174+ $ model = $ lastQuery ->getRelation ($ eachRelation );
175+ switch (true ) {
176+ case $ model instanceof BelongsToMany:
177+ $ pivot = $ model ->getTable ();
178+ $ pivotPK = $ model ->getExistenceCompareKey ();
179+ $ pivotFK = $ model ->getQualifiedParentKeyName ();
180+ $ this ->performJoin ($ pivot , $ pivotPK , $ pivotFK );
181+
182+ $ related = $ model ->getRelated ();
183+ $ table = $ related ->getTable ();
184+ $ tablePK = $ related ->getForeignKey ();
185+ $ foreign = $ pivot . '. ' . $ tablePK ;
186+ $ other = $ related ->getQualifiedKeyName ();
187+
188+ $ lastQuery ->addSelect ($ table . '. ' . $ relationColumn );
189+ $ this ->performJoin ($ table , $ foreign , $ other );
190+
191+ break ;
192+
193+ case $ model instanceof HasOneThrough:
194+ $ pivot = explode ('. ' , $ model ->getQualifiedParentKeyName ())[0 ]; // extract pivot table from key
195+ $ pivotPK = $ pivot . '. ' . $ model ->getLocalKeyName ();
196+ $ pivotFK = $ model ->getQualifiedLocalKeyName ();
197+ $ this ->performJoin ($ pivot , $ pivotPK , $ pivotFK );
198+
199+ $ related = $ model ->getRelated ();
200+ $ table = $ related ->getTable ();
201+ $ tablePK = $ related ->getForeignKey ();
202+ $ foreign = $ pivot . '. ' . $ tablePK ;
203+ $ other = $ related ->getQualifiedKeyName ();
204+
205+ break ;
206+
207+ case $ model instanceof HasOneOrMany:
208+ $ table = $ model ->getRelated ()->getTable ();
209+ $ foreign = $ model ->getQualifiedForeignKeyName ();
210+ $ other = $ model ->getQualifiedParentKeyName ();
211+ break ;
212+
213+ case $ model instanceof BelongsTo:
214+ $ table = $ model ->getRelated ()->getTable ();
215+ $ foreign = $ model ->getQualifiedForeignKeyName ();
216+ $ other = $ model ->getQualifiedOwnerKeyName ();
217+ break ;
218+
219+ default :
220+ throw new Exception ('Relation ' . get_class ($ model ) . ' is not yet supported. ' );
221+ }
222+ $ this ->performJoin ($ table , $ foreign , $ other );
223+ $ lastQuery = $ model ->getQuery ();
224+ }
225+
226+ return $ table . '. ' . $ relationColumn ;
227+ }
228+
229+ /**
230+ * Perform join query.
231+ *
232+ * @param string $table
233+ * @param string $foreign
234+ * @param string $other
235+ * @param string $type
236+ */
237+ protected function performJoin ($ table , $ foreign , $ other , $ type = 'left ' )
238+ {
239+ $ joins = [];
240+ foreach ((array ) $ this ->getBaseQueryBuilder ()->joins as $ key => $ join ) {
241+ $ joins [] = $ join ->table ;
242+ }
243+
244+ if (! in_array ($ table , $ joins )) {
245+ $ this ->getBaseQueryBuilder ()->join ($ table , $ foreign , '= ' , $ other , $ type );
246+ }
247+ }
248+ }
0 commit comments