Make your own custom cast type for Laravel model attributes

Overview

Laravel Custom Casts

Build Downloads Stable License

Make your own cast type for Laravel model attributes

Laravel custom casts works similarly to Eloquent attribute casting, but with custom-defined logic (in a separate class). This means we can use the same casting logic across multiple models — we might write image upload logic and use it everywhere. In addition to casting to custom types, this package allows custom casts to listen and react to underlying model events.

Let's review some Laravel common cast types and examples of their usage:

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $casts = [
        'is_admin' => 'boolean',
        'login_count' => 'integer'
        'height' => 'decimal:2'
    ];
}

In addition to boolean, integer, and decimal, out of the box Laravel supports real, float, double, string, object, array, collection, date, datetime, and timestamp casts.

Sometimes it is convenient to handle more complex types with custom logic, and for casts to be able to listen and react to model events. This is where this package come in handy.

Handling events directly from custom casts can be very useful if, for example, we're storing an image using a custom casts and we need to delete it when the model is deleted. Check out the old documentation for this example.

📦 vkovic packages 📦

Please check out my other packages — they are all free, well-written, and some of them are useful 😄 . If you find something interesting, consider giving me a hand with package development, suggesting an idea or some kind of improvement, starring the repo if you like it, or simply check out the code - there's a lot of useful stuff under the hood.

Compatibility

The package is compatible with Laravel versions 5.5, 5.6, 5.7, 5.8 and 6

and Lumen versions 5.5, 5.6, 5.7, 5.8.

Minimum supported version of PHP is 7.1. PHP 8 is also supported.

Installation

Install the package via Composer:

composer require vkovic/laravel-custom-casts

Usage

Utilizing a custom cast class

To enable custom casts in a model, use the HasCustomCasts trait and define which attributes will be casted using $casts - per Laravel standards.

// File: app/User.php

namespace App;

use App\CustomCasts\NameCast;
use Illuminate\Database\Eloquent\Model;
use Vkovic\LaravelCustomCasts\HasCustomCasts;

class User extends Model
{
    use HasCustomCasts;

    protected $casts = [
        'is_admin' => 'boolean', // <-- Laravel default cast type
        'name' => NameCast::class // <-- Our custom cast class (see the section below)
    ];
}

Defining a custom cast class

This class will be responsible for our custom casting logic.

// File: app/CustomCasts/NameCast.php

namespace App\CustomCasts;

use Vkovic\LaravelCustomCasts\CustomCastBase;

class NameCast extends CustomCastBase
{
    public function setAttribute($value)
    {
        return ucwords($value);
    }

    public function castAttribute($value)
    {
        return $this->getTitle() . ' ' . $value;
    }

    protected function getTitle()
    {
        return ['Mr.', 'Mrs.', 'Ms.', 'Miss'][rand(0, 3)];
    }
}

The required setAttribute method receives the $value being set on the model field, and should return a raw value to store in the database.

The optional castAttribute method receives the raw $value from the database, and should return a mutated value. If this method is omitted, the raw database value will be returned.

For the sake of this example we'll implement one more method which will attach a random title to a user when their name is retrieved from database.

Testing a custom cast class

Let's create a user and see what happens.

$user = new App\User;
$user->name = 'john doe';

$user->save();

This will create our new user and store their name in the database, with the first letter of each word uppercased.

When we retrieve the user and try to access their name, title will be prepended to it — just like we defined in our custom NameCast class.

dd($user->name); // 'Mr. John Doe'

Handling model events

Let's say that we want to notify our administrator when a user's name changes.

// File: app/CustomCasts/NameCast.php

public function updated()
{
    $attribute = $this->attribute;

    if($this->model->isDirty($attribute)) {
        // Notify admin about name change
    }
}

In addition to the updated method, we can define other methods for standard model events: retrieved, creating, created, updating, saving, saved, deleting, deleted, restoring and restored.

Other functionality

As you can see from the above code, we can easily access the casted attribute name as well as an instance of the underlying model.

// File: app/CustomCasts/NameCast.php

// Get the name of the model attribute being casted
dd($this->attribute); // 'name'

// Access our `User` model
dd(get_class($this->model)); // 'App/User'

