Back to Course |
How to Structure Laravel 11 Projects

Global Helpers: Last Resort

This lesson is the last part of the section about the app/ folder structure. What if you have some methods for transforming data but don't know where to place them, as they may be used in various places?

The most flexible "last resort" is to create global helpers.

Helper classes have been around for a while. It's any Class with some helper methods related to some topic, like DateHelper or CurrencyHelper.

For example, earlier, we created the Attribute startAt() in the User model, which has some data manipulation that can be added to a helper.

Model:

protected function startAt(): Attribute
{
return Attribute::make(
set: fn ($value) => Carbon::createFromFormat('m/d/Y', $value)->format('Y-m-d');
)
}

Create a new file app/Helpers/DateHelper.php (there's no Artisan command for this), and inside the DateHelper class, add the convertToDB() method, which will have the code from the attribute.

namespace App\Helpers;
 
class DateHelper
{
public static function convertToDB($date)
{
return Carbon::createFromFormat('m/d/Y', $date)->format('Y-m-d');
}
}

Now you can change Attribute to use this helper:

protected function startAt(): Attribute
{
return Attribute::make(
set: fn ($value) => DateHelper::convertToDB($value);
)
}

Now, you can just use this helper whenever you need to convert the date from the same format before saving it to DB.

Notice that the Helper method is static because it doesn't care about the application state. It just cares about its parameters and returns value.

Also, you could create Helpers as a regular file with PHP methods without any class. Still, personally, I would prefer the whole app/ folder to be OOP with the structure to avoid collision of the same method names in different helpers.


Open-Source Examples

Example Project 1. tighten/novapackages

The first example of a global helper is from the tighten/novapackages open-source project. This project has a separate file called helpers.php where functions are added.

app/helpers.php:

if (! function_exists('markdown')) {
function markdown($text)
{
return '<div class="markdown">'.(new Parsedown)->text($text).'</div>';
}
}
 
if (! function_exists('abstractify')) {
function abstractify($text)
{
$text = strip_tags($text);
 
return strlen($text) > 190 ? substr($text, 0, 190).'...' : $text;
}
}
 
if (! function_exists('translate_github_emoji')) {
function translate_github_emoji($key)
{
return Arr::get([
'+1' => '๐Ÿ‘',
'-1' => '๐Ÿ‘Ž',
'laugh' => '๐Ÿ˜',
'hooray' => '๐ŸŽ‰',
'confused' => '๐Ÿ˜•',
'heart' => 'โค๏ธ',
], $key, 'โ‰๏ธ');
}
}

Now, these functions can be used anywhere in the project.

app/Http/Resources/Collaborator.php:

class Collaborator extends JsonResource
{
public function toArray($request)
{
return [
'name' => $this->name,
'url' => $this->url,
'description' => $this->description,
'description_html' => markdown($this->description),
'github_username' => $this->github_username,
];
}
}

Personally, as I stated above, I don't really like this approach of non-OOP helper files. See, we need to add function_exists() every time to check if we don't accidentally override anything. With a Class-based approach, this problem wouldn't exist.


Example Project 2. officelifehq/officelife

The following example is from officelifehq/officelife, an open-source project.

In this project, helpers are made as a PHP class, and methods from that class can be used in the project. For example, a helper to work with user birthdays.

app/Helpers/BirthdayHelper.php:

class BirthdayHelper
{
public static function age(Carbon $birthdate, string $timezone = null): int
{
$now = Carbon::now($timezone);
$now = Carbon::create($now->year, $now->month, $now->day);
 
return $birthdate->diffInYears($now, true);
}
 
public static function isBirthdayInXDays(Carbon $startDate, Carbon $birthdate, int $numberOfDays): bool
{
$future = $startDate->copy()->addDays($numberOfDays);
$birthdate->year = $startDate->year;
 
if ($birthdate->isPast()) {
return false;
}
 
return $birthdate->lessThanOrEqualTo($future) && $future->greaterThanOrEqualTo($startDate);
}
 
public static function isBirthdayInRange(Carbon $birthdate, Carbon $minDate, Carbon $maxDate): bool
{
return $birthdate->between($minDate, $maxDate);
}
}

Now, any method from the BirthdayHelper class can be used in the project.

app/Models/Company/Employee.php:

class Employee extends Model
{
// ...
 
public function toObject(): array
{
$address = $this->getCurrentAddress();
$loggedEmployee = InstanceHelper::getLoggedEmployee();
 
return [
// ...
'birthdate' => (! $this->birthdate) ? null : [
'full' => DateHelper::formatDate($this->birthdate),
'partial' => DateHelper::formatMonthAndDay($this->birthdate),
'year' => $this->birthdate->year,
'month' => $this->birthdate->month,
'day' => $this->birthdate->day,
'age' => BirthdayHelper::age($this->birthdate, $this->timezone),
],
// ...
];
}
 
// ...
}

Ok, this is the end of the course section about structuring the app/ folder files and classes. The following section will be about a more global structure: modules, packages, and admin/user areas.