The Support Namespace — Your App's Internal Library

The Support Namespace — Your App’s Internal Library

Reading Time: 5 minutes

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 a Helpers/ god file
  • Support/Contracts/ — interfaces your services implement
  • Support/Traits/ — model and console behaviors
  • Support/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 logic
  • app/Models/ — Eloquent persistence
  • app/Actions/ — business executors
  • app/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 modelsSupport/Traits/
Is an interface that multiple services implementSupport/Contracts/
Is a utility (file, string, date) with no domainSupport/File.php or Support/Helpers/
Has business logic for a specific domainServices/{Domain}/
Is a DTO for a specific ActionDTOs/
Is a single-use helper for one modelKeep 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.php god file by another name)
  • The place for business logic (that lives in Services/ and Actions/)
  • 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.php centralizes all file-related utilities — naming, hashing, MIME detection, size formatting.
  • Support/Traits/Encryptable.php provides 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 in app/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 use SluggableCanBeActivated, and DateScopes together 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


← Model Change Logger | Next: Jobs and Queues →

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

One Comment