We can also retrieve all casted attributes and their corresponding classes directly from the model.

// File: app/User.php

dd($this->getCustomCasts()); // ['name' => 'App/CustomCasts/NameCast']

Using aliased casts

You may find it easier to use aliases for custom casts, e.g.:

protected $casts = [
    'avatar' => 'image' // <-- You prefer this ...
    // ---
    'avatar' => ImageCast::class // <-- ... over this
];

To make the magic happen, first add the package's service provider to the providers array:

// File: config/app.php

'providers' => [
    // ...

    /*
     * Package Service Providers...
     */
    Vkovic\LaravelCustomCasts\CustomCastsServiceProvider::class

    // ...
]

Once the provider is added, publish the config file which will be used to associate aliases with their corresponding custom cast classes:

php artisan vendor:publish --provider="Vkovic\LaravelCustomCasts\CustomCastsServiceProvider"

This command should create a config file located at config/custom_casts.php. Open it up and check out the comments for examples of config options.

Use it without Laravel

This package can also be used without full Laravel installation, with something like jenssegers/model or if your project is using illuminate/database library.

More examples

You can find more examples in the old documentation.

Contributing

If you plan to modify this Laravel package you should run the tests that come with it. The easiest way to accomplish this is with Docker, docker-compose, and phpunit.

First, we need to initialize Docker container (see docker-composer.yaml for details).

docker-compose up --exit-code-from app

After that, we can run tests and watch the output:

