11<?php
22
33use Fooino \Core \Exceptions \FooinoException ;
4+ use Fooino \Core \Exceptions \TransactionRollBackedException ;
45use Fooino \Core \Facades \Date ;
56use Fooino \Core \Facades \Json ;
67use Fooino \Core \Facades \Math ;
78
89use Fooino \Core \Interfaces \Mathable ;
910use Fooino \Core \Support \Sanitizer ;
11+
12+ use Illuminate \Database \Eloquent \Casts \Attribute ;
13+ use Illuminate \Database \Eloquent \Model ;
1014use Illuminate \Http \JsonResponse ;
15+ use Illuminate \Http \Request ;
16+ use Illuminate \Foundation \Auth \User ;
17+ use Illuminate \Foundation \Http \FormRequest ;
18+ use Illuminate \Support \Facades \DB ;
1119
1220if (!defined ('CONSTANTS_DEFINED ' )) {
1321
@@ -321,6 +329,55 @@ function nullIfBlankOrZero(int|float|string|null|bool|array|object|callable $val
321329 }
322330}
323331
332+ if (!function_exists ('nullIfBlankInput ' )) {
333+ /**
334+ * Retrieve an input value from the request and return null if it is blank
335+ */
336+ function nullIfBlankInput (string $ key , int |float |string |null |bool |array |object |callable $ fallback = null , Request |null $ request = null ): int |float |string |null |bool |array |object |callable
337+ {
338+ $ request ??= request ();
339+
340+ return nullIfBlank (value: $ request ->input ($ key ), fallback: $ fallback );
341+ }
342+ }
343+
344+ if (!function_exists ('unwrapBackedEnum ' )) {
345+ /**
346+ * Unwrap a backed enum to its scalar value, or return the value unchanged if it is not a backed enum
347+ */
348+ function unwrapBackedEnum (int |float |string |null |bool |array |object $ object ): int |float |string |null |bool |array |object
349+ {
350+ return ($ object instanceof \BackedEnum) ? $ object ->value : $ object ;
351+ }
352+ }
353+
354+ if (!function_exists ('mergeArraysByKey ' )) {
355+ /**
356+ * Merge multiple arrays by grouping values under shared keys into sub-arrays
357+ */
358+ function mergeArraysByKey (array ...$ arrays ): array
359+ {
360+ $ merged = [];
361+
362+ foreach ($ arrays as $ array ) {
363+ foreach ($ array as $ key => $ value ) {
364+ if (!isset ($ merged [$ key ])) {
365+ $ merged [$ key ] = [];
366+ }
367+
368+ if (is_array ($ value )) {
369+ $ merged [$ key ] = array_merge ($ merged [$ key ], $ value );
370+ } else {
371+ $ merged [$ key ][] = $ value ;
372+ }
373+ }
374+ }
375+
376+ return $ merged ;
377+ }
378+ }
379+
380+
324381if (!function_exists ('removeComma ' )) {
325382 /**
326383 * Remove comma between letters when the value is string or array
@@ -361,6 +418,26 @@ function replaceSlashToDash(int|float|string|null|bool|array $value): int|float|
361418 }
362419}
363420
421+ if (!function_exists ('setUserTimezone ' )) {
422+ /**
423+ * Store the user timezone in the config so it can be retrieved later
424+ */
425+ function setUserTimezone (string $ timezone ): void
426+ {
427+ config (['user-timezone ' => $ timezone ]);
428+ }
429+ }
430+
431+ if (!function_exists ('getUserTimezone ' )) {
432+ /**
433+ * Retrieve the user timezone from config, falling back to UTC
434+ */
435+ function getUserTimezone (): string
436+ {
437+ return (config ('user-timezone ' , 'UTC ' )) ?: 'UTC ' ;
438+ }
439+ }
440+
364441if (!function_exists ('setDefaultLocale ' )) {
365442 /**
366443 * Setter for 'app.locale' config
@@ -381,6 +458,20 @@ function getDefaultLocale(): string
381458 }
382459}
383460
461+ if (!function_exists ('perPage ' )) {
462+ /**
463+ * Resolve the per-page value from the request with validation against a maximum
464+ */
465+ function perPage (string $ key = 'per_page ' , int $ maxPerPage = 300 , Request |null $ request = null ): int
466+ {
467+ $ request ??= request ();
468+
469+ $ perPage = $ request ->input ($ key );
470+
471+ return (is_null ($ perPage ) || !is_numeric ($ perPage ) || $ perPage <= 0 || $ perPage > $ maxPerPage ) ? FOOINO_PER_PAGE : $ perPage ;
472+ }
473+ }
474+
384475if (!function_exists ('currentDate ' )) {
385476 /**
386477 * Return date in 'Y-m-d' format
@@ -587,3 +678,156 @@ function sanitizeSlug(string|int|float|null|bool|array $value): string|int|float
587678 ->value ();
588679 }
589680}
681+
682+ if (!function_exists ('jsonAttribute ' )) {
683+ /**
684+ * Cast an Eloquent attribute to/from JSON automatically
685+ */
686+ function jsonAttribute (): Attribute
687+ {
688+ return Attribute::make (
689+ get: fn ($ value ) => !is_null (nullIfBlank ($ value )) ? jsonDecodeToArray ($ value ) : [],
690+ set: fn ($ value ) => !is_null (nullIfBlank ($ value )) ? jsonEncode ($ value ) : null ,
691+ );
692+ }
693+ }
694+
695+
696+
697+ if (!function_exists ('resolveRequest ' )) {
698+ /**
699+ * Resolve and validate a FormRequest with the given data and optional authenticated user
700+ */
701+ function resolveRequest (string $ request , array $ data = [], User |null $ user = null ): FormRequest
702+ {
703+ $ req = new $ request ();
704+
705+ $ req ->merge ($ data );
706+
707+ if (!is_null ($ user )) {
708+ $ req ->setUserResolver (fn () => $ user );
709+ }
710+
711+ $ container = app ();
712+
713+ $ req
714+ ->setContainer ($ container )
715+ ->setRedirector ($ container ->make ('redirect ' ))
716+ ->validateResolved ();
717+
718+ return $ req ;
719+ }
720+ }
721+
722+ if (!function_exists ('dbTransaction ' )) {
723+ /**
724+ * Execute a callback within a database transaction, rethrowing any exception as a TransactionRollBackedException
725+ */
726+ function dbTransaction (callable $ callback ): mixed
727+ {
728+ try {
729+ DB ::beginTransaction ();
730+
731+ $ result = $ callback ();
732+
733+ DB ::commit ();
734+
735+ return $ result ;
736+
737+ //
738+ } catch (FooinoException | Exception $ e ) {
739+
740+ DB ::rollBack ();
741+
742+ app (TransactionRollBackedException::class)
743+ ->setMessage ($ e ->getMessage ())
744+ ->setCode ($ e ->getCode ())
745+ ->setLevel (callMethodIfExists (object: $ e , method: 'getLevel ' , fallback: 'error ' ))
746+ ->report (callMethodIfExists (object: $ e , method: 'reportable ' , fallback: true ))
747+ ->setHttpStatusCode (callMethodIfExists (object: $ e , method: 'getHttpStatusCode ' , fallback: 500 ))
748+ ->with (callMethodIfExists (object: $ e , method: 'getWith ' , fallback: []))
749+ ->throw ();
750+ }
751+ }
752+ }
753+
754+
755+ if (!function_exists ('userInfo ' )) {
756+ /**
757+ * Extract user information from a loaded polymorphic relationship
758+ */
759+ function userInfo (Model $ model , string $ relation ): array
760+ {
761+
762+ if (!$ model ->relationLoaded ($ relation )) {
763+
764+ return [
765+ 'id ' => 0 ,
766+ 'country_id ' => 0 ,
767+ 'full_name ' => '' ,
768+ 'country_code ' => '' ,
769+ 'phone_number ' => '' ,
770+ 'phone_number_original ' => '' ,
771+ 'type ' => __ (key: 'msg.unknown ' ),
772+ ];
773+ }
774+
775+ $ user = $ model ?->{$ relation };
776+
777+ if (is_null ($ user )) {
778+ return [
779+ 'id ' => 0 ,
780+ 'country_id ' => 0 ,
781+ 'full_name ' => '' ,
782+ 'country_code ' => '' ,
783+ 'phone_number ' => '' ,
784+ 'phone_number_original ' => '' ,
785+ 'type ' => __ (key: 'msg.unknown ' ),
786+ ];
787+ }
788+
789+ return [
790+ 'id ' => (float ) ($ user ?->id ?? 0 ),
791+
792+ 'country_id ' => (float ) ($ user ?->country_id ?? 0 ),
793+
794+ 'full_name ' => (string ) ($ user ?->full_name ?? $ user ?->name ?? trim (($ user ?->first_name ?? '' ) . ' ' . ($ user ?->last_name ?? '' ))),
795+
796+ 'country_code ' => (string ) ($ user ?->country_code ?? '' ),
797+
798+ 'phone_number ' => (string ) ($ user ?->phone_number ?? '' ),
799+
800+ 'phone_number_original ' => (string ) ($ user ?->getRawOriginal('phone_number ' , '' ) ?? '' ),
801+
802+ 'type ' => callMethodIfExists (object: $ user , method: 'objectName ' , fallback: [])['type ' ] ?? __ (key: 'msg.unknown ' ),
803+ ];
804+ }
805+ }
806+
807+ if (!function_exists ('getUserable ' )) {
808+ /**
809+ * Resolve the authenticated user into polymorphic relation columns (e.g. creatorable_type, creatorable_id)
810+ */
811+ function getUserable (string $ able , Request |Model |null $ user = null , string $ guard = 'web ' , bool $ throwException = false ): array
812+ {
813+ $ user = match (true ) {
814+
815+ ($ user instanceof Request) => $ user ->user (guard: $ guard ),
816+
817+ ($ user instanceof Model) => $ user ,
818+
819+ default => request ()->user (guard: $ guard )
820+ };
821+
822+ $ id = $ user ?->id ?? null ;
823+
824+ if ($ throwException && (blank ($ user ) || blank ($ id ))) {
825+ throw new Exception ('The user is empty ' );
826+ }
827+
828+ return [
829+ ($ able . '_type ' ) => (filled ($ user ) && filled ($ id )) ? get_class ($ user ) : null ,
830+ ($ able . '_id ' ) => $ id ,
831+ ];
832+ }
833+ }
0 commit comments