Helpers
The file app/helpers.php is the project's single grab-bag of global helper functions. Composer autoloads it, so every function below is available without an import — you call res(), format_price($amount) or isWinch() from anywhere.
Helpers exist for two reasons: to make a common call shorter (e.g. res()->data(...) instead of constructing a response envelope by hand) and to keep a project convention enforceable in one place (e.g. combine_plate_parts(...) so plate strings are built the same way everywhere). Reach for an existing helper before you write the same logic inline a second time.
The list below is grouped by purpose. Every function in app/helpers.php is covered. If you don't see one here, it's not in the file.
Response envelope
One helper, used in every controller. See RESTful API for the full envelope shape.
| Helper | Signature | What it does |
|---|---|---|
res() | res(int $status = 200, ?string $message = null, array $headers = [], array $replaceAttributesMessage = []): ResponseBuilder | Returns a fresh ResponseBuilder. Always use this instead of response()->json(...) directly — it is what gives every endpoint the same {status, message, data} envelope. |
return res()->data($payload);
return res(422, 'validation.failed')->errors($errors);
Project flavor and features
The codebase ships two flavors — Winch and SaaS. These helpers gate code by flavor without sprinkling config('app.flavor') === ... checks. See Project flavor for the bigger picture and Feature flags for the feature-flag system.
| Helper | Signature | What it does |
|---|---|---|
projectFlavor() | projectFlavor(): ProjectFlavor | Returns the current ProjectFlavor enum. Delegates to ProjectFlavor::current(). |
isSaas() | isSaas(): bool | True when the current flavor is SaaS. |
isWinch() | isWinch(): bool | True when the current flavor is Winch. |
winchBranchesFeature() | winchBranchesFeature(): class-string<WinchBranchesFeature> | Returns the fully-qualified WinchBranchesFeature class string — used wherever a feature-flag check needs a class reference. |
if (isWinch()) {
return $this->winchPayload();
}
Feature::for($user)->active(winchBranchesFeature());
Notifications
Two helpers for outbound messages — one for app notifications, one for the Telegram error/log channel.
| Helper | Signature | What it does |
|---|---|---|
notify(array $data) | notify(array $data): void | Resolves the NotificationContract and dispatches the payload through SendNotificationData::fromArray($data). The single entry point for app notifications — never call the notification service directly. |
telegram(...) | telegram(array|string $text, ?string $template = 'local', array $info = []): void | Sends a message through TelegramLogger. Pass a string or an array of lines; $template and $info shape the formatted output. Used for ad-hoc operational alerts. |
Numbers and currency
Most projects accumulate a dozen near-duplicates here. We've tried to consolidate; the table below is what we have today. Read the description carefully — several of these helpers look similar but behave differently.
| Helper | Signature | What it does |
|---|---|---|
currency_symbol() | currency_symbol(): string | Returns the Saudi Riyal glyph (custom icon-font codepoint U+E800). Use this anywhere you need the currency symbol — never hardcode SAR or ر.س. |
format_price(...) | format_price(string $amount, ?string $lang = null): string | Formats "{symbol} {amount}" (en) or "{amount} {symbol}" (ar/other). Locale is read from $lang first, then app()->getLocale(). |
num_format($num) | num_format($num) | Custom 2-decimal truncation (not rounding) with sign preservation; trailing zero decimals stripped. Returns '0' when the result is falsy. |
invoice_num_format($num) | invoice_num_format($num) | number_format($num, 2, '.', '') with a trailing .00 stripped. Used for invoice line totals where group separators must be absent. |
comma_num_format($num) | comma_num_format($num) | Adds thousand-separator commas. Integers are formatted with no decimals; floats are rounded to 2 decimals with trailing zeros stripped. Returns 0 for an empty string. |
removeZeroDigitsFromDecimal($number, $decimal_sep = '.') | string | Strips a trailing .00 (or .0) from a numeric string. Building block for the formatters above; rarely called directly. |
points_round($number) | points_round($number) | floor($number) — used wherever loyalty/credit points must round down. Renamed from floor() to make the business intent explicit at the call site. |
extractDigits(?string $value = '') | ?string | Strips every non-digit character. Use for phone numbers, plate digits and any free-text field where only the digits matter. |
toArabicNumbers(?string $number = '') | ?string | Replaces 0–9 with ٠–٩. Use for any number that will render inside Arabic-language text where Eastern Arabic numerals are expected. |
current_balance($balance) | numeric | Returns $balance * -1. The legacy way to flip the sign on a stored balance for display. |
balance($balance, ?string $model = null) | numeric | Returns the balance with the sign flipped only when $model is a customer-side class (i.e. not a service provider). Use when the same number must read positive to one side and negative to the other. |
reasign_balance($balance) | numeric | Maps positive → negative, negative → positive (abs), zero stays zero. Used in legacy balance recalculations; prefer format_signed_balance() for display. |
balance_sign_position(?string $position = null, ?string $direction = null): string | 'prefix'|'suffix' | Decides whether the minus sign goes before or after the number. Reads config('balance.sign_position'), falls back to the language-direction map, and finally to config('balance.default_position', 'suffix'). |
balance_state($value): string | 'zero'|'negative'|'positive' | Bucket a numeric value, applying config('balance.zero_threshold', 0.01) so near-zero floats render as zero. |
format_signed_balance($value, ?string $position = null, ?string $direction = null): string | string | Formats a balance for display: thousands-grouped absolute value with the minus sign placed per balance_sign_position(). Non-negative values return the absolute formatted number with no sign. |
signed_raw_value($value, ?string $direction = null): string | string | Like format_signed_balance() but without thousand separators — used where the number must remain machine-parseable. |
display_balance($value, array $options = []): string | HTML | Builds the full balance widget: optional currency, optional colour class (from config('balance.colors.*')), optional flip, wrapped in <span class="...">. Returns escaped HTML. |
price_to_words(float $price, ?string $lang = null): string | string | Spells the price out in Arabic (default) or English, splitting whole and fractional parts and appending ريال/Riyals and هللة/Halalas. Used on printed invoices. |
balance, reasign_balance, current_balance, format_signed_balance, display_balance) exist for legacy reasons — they encode the sign convention that storage uses the inverse of what humans expect. When you touch a balance, prefer display_balance() for output and balance_state() for branching. Avoid inventing new sign-flipping logic.Dates and time
| Helper | Signature | What it does |
|---|---|---|
str_to_date($date) | mixed | Maps the strings 'day', 'week', 'month', 'year' to a Carbon/date value (today, 6 days ago, 29 days ago, current year). Used by dashboard filters. The branches return mixed types (Carbon vs int year) — callers must know which they asked for. |
human_timing($time) | string | Takes a number of seconds and returns an Arabic phrase like "٣ ساعة". Returns 0 (the integer) when the input is 0. Hard-coded Arabic — no English fallback. |
filter_month($users) | Collection | Buckets a collection of ['month' => 0..11, ...] rows into a flat, month-ordered list, dropping months with no entries. Used by month-by-month charts. |
chart_data_monthly($data) | array | Given a [monthNumber => [['count' => n], ...]] map (months 1–12), returns a 12-element array of summed counts in calendar order, zero-filling missing months. |
extract_count_in_days($data) | array | Groups a collection by created_at day-of-month and returns [day => count]. |
Strings and text
| Helper | Signature | What it does |
|---|---|---|
lreplace($search, $replace, $subject) | string | Replaces the last occurrence of $search with $replace. Used internally by convertRouteVerb(). |
remove_url_from_text(?string $text = null): ?string | ?string | Strips http(s)://... URLs and collapses extra whitespace. Returns the input unchanged when null/empty. |
replacePlaceholders(?string $body, ?array $replacement, ?bool $isSensitive = false) | string|null | Replaces {{ name }} / {{ 1 }} placeholders in a template. Numeric placeholders index into $replacement (1-based); named placeholders look up by key; unmatched placeholders fall back through $replacement in order. When $isSensitive, every replacement is rendered as XXXX — used to redact OTPs in logs. |
replaceHtmlTemplate(?string $html = null, ?string $lang = null): ?string | ?string | Parses an HTML fragment, then for each element decides whether to add direction: ltr or direction: rtl; text-align: right; based on existing styles and $lang. Used to normalise WYSIWYG output to the active locale. |
formatDomTextDirection($text) | string | In Arabic locale only, wraps runs of Latin/digits/punctuation embedded inside Arabic text with <span dir="ltr">…</span>. Preserves URLs and existing HTML tags. Returns the input unchanged in non-Arabic locales. |
ar_en_languages() | array | Returns the subset of config('languages') whose keys are ar or en (preserving keys). |
sql_ar_query($text) | string | Escapes regex metacharacters in $text, then expands Arabic letters to character classes (e.g. أ → (أ|ا|آ|إ)) so a REGEXP query matches across common spelling variants. |
get_starred($str) | string | Replaces the middle of each whitespace-separated token with ***, keeping the first and last character. Used to redact names in shareable logs. |
URLs and routes
| Helper | Signature | What it does |
|---|---|---|
localize_route($name, $parameters = [], $absolute = true) | string | Builds a route URL prefixed with the current locale — route("{locale}.{name}", ...). |
is_current_localize_route($name) | bool | True when the current route's locale-stripped name equals $name. |
route_unlocalize() | string | Returns the current route name with the ar./en. prefix removed when present. |
route_with_locale() | string | Returns the URL of the opposite locale for the current route, falling back to {otherLocale}.home when no equivalent route exists. Powers the "EN ↔ AR" language toggle. |
replace_url_parameters($url, $newParams) | string | Parses a URL, merges $newParams into its query string, returns the rebuilt URL. Assumes the URL has a query string — it will warn on URLs without one. |
get_url_parameter($url, $param) | mixed|null | Reads a single query-string parameter from a URL. |
convertRouteVerb($route) | ?string | Converts a *.create route name to *.store and *.edit to *.update; returns null for routes starting with ajax.. Used by the admin scaffolding to map form actions to their POST endpoints. |
getQueryParams($url) | array | Parses a URL and returns its query parameters as an associative array. Returns [] when the URL has no query string. |
Files and assets
| Helper | Signature | What it does |
|---|---|---|
images($collection) | App\Packages\Images | Constructs the project's Images wrapper around the given Eloquent collection. The wrapper exposes the upload/URL helpers used in views. |
files($collection) | App\Packages\Files | Same shape as images() but for arbitrary file attachments. |
fetch_s3_file_content($url) | string | Downloads a URL via curl with SSL verification disabled and returns the body when HTTP 200. On any other status, returns an error string — not an exception. Callers must check for the "Failed to fetch file..." prefix. |
file_to_base64($url) | string | Calls fetch_s3_file_content() then base64-encodes the result with a data:image/{ext};base64, prefix. Returns '' on exception (silent failure). |
assets_files_version() | assets_files_version(): string | Returns the asset cache-busting version string (currently '1.5.78'). Bump this when you ship a frontend asset change that must invalidate browser caches. |
excel_text_style($value) | string | Returns an inline CSS color:#...; for a number — green when positive, red when negative, dark grey for zero. Used in Excel exports. |
invoice_pdf_cols_width($statement, $orderNo) | array | Returns the column-width map for an invoice PDF table. Four variants based on whether the invoice has an order_no column and/or a statement column. |
can_export_data($count) | bool | False when $count > 10000. The single place that enforces the export row cap. |
get_colors_by_count($count) | string | Returns 'success' (>= 20), 'warning' (> 0 && < 20), or 'danger' (otherwise). Dashboard badge colour mapping. |
chartColorPalettes() | chartColorPalettes(): array | Returns the named colour palettes (set2, viridis, tableau, modern) available to charts. |
resolveChartColors(int $count, ?string $scheme = null): array | array | Returns $count colours from the chosen palette (set2 by default, or request('color_scheme')). When $count exceeds the palette size, generates evenly-spaced HSL colours instead. |
Users and types
These wrap the SysUserContract so callers don't have to resolve it themselves. See Domain boundaries for why cross-domain user lookups go through this contract.
| Helper | Signature | What it does |
|---|---|---|
class_user_type(?string $type): ?string | ?string | Given a short user-type string (e.g. 'driver'), returns the FQCN of the matching model. Returns null when $type is null. |
get_user_type(Model|string $model): ?string | ?string | Inverse of class_user_type() — returns the short type string for a model instance or class string. |
get_user_label(Model $model) | string | Returns the human-readable label for a user model (used in admin tables and audit logs). |
is_service_provider(string $modelClass) | bool | True when the given class string is a service-provider type. |
get_users_search_route($type, array $excludeIds = [], int|array|null $winchBranch = null) | string | Builds the AJAX search route URL for a user type — route('ajax.{type}s.search', [...]). Used by select2-style pickers. |
tmp_get_user_api() | User|null | Returns the authenticated user from the api-users guard, falling back to api-branches. The tmp_ prefix signals this is a temporary shim — do not extend its use. |
tmp_get_user_type($user) | string | Returns 'user' when $user->id falls inside the 40000001–49999999 range, otherwise 'company'. Legacy ID-range mapping — do not extend. |
tmp_* helpers encode a legacy ID-range and a legacy guard order. They exist so older code keeps working — new code should resolve users explicitly through the appropriate guard or the SysUserContract.Geography and data
| Helper | Signature | What it does |
|---|---|---|
json_KSA_coordinates() | mixed | Returns the coordinates column for App\Country::find(1) (Saudi Arabia). |
combine_plate_parts(string $right, string $middle, string $left): string | string | Concatenates the three plate parts ({left}{middle}{right}) and translates each Arabic letter to its character-code equivalent via config('plate_characters.letters'). Unknown characters pass through untouched. |
get_vat_registration_no($createdAt) | string | Returns the VAT registration number that was in effect on $createdAt: '310316756600003' for entries before 2022-05-01, '311290634100003' from that date on. Used on invoices issued for historical records. |
Pagination and collections
| Helper | Signature | What it does |
|---|---|---|
paginate($items, $perPage = 15, $page = null, $options = []) | LengthAwarePaginator | Paginates an arbitrary array or collection — the manual equivalent of Eloquent's ->paginate(). Use this when you have an in-memory collection that needs paginator output. |
set_collection_after_map($model, $mapping) | LengthAwarePaginator | Re-attaches a mapped collection to a paginator ($model->setCollection(collect($mapping))). The standard way to transform paginator items without losing the paginator wrapper. |
add_rowspan($tableKeys, $rows) | array | Walks $rows and, for each key in $tableKeys, sets {key}_rowspan on the first row of each run of equal values (and 0 on the rest). Lets a Blade table render a single tall cell for consecutive equal values. |
compareOrderCounts($previousMonthOrders, $currentMonthOrders) | array | Returns ['difference', 'arrow' (font-awesome class), 'color', 'percentage_change' (string)] comparing two counts. Used by KPI cards on the dashboard. |
When to add a new helper
A new function in app/helpers.php is a permanent commitment — it is autoloaded globally and visible to every contributor. The bar is high:
- The function will have three or more call sites within a reasonable horizon (not "could be useful eventually").
- It encodes a project convention (formatting, sign handling, route building) rather than a personal preference.
- No existing class, trait or DTO is a better home — class-level helpers are easier to find and easier to test.
- The behaviour fits in roughly 30 lines and has no constructor-time dependencies.
- The name reads as English (
format_price,is_service_provider) and follows the snake_case convention already used in the file.
If the function depends on state, talks to the database, or returns a domain object, it probably belongs in an Action or a Service — not here. See Actions and Services and contracts.
What to read next
- RESTful API — the response envelope built by
res(). - Project flavor — the system behind
isWinch()/isSaas(). - Feature flags — how
winchBranchesFeature()is wired into the flag system. - Code style — naming and PHPDoc rules apply to helpers too.
Platform Apps
How the project wraps every third-party vendor — Wathq, Yaqeen, payment gateways, AI providers — behind a uniform Connector/Request/Contract shape under `src/Domain/Integration/{Vendor}/`.
Admin CRUD
Step-by-step recipe for adding a new admin dashboard page — controller, route, views, permissions, translations, and sidebar menu — in the order to do them.