docker-compose run --rm app phpunit
Comments
  • Manager

    Manager

    To give people to distribute their custom casts, we have to add the ability to dynamically register new casts. In laravel such functionality is done using Manager::class and extend method.

    class PackageServiceProvider extends ServiceProvider
    {
        public function register()
        {        
            $this->app->make(CustomCastManager::class)->extend('file', FileCast::class);
        }
    }
    
    
    opened by Marko298 8
  • Listener for each attribute

    Listener for each attribute

    Take a look at this idea for events. It will register an event listener for each custom casts attribute only if the method for that listener is defined. This eliminates overhead when you do not use events in your custom casts.

    opened by Marko298 8
  • FirstOrCreate issue

    FirstOrCreate issue

    Describe the bug

    Running

    $email = Network\Email::firstOrCreate( $emailParams );
    

    also tried

    $email = new Network\Email;
    $email->firstOrCreate( $emailParams );
    

    Those don't run the data conversion. Although running...

    $email = Network\Email::create( $emailParams );
    

    Does run the conversion just fine.

    To Reproduce My cast class is

    class PostgresArray extends CustomCastBase
    {
        public function setAttribute($value)  {
            if (is_string($value))  return $value;
            if (is_array($value))   return "{" . implode(',', $value) . "}";
        }
        public function castAttribute($value)   {
            if (is_string($value))  return $value;
            if (is_array($value))   return "{" . implode(',', $value) . "}";
        }
    }
    

    I'm on the latest current version of your library, using Laravel 6.

    I had added dd commands in my cast to make sure it wasn't executing.

    Am I missing something from my cast or is this a limitation with the library?

    Thanks!

    opened by gareth-ib 8
  • Human readable cast names

    Human readable cast names

    I think it would be awesome to be able to specify the cast name in human readable format:

    protected $casts = [
        'amount' => MoneyCast::class
    ];
    

    becomes

    protected $casts = [
        'amount' => 'money'
    ];
    

    Perhaps the approach used could be similar to how Laravel infers the policy class names using the guessPolicyNamesUsing method.

    How difficult would this be to add? Not super familiar with this package yet so wasn't sure how much effort is involved (although happy to help out as well!)

    enhancement 
    opened by jimhlad 8
  • Parameters

    Parameters

    In native laravel cast, you can add params for casts. For example date. Adding this feature gives more control over casting process.

    class User extends Model {
      protected $casts = [
        'avatar' => 'file:disk',
      ];
    }
    
    enhancement good first issue 
    opened by Marko298 4
  • Cache all attribute names with custom casts for improved performance.

    Cache all attribute names with custom casts for improved performance.

    Motivation: I have a class where just one attribute is custom-cast. Every single access to any of the other attributes would call getCustomCasts, which in turn invokes the autoloader in the is_subclass_of call for every single attribute in the casts array.

    Caching the result of $this->getCustomCasts() has doubled performance for me in some cases.

    opened by MrMage 4
  • Allow this repo to be used without Laravel

    Allow this repo to be used without Laravel

    Currently this repo depends on the presence of config(). With this simple change it can be used in projects that use illuminate/database without Laravel.

    opened by brendandebeasi 3
  • Lumen compatibility

    Lumen compatibility

    According to your composer you are strictly targeting Laravel framework, however inside you are using only it's components like eloquent and maybe it's dependencies.

    All the functionality can be easily reused inside Lumen framework (micro-framework of Laravel) cause it also has support for Eloquent usage. But unfortunately your composer requires from me specially Laravel.

    Pls change your dependencies to concrete illuminate packages versions so this package can be installed on both Laravel and Lumen

    opened by silwerclaw 2
  • Typo in docs?

    Typo in docs?

    The optional getAttribute method receives the raw $value from the database, and should return a mutated value. If this method is omitted, the raw database value will be returned.
    

    Is it really getAttribute or rather castAttribute? The example is kinda vague about this.

    opened by pr4xx 1
  • Chainable casts

    Chainable casts

    Idea is to add the ability to use chained casts. Example

    class User extends Model {
      protected $casts = [
        'meta' => ['json', CastToMetadataClass::class],
      ];
    }
    

    with this you can build a lot of reusable parts and combine them

    opened by Marko298 1
  • Laravel 5.8 incompatibility

    Laravel 5.8 incompatibility

    Hi It seems your package is incompatible with Laravel v5.8.

    λ composer require vkovic/laravel-custom-casts
    Using version ^1.0 for vkovic/laravel-custom-casts
    ./composer.json has been updated
    Loading composer repositories with package information
    Updating dependencies (including require-dev)
    Your requirements could not be resolved to an installable set of packages.
    
      Problem 1
        - Conclusion: remove laravel/framework v5.8.14
        - Conclusion: don't install laravel/framework v5.8.14
        - vkovic/laravel-custom-casts v1.0.0 requires laravel/framework 5.5.*|5.6.*|5.7.* -> satisfiable by laravel/framework[5.5.x-dev, 5.6.x-dev, 5.7.x-dev].
        - vkovic/laravel-custom-casts v1.0.1 requires laravel/framework 5.5.*|5.6.*|5.7.* -> satisfiable by laravel/framework[5.5.x-dev, 5.6.x-dev, 5.7.x-dev].
        - Can only install one of: laravel/framework[5.5.x-dev, v5.8.14].
        - Can only install one of: laravel/framework[5.6.x-dev, v5.8.14].
        - Can only install one of: laravel/framework[5.7.x-dev, v5.8.14].
        - Installation request for laravel/framework (locked at v5.8.14, required as 5.8.*) -> satisfiable by laravel/framework[v5.8.14].
        - Installation request for vkovic/laravel-custom-casts ^1.0 -> satisfiable by vkovic/laravel-custom-casts[v1.0.0, v1.0.1].
    
    Installation failed, reverting ./composer.json to its original content.
    
    opened by lidoma 1
  • Support for ValueObject cast.

    Support for ValueObject cast.

    Is your feature request related to a problem? Please describe. Laravel 9 supports AttributeCasting for ValueObject for example class money I wanna this library to support ValueObject casting.

    Describe the solution you'd like We probably should add a way to allow to return of multiple value for setAttribute via hashmap or via some custom DTO object SetAttributes

        public function setAttribute($value)
        {
            return ucwords($value);
        }
    

    We should return Describe alternatives you've considered

    Additional context image

    opened by Legion112 1
  • Is this the same as laravel custom cast attributes in laravel 7?

    Is this the same as laravel custom cast attributes in laravel 7?

    I would like to know if this is the same as laravel custom cast attributes in laravel 7... Is this the same repo merge in the laravel framework or different? What are the difference with this repo to the one being used by the framework thanks

    question 
    opened by codeitlikemiley 1
