PHPackages                             ahinest/laravel-advertising - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. ahinest/laravel-advertising

ActiveLibrary

ahinest/laravel-advertising
===========================

Advertising resources, containers and placements for Laravel.

v1.0.0(today)00MITPHPPHP ^8.1

Since Aug 14Pushed todayCompare

[ Source](https://github.com/J-E-L-E-Dev/laravel-advertising)[ Packagist](https://packagist.org/packages/ahinest/laravel-advertising)[ RSS](/packages/ahinest-laravel-advertising/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (4)Versions (2)Used By (0)

Laravel Advertising
===================

[](#laravel-advertising)

[![Laravel 10.x](https://camo.githubusercontent.com/540748966581100bddf2fb60b744c38e16e00ec667c5f22dd6ae6554a3a41693/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31302e782d7265642e737667)](https://laravel.com/docs/10.x)[![Laravel 11.x](https://camo.githubusercontent.com/1cf7e76377e33c525ce7f5645f062caa61e32d7f2a143fa261c5cbce90820e67/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31312e782d7265642e737667)](https://laravel.com/docs/11.x)[![Laravel 12.x](https://camo.githubusercontent.com/b72e0aa3b09f6ee9f1cd47f19792a8204408312803c6b277768a5d2c99ffd60c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31322e782d7265642e737667)](https://laravel.com/docs/12.x)

[![Latest Stable Version](https://camo.githubusercontent.com/db209c7d3e9dcd0fb5ba0f27d6538618d16d426804bdc4c284a873c566a11f7f/687474703a2f2f706f7365722e707567782e6f72672f6168696e6573742f6c61726176656c6164766572746973696e672f76)](https://packagist.org/packages/ahinest/laraveladvertising)[![Total Downloads](https://camo.githubusercontent.com/b270f73241661ce1ab460c17956e6f7960cf971d69797dbcc93f12ece5c9e015/687474703a2f2f706f7365722e707567782e6f72672f6168696e6573742f6c61726176656c6164766572746973696e672f646f776e6c6f616473)](https://packagist.org/packages/ahinest/laraveladvertising)[![License](https://camo.githubusercontent.com/38d619c3777391174da022ceb5fb1b92ad6a784caa6011c7a5e92d207b3d10fc/687474703a2f2f706f7365722e707567782e6f72672f6168696e6573742f6c61726176656c6164766572746973696e672f6c6963656e7365)](https://packagist.org/packages/ahinest/laraveladvertising)

User guide:
-----------

[](#user-guide)

[![en](https://camo.githubusercontent.com/9687410941adb91c2f673c9d50ef38544f3e9a38a6b9f9367cac918a8d3e2a41/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c616e672d656e2d7265642e737667)](https://github.com/J-E-L-E-Dev/laravel-advertising)[![es](https://camo.githubusercontent.com/836476cfed52d44b1c1aab20b7c942af38e6f73421e52eba781ee89904ec919a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c616e672d65732d79656c6c6f772e737667)](https://github.com/J-E-L-E-Dev/laravel-advertising/blob/main/README.es.md)

Laravel package for managing advertising categories, resources, containers and advertisements.

Installation
------------

[](#installation)

```
composer require ahinest/laravel-advertising
php artisan vendor:publish --tag=advertising-config
php artisan migrate
php artisan advertising:seed-categories
```

Laravel discovers the package automatically. By default it creates `advertising_*` tables and exposes `GET /advertising/{slug}`. Configuration lets you change routes, disk, tables, models and field aliases.

Domain
------

[](#domain)

A `Category` has many `Resource` records. A `Resource` may belong to many `Container` records. A `Container` may belong to many `Advertisement` records. The public endpoint returns only non-expired resources.

### Included categories

[](#included-categories)

The `advertising:seed-categories` seeder idempotently includes the three categories from `demo`:

CodeCategoryFileContent fields`IB`Image with description only`image` or `file``description``IE`Image with link`image` or `file``eyebrow`, `title`, `description`, `button_label`, `button_url``IV`Advertising video`video` or `file``title`When creating a resource, send `category` (or `category_id`) with an identifier from one of these categories. The package verifies that image categories receive an image/file and that the video category receives a video/file.

### Category visible fields

[](#category-visible-fields)

`Category.fields` defines the fields a frontend should show when creating or editing a resource for that category. It is UI metadata: the package does **not** make those fields required when saving because each application keeps its own `FormRequest` business rules.

The following formats are supported and automatically normalized into `fields_schema`:

```
// Compatible with existing categories: all fields are visible and optional.
'fields' => ['eyebrow', 'title', 'description']

// Short syntax: mandatory tells the frontend to mark the field as required.
'fields' => ['eyebrow:mandatory', 'title', 'description:optional']

// Recommended for new categories: expressive and extensible.
'fields' => [
    ['name' => 'eyebrow', 'required' => true],
    ['name' => 'title', 'required' => false],
    ['name' => 'description', 'required' => false, 'visible' => true],
]
```

For example, `['eyebrow:mandatory', 'title']` produces:

```
[
  {"name": "eyebrow", "required": true, "visible": true},
  {"name": "title", "required": false, "visible": true}
]
```

The frontend selects a category, reads `fields_schema`, and builds the required inputs. Its `FormRequest` must enforce the actual business rules for required fields.

Safe CRUD actions
-----------------

[](#safe-crud-actions)

`AdvertisingActions` centralizes creation, updates, relationship synchronization, soft deletion, force deletion and restoration. Only attributes defined in the target model's `$fillable` are persisted. If no allowed field is present, it returns `false`.

```
use Ahinest\LaravelAdvertising\Actions\AdvertisingActions;

$actions = app(AdvertisingActions::class);
$resource = $actions->createResource([
    'resource' => 'advertising/offer.jpg', // mapped to path
    'title' => 'Summer offer',
    'containers' => [1, 2],
]);

$actions->updateResource($resource, ['title' => 'New offer', 'containers' => [2]]);
$actions->delete($resource);       // soft delete
$actions->restore($resource);      // obtain first with onlyTrashed()
$actions->delete($resource, true); // permanent delete
```

For containers, `createContainer($input)` accepts `resources: [ids]`. For advertisements, `createAdvertisement($input)` accepts `containers: [ids]`. Updates synchronize a relationship only when its key is present.

`createCategory` and `updateCategory` are also available. A resource may receive an `UploadedFile` in `file`, `image` or `video`; the package stores it automatically using the configured disk and path.

### Application or API response

[](#application-or-api-response)

Every `AdvertisingActions` method accepts `$response` as its final argument: use `'application'` (default) to receive an `AdvertisingResponse`, independent of Blade, Inertia or Livewire; use `'api'` to receive a `JsonResponse`.

```
$web = $actions->createResource($input);        // AdvertisingResponse
$api = $actions->createResource($input, 'api'); // JsonResponse: success, message, data
```

Application responses contain `success`, `message`, `data` and `status`; use `$web->toArray()` in the layer you prefer. Invalid input returns status 422. Successful deletion actions return 204.

Files are stored only by `createResource` and `updateResource`. A forced deletion (`delete($resource, true)`) also removes the file from the configured disk; categories, containers and advertisements do not manage files.

### Complete relationship flow

[](#complete-relationship-flow)

```
$actions = app(\Ahinest\LaravelAdvertising\Actions\AdvertisingActions::class);
$resource = $actions->createResource(['category' => 1, 'image' => $request->file('image')])->data;
$container = $actions->createContainer(['title' => 'Cover', 'resources' => [$resource->id]])->data;
$advertisement = $actions->createAdvertisement(['title' => 'Home', 'containers' => [$container->id]])->data;

// Stores the new file, updates the database and deletes the former file.
$actions->updateResource($resource, ['image' => $request->file('image')]);
```

`all($type, $with)`, `find($type, $id)`, `trashed($type, $with)` and `restoreMany($type, $ids)` cover listing, lookup, trash and bulk restoration. Available types are `category`, `resource`, `container` and `advertisement`. Action exceptions use Laravel `Log::error` with `message`, `line`, `file` and `data`.

Input formatting
----------------

[](#input-formatting)

The package converts external field names before mass assignment. By default it accepts vocabulary from other projects:

InputPackage field`title``name` for categories, containers and advertisements`resource``path``alt_resource``alt``top_title``eyebrow``button` / `button_link``button_label` / `button_url``end_of_advertising``expires_at`Edit `input_map` in the published configuration to change or add aliases. `Model::create($input)` does not transform input by itself: use `AdvertisingActions` or `CrudAction::attributes()` before creating directly.

Publish and replace models
--------------------------

[](#publish-and-replace-models)

```
php artisan vendor:publish --tag=advertising-models
```

This creates ready-to-edit subclasses in `App\Models\Advertising`. Edit `$fillable`, casts or accessors as needed, then change the matching class in `advertising.models` in the configuration file. Relationships and actions use those configured classes.

Expiration
----------

[](#expiration)

```
Schedule::command('advertising:expire')->daily();
```

The command soft-deletes expired resources, containers and advertisements.

Resource queries
----------------

[](#resource-queries)

`indexResources()` returns all non-deleted resources with their category and containers. It accepts `category`/`category_id`, `container`/`container_id` and `active` filters.

```
$actions = app(\Ahinest\LaravelAdvertising\Actions\AdvertisingActions::class);

$all = $actions->indexResources();
$activeFromCategory = $actions->indexResources(['category' => 1, 'active' => true], 'api');

// Includes category and containers. Returns 404 when it does not exist.
$resource = $actions->showResource(15);
```

`showResource($id)` queries one non-deleted resource. Both methods accept `'application'` (default) or `'api'` to choose the response type.

`createResource` and `updateResource` accept an array or a `Request`/`FormRequest`. When they receive a `FormRequest`, they use `$request->validated()`, preserving validated `image` and `video` files.

```
public function create(StoreResourcesRequest $request)
{
    return $this->actions->createResource($request, 'api');
}
```

When replacing a file during an update, the resource must have an associated category (or receive `category`/`category_id`) to validate whether it is an image or a video. Updating text, date or containers alone does not require a category.

If `image` or `video` is received without a valid category, the package returns 422 with a specific message. If the category exists but the file type does not match (for example, an image for `IV`), it also reports the expected type.

When creating resources, the recommended field is `category`. The package converts it to `category_id` before mass assignment. If the configuration file was published, ensure aliases are present in `advertising.input_map.resource` and run `php artisan config:clear` after changing it.

`category` and `containers` are different: `category` maps to the `category_id` column, while `containers` synchronizes through the many-to-many pivot table. New requests must use `containers: [1, 2]`. If your API uses another name for the relationship, change it in `advertising.relation_inputs.resource_containers`; it is not added to `input_map` because it is not a resource column.

### Filters on every index

[](#filters-on-every-index)

Every `index...` and `indexTrashed...` method accepts a filter array as its first argument, followed by the response type to receive.

```
$actions->indexCategories(['name' => 'video'], 'api');
$actions->indexContainers(['slug' => 'cover', 'active' => true], 'api');
$actions->indexAdvertisements(['description' => 'summer', 'expires_at' => 'active']);
$actions->indexResources(['category_id' => 1, 'container' => 5, 'active' => true]);
```

Models expose common scopes: `name` and `description` perform partial searches, `slug` is exact, and `expiresAt` accepts a date (`YYYY-MM-DD`), `null`, `expired` or `active`. Only filters registered for each model in `advertising.index_filters` are applied; unregistered keys are deliberately ignored.

To enable filtering for a field added to a published model, define its scope and register it in configuration. For example, for `external_id` on `App\Models\Advertising\Resource`:

```
public function scopeExternalId($query, string $value)
{
    return $query->where('external_id', $value);
}
```

```
// config/advertising.php
'index_filters' => [
    'resource' => [
        // Keep any existing filters you want to use.
        'external_id' => 'externalId',
    ],
],
```

Then `indexResources(['external_id' => 'CRM-42'], 'api')` applies that scope. If you published configuration, add the entry inside the existing array —do not replace it entirely— and run `php artisan config:clear` when the application caches configuration.

CRUD and queries for all models
-------------------------------

[](#crud-and-queries-for-all-models)

Yes: the package lets you work with all models and their relationships, not only resources. These explicit operations are available:

ModelCreate / editIndex / detailLoaded relationshipsCategory`createCategory`, `updateCategory``indexCategories`, `showCategory`resourcesResource`createResource`, `updateResource``indexResources`, `showResource`category, containersContainer`createContainer`, `updateContainer``indexContainers`, `showContainer`resources, advertisementsAdvertisement`createAdvertisement`, `updateAdvertisement``indexAdvertisements`, `showAdvertisement`containers, resourcesAll models support `delete($model)`, `delete($model, true)`, `restore($model)`, `restoreMany($type, $ids)` and `trashed($type)`.

Each model also has specific deletion methods: `deleteResourceById`, `deleteContainerById`, `deleteAdvertisementById` and `deleteCategoryById`. They preserve the `'application'` or `'api'` response type and return 404 when the record to delete does not exist.

Each model also has trash and restore-by-ID methods: `indexTrashedResources` / `showTrashedResource` / `restoreResourceById`, `indexTrashedContainers` / `showTrashedContainer` / `restoreContainerById`, and `indexTrashedAdvertisements` / `showTrashedAdvertisement` / `restoreAdvertisementById`. They preserve the `'application'` or `'api'` response type and return 404 when the deleted record does not exist.

When creating or updating a container, both relationships may be sent independently: `resources: [ids]` synchronizes resources and `advertisements: [ids]` synchronizes advertisements that use it. If a key is omitted, that relationship is unchanged; if it is sent as an empty array, every record is detached from that relationship.

When a create or update request includes a synchronizable relationship, the response reloads and includes that relationship in its final state. For example, creating a resource with `containers: [1, 2]` returns the resource together with `containers`.

Category has complete CRUD: `name`/`title`, `code` and `fields` are editable. `fields` is stored as a JSON array and defines the presentation fields required by each category. Categories use soft deletion, just like resources, containers and advertisements.

`description` is also editable. After updating the package, run `php artisan migrate` to add the column to existing installations.

### Update or delete by identifier

[](#update-or-delete-by-identifier)

`showCategory()` returns a response, not a `Category` instance; therefore it must not be used as a model for `delete()`. For services that receive an ID, use these package methods, which return 404 if the record does not exist:

```
$actions->updateCategoryById($id, $request->all(), 'api');
$actions->deleteCategoryById($id, false, 'api'); // soft deletion
$actions->deleteCategoryById($id, true, 'api');  // permanent deletion
```

Internal architecture
---------------------

[](#internal-architecture)

`AdvertisingActions` contains only public domain operations: categories, resources, containers, advertisements and their relationships. Shared infrastructure —responses, internal lookups, generic listings, restoration, deletion and logs— lives in the inherited `AbstractAdvertisingAction`. File storage, validation and deletion live in `ResourceFileService`, because files belong exclusively to resources.

Published models and constants
------------------------------

[](#published-models-and-constants)

Published models are package subclasses; **they do not replace the original model with an empty model**. They inherit relationships, `$casts`, accessors, soft deletes and methods. You may add your own methods directly. To add allowed fields without replacing base `$fillable`, use:

```
public function __construct(array $attributes = [])
{
    parent::__construct($attributes);
    $this->mergeFillable(['frequency', 'web_publish']);
}
```

The package uses `Ahinest\LaravelAdvertising\Constants\ModelName` instead of internal strings. For example: `ModelName::RESOURCE`, `ModelName::CATEGORY`, `ModelName::CONTAINER` and `ModelName::ADVERTISEMENT`.

Response language
-----------------

[](#response-language)

Package responses automatically follow Laravel's active locale (`app()->getLocale()`). Internally every message uses an English key, so when no translation exists for the active locale, Laravel returns the English message as the fallback.

The package already includes Spanish translations and loads them automatically. With `APP_LOCALE=es`, for example, `Category created.` is returned as `CategorÃ­a creada.`. With `APP_LOCALE=en`, it is returned as `Category created.`.

To use any other language, add the English key and its translation to your application's language JSON file. For example, in `lang/fr.json`:

```
{
    "Category created.": "CatÃ©gorie crÃ©Ã©e.",
    "Resource created.": "Ressource crÃ©Ã©e.",
    "The request is incorrect or incomplete; review the allowed fields.": "La requÃªte est incorrecte ou incomplÃ¨te ; vÃ©rifiez les champs autorisÃ©s."
}
```

You do not need to change actions or controllers: both the `'application'` and `'api'` responses use the same translator. Use the available keys in the package's `lang/es.json` as a source and copy only the keys you want to translate into `lang/{locale}.json` in your project.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/a0ec6bbe204480ccdd3dfe608b9343575ece5f1d8791ccadac0190c434d2fc9c?d=identicon)[edwinylil1](/maintainers/edwinylil1)

---

Top Contributors

[![J-E-L-E-Dev](https://avatars.githubusercontent.com/u/84980112?v=4)](https://github.com/J-E-L-E-Dev "J-E-L-E-Dev (1 commits)")

### Embed Badge

![Health badge](/badges/ahinest-laravel-advertising/health.svg)

```
[![Health](https://phpackages.com/badges/ahinest-laravel-advertising/health.svg)](https://phpackages.com/packages/ahinest-laravel-advertising)
```

###  Alternatives

[mike-bronner/laravel-model-caching

Automatic caching for Eloquent models.

2.4k161.4k2](/packages/mike-bronner-laravel-model-caching)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.6k31.8M158](/packages/laravel-cashier)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[masterix21/laravel-licensing

Laravel licensing package with polymorphic assignment to any model, activation keys, expirations/renewals, and seat control via LicenseUsage. Supports offline verification with public-key–signed tokens, a CLI to generate/rotate/revoke keys, and an extensible architecture via config and contracts.

1614.1k4](/packages/masterix21-laravel-licensing)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
