@@ -143,21 +143,31 @@ private function getTableMap()
143143 */
144144 protected function getPkDbName ()
145145 {
146- $ tableMap = $ this ->getTableMap ();
146+ $ tableMap = $ this ->requireTableMapForPrimaryKeys ();
147147 $ tableName = $ this ->resolveTableName ($ tableMap );
148148 $ pks = $ tableMap ->getPrimaryKeys ();
149- if (count ($ pks ) === 1 ) {
150- $ pks = array_keys ($ pks );
151- return [
152- $ tableName . '. ' . $ pks [0 ] => Api::API_MODEL_KEY_FIELD
153- ];
149+ $ pkCount = count ($ pks );
150+ if ($ pkCount === 1 ) {
151+ return $ this ->buildSinglePkMap ($ tableName , $ pks );
154152 }
155- if (count ( $ pks ) > 1 ) {
153+ if ($ pkCount > 1 ) {
156154 return $ this ->buildCompositePkMap ($ tableName , $ pks );
157155 }
158156 throw new ApiException (t ('The API model is not properly mapped, there is no Primary Key or it is composite ' ));
159157 }
160158
159+ /**
160+ * @throws ApiException
161+ */
162+ private function requireTableMapForPrimaryKeys (): TableMap
163+ {
164+ $ tableMap = $ this ->getTableMap ();
165+ if (!$ tableMap instanceof TableMap) {
166+ throw new ApiException (t ('The API model is not properly mapped, there is no Primary Key or it is composite ' ));
167+ }
168+ return $ tableMap ;
169+ }
170+
161171 private function resolveTableName (TableMap $ tableMap ): string
162172 {
163173 $ tableMapClass = get_class ($ tableMap );
@@ -173,25 +183,50 @@ private function resolveTableName(TableMap $tableMap): string
173183 return $ tableName ;
174184 }
175185
186+ /**
187+ * @param array<int, ColumnMap> $primaryKeys
188+ * @return array<string, string>
189+ */
190+ private function buildSinglePkMap (string $ tableName , array $ primaryKeys ): array
191+ {
192+ $ pkKeys = array_keys ($ primaryKeys );
193+ $ pkName = (string )($ pkKeys [0 ] ?? '' );
194+
195+ return [
196+ $ tableName . '. ' . $ pkName => Api::API_MODEL_KEY_FIELD ,
197+ ];
198+ }
199+
176200 /**
177201 * @param array<int, ColumnMap> $primaryKeys
178202 * @return array<string, string>
179203 */
180204 private function buildCompositePkMap (string $ tableName , array $ primaryKeys ): array
181205 {
182206 $ apiPks = [];
183- $ principal = '' ;
184- $ sep = 'CONCAT( ' ;
207+ $ segments = [];
185208 foreach ($ primaryKeys as $ pk ) {
186209 $ apiPks [$ tableName . '. ' . $ pk ->getName ()] = $ pk ->getPhpName ();
187- $ principal .= $ sep . $ tableName . '. ' . $ pk ->getName ();
188- $ sep = ', " ' . Api::API_PK_SEPARATOR . '", ' ;
210+ $ segments [] = $ tableName . '. ' . $ pk ->getName ();
189211 }
190- $ principal .= ' ) ' ;
212+ $ principal = $ this -> buildCompositePkExpression ( $ segments ) ;
191213 $ apiPks [$ principal ] = Api::API_MODEL_KEY_FIELD ;
192214 return $ apiPks ;
193215 }
194216
217+ /**
218+ * @param array<int, string> $segments
219+ */
220+ private function buildCompositePkExpression (array $ segments ): string
221+ {
222+ if (count ($ segments ) === 0 ) {
223+ return 'CONCAT() ' ;
224+ }
225+
226+ $ glue = ', " ' . Api::API_PK_SEPARATOR . '", ' ;
227+ return 'CONCAT( ' . implode ($ glue , $ segments ) . ') ' ;
228+ }
229+
195230 /**
196231 * @throws ApiException
197232 */
@@ -207,13 +242,24 @@ protected function addPkToList()
207242 */
208243 private function addClassListName (TableMap $ tableMap )
209244 {
210- $ pks = '' ;
211- $ sep = '' ;
245+ $ segments = [];
212246 foreach ($ tableMap ->getPrimaryKeys () as $ pk ) {
213- $ pks .= $ sep . $ pk ->getFullyQualifiedName ();
214- $ sep = ', "|", ' ;
247+ $ segments [] = $ pk ->getFullyQualifiedName ();
215248 }
216- $ this ->extraColumns ['CONCAT(" ' . $ tableMap ->getPhpName () . ' #", ' . $ pks . ') ' ] = Api::API_LIST_NAME_FIELD ;
249+
250+ $ this ->extraColumns [$ this ->buildClassListNameExpression ((string )$ tableMap ->getPhpName (), $ segments )] = Api::API_LIST_NAME_FIELD ;
251+ }
252+
253+ /**
254+ * @param array<int, string> $segments
255+ */
256+ private function buildClassListNameExpression (string $ phpName , array $ segments ): string
257+ {
258+ if (count ($ segments ) === 0 ) {
259+ return 'CONCAT(" ' . $ phpName . ' #") ' ;
260+ }
261+
262+ return 'CONCAT(" ' . $ phpName . ' #", ' . implode (', "|", ' , $ segments ) . ') ' ;
217263 }
218264
219265
@@ -237,22 +283,38 @@ protected function addDefaultListField()
237283 */
238284 private function addExtraColumns (ModelCriteria &$ query , $ action )
239285 {
240- if (Api::API_ACTION_LIST === $ action ) {
241- // Legacy tokens kept for compatibility (`__name__`, `__pk`).
242- // Planned future cleanup can remove them behind a versioned contract switch.
243- $ this ->addDefaultListField ();
244- $ this ->addPkToList ();
286+ $ this ->prepareLegacyListExtraColumns ((string )$ action );
287+ if (empty ($ this ->extraColumns )) {
288+ return ;
245289 }
246- if (!empty ($ this ->extraColumns )) {
247- $ fields = $ this ->resolveRequestedExtraFields ();
248- foreach ($ this ->extraColumns as $ expression => $ columnName ) {
249- if (empty ($ fields ) || in_array ($ columnName , $ fields , true )) {
250- $ query ->withColumn ($ expression , $ columnName );
251- }
290+
291+ $ fields = $ this ->resolveRequestedExtraFields ();
292+ foreach ($ this ->extraColumns as $ expression => $ columnName ) {
293+ if ($ this ->shouldIncludeExtraColumn ($ columnName , $ fields )) {
294+ $ query ->withColumn ($ expression , $ columnName );
252295 }
253296 }
254297 }
255298
299+ private function prepareLegacyListExtraColumns (string $ action ): void
300+ {
301+ if (Api::API_ACTION_LIST !== $ action ) {
302+ return ;
303+ }
304+ // Legacy tokens kept for compatibility (`__name__`, `__pk`).
305+ // Planned future cleanup can remove them behind a versioned contract switch.
306+ $ this ->addDefaultListField ();
307+ $ this ->addPkToList ();
308+ }
309+
310+ /**
311+ * @param array<int, string> $fields
312+ */
313+ private function shouldIncludeExtraColumn (string $ columnName , array $ fields ): bool
314+ {
315+ return empty ($ fields ) || in_array ($ columnName , $ fields , true );
316+ }
317+
256318 /**
257319 * @return array
258320 */
@@ -338,10 +400,21 @@ protected function resolveRequestedExtraFields(): array
338400 if (Config::getParam ('api.extrafields.compat ' , true )) {
339401 return array_values ($ this ->extraColumns );
340402 }
341- $ returnFields = Request::getInstance ()->getQuery (Api::API_FIELDS_RESULT_FIELD );
342- $ fields = explode (', ' , $ returnFields ?: '' );
403+ return $ this ->normalizeRequestedExtraFields (
404+ (string )(Request::getInstance ()->getQuery (Api::API_FIELDS_RESULT_FIELD ) ?? '' )
405+ );
406+ }
407+
408+ /**
409+ * @return array<int, string>
410+ */
411+ private function normalizeRequestedExtraFields (string $ returnFields ): array
412+ {
413+ $ fields = explode (', ' , $ returnFields );
343414 $ fields [] = self ::API_MODEL_KEY_FIELD ;
344- return $ fields ;
415+ $ fields = array_values (array_filter (array_map ('trim ' , $ fields ), static fn (string $ field ): bool => $ field !== '' ));
416+
417+ return array_values (array_unique ($ fields ));
345418 }
346419
347420 protected function sanitizeString (string $ value ): string
0 commit comments