The Support Namespace — Your App’s Internal Library
Series: Every Laravel Project Should Have These Building Blocks
Part: 14 of 35 Level: Intermediate Prerequisites: Folder Structure
What You’ll Learn
- What
app/Support/is for and why it exists - The difference between
Support/and aHelpers/god file Support/Contracts/— interfaces your services implementSupport/Traits/— model and console behaviorsSupport/File.php— the universal file utility- What belongs in
Support/vs. a dedicated domain folder
What Is app/Support/?
app/Support/ is the internal library of your application. It contains code that:
- Is used across multiple domains (not owned by a single domain)
- Is infrastructure-level, not business-logic-level
- Has no better home elsewhere in
app/
It’s the equivalent of what Illuminate/Support/ is to Laravel itself — the toolkit that everything else uses.
This is different from:
app/Services/— domain-specific business logicapp/Models/— Eloquent persistenceapp/Actions/— business executorsapp/Http/— HTTP layer
app/Support/ is for things like: “generate a unique filename for any uploaded file”, “make any model track who created it”, “give any Artisan command dual console+log output”.
The Full Structure
app/Support/
├── Contracts/
│ ├── HasRoles.php # Interface for role-aware models
│ ├── Notifiable.php # Custom notifiable interface
│ └── Searchable.php # Interface for searchable models
│
├── Traits/
│ ├── Console/
│ │ └── LogsCommandMessages.php # Dual output for Artisan commands
│ │
│ ├── Models/
│ │ ├── HasCreator.php # Auto-sets created_by
│ │ ├── HasCachedAttributes.php # Per-request in-memory cache
│ │ └── HasNonDevelopmentAccess.php
│ │
│ ├── ModelChangeLogger.php # Polymorphic audit trail
│ ├── Sluggable.php # Auto-generated slugs
│ ├── CanBeActivated.php # is_active toggle + scopes
│ ├── DateScopes.php # lastDays(), thisMonth(), etc.
│ ├── HasAddress.php # Address fields and validation
│ ├── HasPermissions.php # Permission check methods
│ ├── HasRoles.php # Role assignment methods
│ ├── InteractsWithMedia.php # File attachment helpers
│ ├── BarcodeGenerator.php # Barcode generation mixin
│ └── Encryptable.php # Transparent attribute encryption
│
├── Enum/
│ └── ChangeLogType.php # For ModelChangeLogger
│
└── File.php # File utility class
Support/Contracts/ — Interfaces for Services
Interfaces belong in Support/Contracts/ when they define behaviors that span multiple models or services:
// app/Support/Contracts/HasRoles.php
interface HasRoles
{
public function roles(): BelongsToMany;
public function hasRole(string|array $role): bool;
public function assignRole(string $role): void;
public function removeRole(string $role): void;
}
// app/Support/Contracts/Searchable.php
interface Searchable
{
public function scopeSearch(Builder $query, string $term): Builder;
public static function searchableFields(): array;
}
Then implement them on any model:
class User extends Authenticatable implements HasRoles, Searchable
{
use \App\Support\Traits\HasRoles;
public static function searchableFields(): array
{
return ['name', 'email', 'phone'];
}
public function scopeSearch(Builder $query, string $term): Builder
{
return $query->where(function (Builder $q) use ($term): void {
foreach (static::searchableFields() as $field) {
$q->orWhere($field, 'like', "%{$term}%");
}
});
}
}
The interface lives in Support/Contracts/, the trait implementation in Support/Traits/, and the model just wires them together.
Support/File.php — The Universal File Utility
Every project that handles uploads needs file utilities: safe naming, MIME detection, size formatting, hashing. Instead of scattering these across models and controllers, centralize them in Support/File.php:
// app/Support/File.php
<?php
declare(strict_types=1);
namespace App\Support;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
final class File
{
/**
* Generate a unique filename preserving the extension.
* "My Photo.JPG" → "a3f92b1c4d8e6f7a.jpg"
*/
public static function uniqueName(UploadedFile $file): string
{
return Str::random(16) . '.' . strtolower($file->getClientOriginalExtension());
}
/**
* Generate a filename from a string + extension.
* ("Invoice", "pdf") → "invoice-1702394871.pdf"
*/
public static function nameFromString(string $name, string $extension): string
{
return Str::slug($name) . '-' . time() . '.' . $extension;
}
/**
* Hash a file for deduplication (e.g., checking if already uploaded).
*/
public static function hash(UploadedFile $file): string
{
return hash_file('sha256', $file->getRealPath());
}
/**
* Human-readable file size.
* 1536 → "1.5 KB"
*/
public static function humanSize(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 1) . ' ' . $units[$i];
}
/**
* Check if a MIME type is an image.
*/
public static function isImage(string $mimeType): bool
{
return str_starts_with($mimeType, 'image/');
}
/**
* Check if a MIME type is a document (PDF, Word, Excel).
*/
public static function isDocument(string $mimeType): bool
{
return in_array($mimeType, [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
], true);
}
/**
* Safe storage path for an entity's files.
* e.g. ("users", 42, "avatar") → "users/42/avatar"
*/
public static function storagePath(string $entity, int|string $id, string $collection = 'files'): string
{
return "{$entity}/{$id}/{$collection}";
}
}
Usage anywhere in the app:
// In a model or action
$filename = File::uniqueName($request->file('avatar'));
$path = File::storagePath('users', $user->id, 'avatars') . '/' . $filename;
$size = File::humanSize($request->file('document')->getSize());
Support/Traits/Encryptable.php — Transparent Attribute Encryption
For models that store sensitive data (API keys, personal identification numbers), the Encryptable trait encrypts and decrypts transparently:
// app/Support/Traits/Encryptable.php
trait Encryptable
{
/**
* Define which attributes to encrypt in the model:
* protected array $encryptable = ['api_key', 'secret_token'];
*/
public function getAttribute(string $key): mixed
{
$value = parent::getAttribute($key);
if (in_array($key, $this->encryptable ?? [], true) && $value !== null) {
try {
return decrypt($value);
} catch (\Illuminate\Contracts\Encryption\DecryptException) {
return $value; // Return raw if decryption fails (e.g., not yet encrypted)
}
}
return $value;
}
public function setAttribute(string $key, mixed $value): mixed
{
if (in_array($key, $this->encryptable ?? [], true) && $value !== null) {
$value = encrypt($value);
}
return parent::setAttribute($key, $value);
}
}
On any model:
class ApiIntegration extends Model
{
use Encryptable;
protected array $encryptable = ['api_key', 'api_secret', 'webhook_secret'];
}
// Usage — encryption/decryption is automatic:
$integration->api_key = 'sk_live_abc123'; // stored as encrypted ciphertext
echo $integration->api_key; // returns 'sk_live_abc123'
What Belongs in Support/ vs. Elsewhere
| If it… | Put it in… |
|---|---|
| Is a reusable trait for multiple models | Support/Traits/ |
| Is an interface that multiple services implement | Support/Contracts/ |
| Is a utility (file, string, date) with no domain | Support/File.php or Support/Helpers/ |
| Has business logic for a specific domain | Services/{Domain}/ |
| Is a DTO for a specific Action | DTOs/ |
| Is a single-use helper for one model | Keep it on the model |
The key question: “Could this be useful in another project?” If yes, it belongs in Support/. If it’s specific to your business domain, it belongs in Services/ or Actions/.
What Support/ Is NOT
Support/ is not:
- A dumping ground for code that doesn’t fit elsewhere (that’s a
Helpers.phpgod file by another name) - The place for business logic (that lives in
Services/andActions/) - A replacement for properly namespaced domain code
The test: if you copy app/Support/ into another Laravel project, does it still make sense there? If yes — it belongs in Support/. If it references your specific models or domain concepts — it doesn’t.
Key Takeaways
app/Support/is your app’s internal library: traits, contracts, and utilities used across multiple domains.Support/Contracts/holds interfaces that models and services implement.Support/File.phpcentralizes all file-related utilities — naming, hashing, MIME detection, size formatting.Support/Traits/Encryptable.phpprovides transparent attribute encryption for sensitive model fields.- The question for what belongs here: “Could this be useful in another project?” If yes, it’s
Support/.
Tips and Gotchas
⚠️ Warning: Don’t put business logic in
app/Support/. The support namespace is for reusable infrastructure — traits, utilities, contracts. If a class inapp/Support/is tightly coupled to a specific domain model, it belongs in that domain instead.
💡 Tip: Traits in
app/Support/Traits/Models/should be composable — each one does one thing. A model can useSluggable,CanBeActivated, andDateScopestogether without conflict. Design traits to be combined, not to be comprehensive.
🔥 Expert Note: The
app/Support/pattern is an explicit signal to future developers: “this code is shared infrastructure, handle changes carefully.” It’s the difference between “I changed this helper and three things broke unexpectedly” and “I know exactly what uses this because it’s in Support and I grepped for it.”
Further Reading
- PHP Docs: Traits
- PHP Docs: Interfaces
- Laravel Docs: Service Container — for registering Support contracts
One Comment