Releases(v1.3.1)
Owner
Vladimir Ković
Founder @movor
Vladimir Ković
Laravel-model-mapper - Map your model attributes to class properties with ease.

Laravel Model-Property Mapper This package provides functionality to map your model attributes to local class properties with the same names. The pack

Michael Rubel 15 Oct 29, 2022
Encryption cast for Eloquent

Encrypted A custom cast class for Laravel Eloquent that encrypts or hashes your values. Package is small and provides just a few, simple, well tested

null 58 Oct 1, 2022
Ani Cast - Anime List & Trending App. (Powered by Jikan API)

(Under Development) Ani Cast - Anime Shows App.

Noval 2 Jun 15, 2022
Guess attributes for Laravel model factories

Eloquent Populator This package provides default attributes for Laravel model factories by guessing the best Faker formatters from columns' names and

Guido Cella 68 Aug 11, 2022
Update multiple Laravel Model records, each with it's own set of values, sending a single query to your database!

Laravel Mass Update Update multiple Laravel Model records, each with its own set of values, sending a single query to your database! Installation You

Jorge González 88 Dec 31, 2022
cybercog 996 Dec 28, 2022
A laravel package to handle sanitize process of model data to create/update model records.

Laravel Model UUID A simple package to sanitize model data to create/update table records. Installation Require the package using composer: composer r

null 66 Sep 19, 2022
A laravel package to generate model hashid based on model id column.

Laravel Model Hashid A package to generate model hash id from the model auto increment id for laravel models Installation Require the package using co

Touhidur Rahman 13 Jan 20, 2022
A package to filter laravel model based on query params or retrieved model collection

Laravel Filterable A package to filter laravel model based on query params or retrived model collection. Installation Require/Install the package usin

Touhidur Rahman 17 Jan 20, 2022
⏱️Make Laravel model versionable

laravel-versionable ⏱️ Make Laravel model versionable. It's a minimalist way to make your model support version history, and it's very simple to roll

安正超 337 Dec 28, 2022
Make custom helper just with one command!

Laravel Custom Helper Make custom helper just with one command! Very light!!! Installation requires Laravel 8+ Via composer: $ composer require Ranjba

Ali Ranjbar 2 Aug 23, 2022
Podcastwala - Your very own Podcast web app built with Laravel. Manage and listen to your favorite podcasts

Podcastwala Your very own Podcast web app built with Laravel 5. This web app enables you to manage RSS feeds for your favorite podcasts and listen to

null 142 Sep 14, 2022
🔌 Autowire and configure using PHP 8 Attributes in Laravel.

?? Autowire for Laravel Autowire and configure using PHP 8 Attributes in Laravel. Installation Via Composer composer require jeroen-g/autowire You wil

JeroenG 13 Oct 7, 2022
Provide all attributes (including irregular patterns) to Laravel Blade class components.

blade-wants-attributes blade-wants-attributes offers you the ability to use Blade/HTML-defined attributes within the constructors of Laravel Blade cla

Stephan Casas 4 Sep 15, 2022
Control frontend access to properties/methods in Livewire using PHP 8 attributes.

This package adds PHP 8.0 attribute support to Livewire. In specific, the attributes are used for flagging component properties and methods as frontend-accessible.

ARCHTECH 83 Dec 17, 2022
Easily validate data attributes through a remote request

Laravel Remote Rule Easily validate data attributes through a remote request. This package allows you to define a subset of custom rules to validate a

H-FARM Innovation 27 Nov 20, 2022
Generate previous attributes when saving Eloquent models

This package provides a trait that will generate previous attributes when saving any Eloquent model.

Ricardo Sawir 33 Nov 6, 2022
This package allows you to render livewire components like a blade component, giving it attributes, slots etc

X-livewire This package allows you to render livewire components like a blade component, giving it attributes, slots etc. Assuming you wanted to creat

null 7 Nov 15, 2022
A simple pure PHP RADIUS client supporting Standard and Vendor-Specific Attributes in single file

BlockBox-Radius A simple pure PHP RADIUS client supporting Standard and Vendor-Specific Attributes in single file Author: Daren Yeh [email protected]

null 2 Oct 2, 2022