├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── config └── mutators.php ├── phpunit.xml ├── src ├── Console │ ├── InstallCommand.php │ └── PublishCommand.php ├── Contracts │ └── Factory.php ├── Database │ └── Eloquent │ │ ├── Concerns │ │ └── HasAttributes.php │ │ └── Model.php ├── Exceptions │ └── UnregisteredMutatorException.php ├── Facades │ └── Mutator.php ├── Mutable.php ├── Mutator.php └── MutatorServiceProvider.php ├── stubs └── MutatorServiceProvider.stub └── tests ├── .gitkeep ├── Models └── Post.php ├── MutatorTest.php ├── TestCase.php └── migrations.php /.gitignore: -------------------------------------------------------------------------------- 1 | /bootstrap/compiled.php 2 | .env.*.php 3 | .env.php 4 | .env 5 | /vendor/ 6 | /.idea/ 7 | /composer.lock 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Fork this repository. 11 | 2. Add new features, fix bug or bring improvements. 12 | 3. Update the README.md with details of changes (optional). 13 | 4. Increase the version numbers in any examples files and the README.md to the new version that this 14 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 15 | 5. Make a pull request. 16 | 17 | ## Code of Conduct 18 | 19 | ### Our Pledge 20 | 21 | In the interest of fostering an open and welcoming environment, we as 22 | contributors and maintainers pledge to making participation in our project and 23 | our community a harassment-free experience for everyone, regardless of age, body 24 | size, disability, ethnicity, gender identity and expression, level of experience, 25 | nationality, personal appearance, race, religion, or sexual identity and 26 | orientation. 27 | 28 | ### Our Standards 29 | 30 | Examples of behavior that contributes to creating a positive environment 31 | include: 32 | 33 | * Using welcoming and inclusive language 34 | * Being respectful of differing viewpoints and experiences 35 | * Gracefully accepting constructive criticism 36 | * Focusing on what is best for the community 37 | * Showing empathy towards other community members 38 | 39 | Examples of unacceptable behavior by participants include: 40 | 41 | * The use of sexualized language or imagery and unwelcome sexual attention or 42 | advances 43 | * Trolling, insulting/derogatory comments, and personal or political attacks 44 | * Public or private harassment 45 | * Publishing others' private information, such as a physical or electronic 46 | address, without explicit permission 47 | * Other conduct which could reasonably be considered inappropriate in a 48 | professional setting 49 | 50 | ### Our Responsibilities 51 | 52 | Project maintainers are responsible for clarifying the standards of acceptable 53 | behavior and are expected to take appropriate and fair corrective action in 54 | response to any instances of unacceptable behavior. 55 | 56 | Project maintainers have the right and responsibility to remove, edit, or 57 | reject comments, commits, code, wiki edits, issues, and other contributions 58 | that are not aligned to this Code of Conduct, or to ban temporarily or 59 | permanently any contributor for other behaviors that they deem inappropriate, 60 | threatening, offensive, or harmful. 61 | 62 | ### Scope 63 | 64 | This Code of Conduct applies both within project spaces and in public spaces 65 | when an individual is representing the project or its community. Examples of 66 | representing a project or community include using an official project e-mail 67 | address, posting via an official social media account, or acting as an appointed 68 | representative at an online or offline event. Representation of a project may be 69 | further defined and clarified by project maintainers. 70 | 71 | ### Enforcement 72 | 73 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 74 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 75 | complaints will be reviewed and investigated and will result in a response that 76 | is deemed necessary and appropriate to the circumstances. The project team is 77 | obligated to maintain confidentiality with regard to the reporter of an incident. 78 | Further details of specific enforcement policies may be posted separately. 79 | 80 | Project maintainers who do not follow or enforce the Code of Conduct in good 81 | faith may face temporary or permanent repercussions as determined by other 82 | members of the project's leadership. 83 | 84 | ### Attribution 85 | 86 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 87 | available at [http://contributor-covenant.org/version/1/4][version] 88 | 89 | [homepage]: http://contributor-covenant.org 90 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Awobaz Technologies Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Eloquent Mutators 2 | ================== 3 | 4 | **Eloquent Mutators** allows us to define accessors and mutators outside of an Eloquent model. This gives us the ability to organize and reuse them on any model or any attribute of the same model. 5 | 6 | ## The problem 7 | 8 | Eloquent has support for [accessors and mutators](https://laravel.com/docs/5.7/eloquent-mutators). However, it requires us to define them directly in the model. What if we want to reuse an accessor/mutator logic in another model? Or, what if we want to reuse an accessor/mutator logic for another attribute of the same model? We can't! **Eloquent Mutators** aims at solving this limitation. 9 | 10 | #### Related discussions: 11 | 12 | * [Reusing accessors & mutators](https://stackoverflow.com/questions/37725691/reusing-an-accessors-mutators/37727418#37727418) 13 | * [Simple and organized accessors & mutators](https://github.com/laravel/ideas/issues/1270) 14 | 15 | ## Installation 16 | 17 | The recommended way to install **Eloquent Mutators** is through [Composer](http://getcomposer.org/) 18 | 19 | ```bash 20 | $ composer require awobaz/eloquent-mutators 21 | ``` 22 | 23 | The package will automatically register itself if you're using Laravel 5.5+. For Laravel 5.4, you'll have to register the package manually: 24 | 25 | 1) Open your `config/app.php` and add the following to the `providers` array: 26 | 27 | ```php 28 | Awobaz\Mutator\MutatorServiceProvider::class, 29 | ``` 30 | 31 | 2) In the same `config/app.php` add the following to the `aliases ` array: 32 | 33 | ```php 34 | 'Mutator' => Awobaz\Mutator\Facades\Mutator::class, 35 | ``` 36 | 37 | > **Note:** **Eloquent Mutators** requires Laravel 5.4+. 38 | 39 | After installation, publish the assets using the `mutators:install` Artisan command. The primary configuration file will be located at `config/mutators.php`. The installation also publishes and registers the `app/Providers/MutatorServiceProvider.php`. Within this service provider, you may register custom accessors/mutators extensions. 40 | 41 | ```sh 42 | php artisan mutators:install 43 | ``` 44 | 45 | ## Usage 46 | 47 | ### Using the `Awobaz\Mutator\Database\Eloquent\Model` class 48 | 49 | Simply make your model class derive from the `Awobaz\Mutator\Database\Eloquent\Model` base class. The `Awobaz\Mutator\Database\Eloquent\Model` extends the `Eloquent` base class without changing its core functionality. 50 | 51 | ### Using the `Awobaz\Mutator\Mutable` trait 52 | 53 | If for some reasons you can't derive your models from `Awobaz\Mutator\Database\Eloquent\Model`, you may take advantage of the `Awobaz\Mutator\Mutable` trait. Simply use the trait in your models. 54 | 55 | ### Syntax 56 | 57 | After configuring your model, you may configure accessors and mutators for its attributes. 58 | 59 | #### Defining accessors 60 | 61 | For the following Post model, we configure accessors to trim whitespace from the beginning and end of the `title` and `content` attributes: 62 | 63 | ```php 64 | namespace App; 65 | 66 | use Illuminate\Database\Eloquent\Model; 67 | 68 | class Post extends Model 69 | { 70 | use \Awobaz\Mutator\Mutable; 71 | 72 | protected $accessors = [ 73 | 'title' => 'trim_whitespace', 74 | 'content' => 'trim_whitespace', 75 | ]; 76 | } 77 | ``` 78 | 79 | As you can see, we use an array property named `accessors` on the model to configure its **accessors**. Each key of the array represents the name of an attribute, and the value points to one or multiple accessors. To apply multiple accessors, pass an array as value (the accessors will be applied in the order they are specified): 80 | 81 | ```php 82 | namespace App; 83 | 84 | use Illuminate\Database\Eloquent\Model; 85 | 86 | class Post extends Model 87 | { 88 | use \Awobaz\Mutator\Mutable; 89 | 90 | protected $accessors = [ 91 | 'title' => ['trim_whitespace', 'capitalize'], 92 | 'content' => ['trim_whitespace', 'remove_extra_whitespace'], 93 | ]; 94 | } 95 | ``` 96 | 97 | #### Defining mutators 98 | 99 | To define mutators, use an array property named `mutators` instead. 100 | 101 | ```php 102 | namespace App; 103 | 104 | use Illuminate\Database\Eloquent\Model; 105 | 106 | class Post extends Model 107 | { 108 | use \Awobaz\Mutator\Mutable; 109 | 110 | protected $mutators = [ 111 | 'title' => 'remove_extra_whitespace', 112 | ]; 113 | } 114 | ``` 115 | 116 | > **Note:** The name of the properties used for accessors and mutators can be respectively configured in the `config/mutators.php` configuration file. 117 | 118 | ### Defining accessors/mutators extensions 119 | 120 | In the previous examples, we use [accessors/mutators provided](#built-in-accessorsmutators) by the package. You may also register accessors/mutators extensions using the **extend** method of the `Mutator` facade. The **extend** method accepts the name of the accessor/mutator and a closure. 121 | 122 | ``` 123 | ['str_replace' => ['one', 'two']] 191 | //OR 192 | //'content' => 'str_replace:one,two' 193 | ]; 194 | } 195 | ``` 196 | 197 | This will replace every occurence of ***one*** with ***two*** for the `content` attribute. 198 | 199 | ## Built-in accessors/mutators 200 | 201 | - [`lower_case`](#lower_case) 202 | - [`upper_case`](#upper_case) 203 | - [`capitalize`](#capitalize) 204 | - [`capitalize_words`](#capitalize_words) 205 | - [`trim_whitespace`](#trim_whitespace) 206 | - [`camel_case`](#camel_case) 207 | - [`snake_case`](#snake_case) 208 | - [`kebab_case`](#kebab_case) 209 | - [`studly_case`](#studly_case) 210 | - [`title_case`](#title_case) 211 | - [`plural`](#plural) 212 | - [`singular`](#singular) 213 | - [`slug`](#slug) 214 | - [`remove_extra_whitespace`](#remove_extra_whitespace) 215 | - [`preg_replace:pattern,replacement[,limit]`](#preg_replacepatternreplacementlimit) 216 | 217 | ### `lower_case` 218 | Convert the attribute to lower case. 219 | 220 | ### `upper_case` 221 | Convert the attribute to upper case. 222 | 223 | ### `capitalize` 224 | Convert the first character of attribute to upper case. 225 | 226 | ### `capitalize_words` 227 | Convert the first character of each word of the attribute to upper case. 228 | 229 | ### `trim_whitespace` 230 | Strip whitespace from the beginning and end of the attribute. 231 | 232 | ### `camel_case` 233 | Convert the attribute to camel case. 234 | 235 | ### `snake_case` 236 | Convert the attribute to snake case. 237 | 238 | ### `studly_case` 239 | Convert the attribute to studly case. 240 | 241 | ### `kebab_case` 242 | Convert the attribute to kebab case. 243 | 244 | ### `title_case` 245 | Convert the attribute to title case. 246 | 247 | ### `plural` 248 | Convert the attribute to its plural form (only supports the English language). 249 | 250 | ### `singular` 251 | Convert the attribute to its singular form (only supports the English language). 252 | 253 | ### `slug` 254 | Convert the attribute to its URL friendly "slug" form. 255 | 256 | ### `remove_extra_whitespace` 257 | Remove extra whitespaces within the attribute. 258 | 259 | ### `preg_replace:pattern,replacement[,limit]` 260 | Perform a regular expression search and replace on the attribute. 261 | 262 | ## Versioning 263 | 264 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/topclaudy/eloquent-mutators/tags). 265 | 266 | ## Unit Tests 267 | 268 | In order to run the test suite, install the development dependencies: 269 | 270 | ```bash 271 | $ composer install --dev 272 | ``` 273 | 274 | Then, run the following command: 275 | 276 | ```bash 277 | $ vendor/bin/phpunit 278 | ``` 279 | 280 | ## Authors 281 | 282 | * [Claudin J. Daniel](https://github.com/topclaudy) - *Initial work* 283 | 284 | ## Contributing 285 | 286 | Please read [CONTRIBUTING.md](https://github.com/topclaudy/eloquent-mutators/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests. 287 | 288 | [![](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/images/0)](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/links/0) 289 | [![](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/images/1)](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/links/1) 290 | [![](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/images/2)](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/links/2) 291 | [![](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/images/3)](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/links/3) 292 | [![](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/images/4)](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/links/4) 293 | [![](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/images/5)](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/links/5) 294 | [![](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/images/6)](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/links/6) 295 | [![](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/images/7)](https://sourcerer.io/fame/topclaudy/topclaudy/eloquent-mutators/links/7) 296 | 297 | 298 | ## Sponsored by 299 | 300 | * [Awobaz](https://awobaz.com) - Web/Mobile agency based in Montreal, Canada 301 | 302 | ## License 303 | 304 | **Eloquent Mutators** is licensed under the [MIT License](http://opensource.org/licenses/MIT). -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awobaz/eloquent-mutators", 3 | "description": "Reusable accessors/mutators (getters/setters) for Laravel 5's Eloquent", 4 | "license": "MIT", 5 | "keywords": [ 6 | "laravel", 7 | "laravel mutators", 8 | "laravel accessors", 9 | "laravel getters setters" 10 | ], 11 | "authors": [ 12 | { 13 | "name": "Claudin J. Daniel", 14 | "email": "cdaniel@awobaz.com" 15 | } 16 | ], 17 | "require": { 18 | "php": "^7.1|^8.0", 19 | "illuminate/database": "~5.4|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", 20 | "illuminate/console": "~5.4|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", 21 | "illuminate/support": "~5.4|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", 22 | "illuminate/cache": "~5.4|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", 23 | "laravel/helpers": "^1.5" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "~5.4|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", 27 | "laravel/laravel": "~5.4|~6.0|~7.0|~8.0|~9.0|~10.0|~11.0", 28 | "fakerphp/faker": "^1.8" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Awobaz\\Mutator\\": "src" 33 | }, 34 | "files": [ 35 | ] 36 | }, 37 | "extra": { 38 | "laravel": { 39 | "providers": [ 40 | "Awobaz\\Mutator\\MutatorServiceProvider" 41 | ] 42 | } 43 | }, 44 | "suggest": { 45 | "awobaz/compoships": "Multi-columns relationships for Laravel 5's Eloquent", 46 | "awobaz/eloquent-auto-append": "Automatically append accessors to model serialization", 47 | "awobaz/blade-active": "Blade directives for the Laravel 'Active' package", 48 | "awobaz/syntactic": "Syntactic sugar for named and indexed parameters call." 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /config/mutators.php: -------------------------------------------------------------------------------- 1 | 'accessors', 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Eloquent property for setters 19 | |-------------------------------------------------------------------------- 20 | | 21 | | This configuration options define the name of the property 22 | | be used to define setters on the Eloquent model. 23 | | 24 | */ 25 | 26 | 'mutators_property' => 'mutators', 27 | ]; 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Console/InstallCommand.php: -------------------------------------------------------------------------------- 1 | comment('Publishing Eloquent Mutators Service Provider...'); 32 | $this->callSilent('vendor:publish', ['--tag' => 'mutators-provider']); 33 | 34 | $this->comment('Publishing Eloquent Mutators Configuration...'); 35 | $this->callSilent('vendor:publish', ['--tag' => 'mutators-config']); 36 | 37 | $this->registerMutatorServiceProvider(); 38 | 39 | $this->info('Eloquent Mutators scaffolding installed successfully.'); 40 | } 41 | 42 | /** 43 | * Register the Mutator service provider in the application configuration file. 44 | * 45 | * @return void 46 | */ 47 | protected function registerMutatorServiceProvider() 48 | { 49 | $namespace = str_replace_last('\\', '', $this->laravel->getNamespace()); 50 | 51 | $appConfig = file_get_contents(config_path('app.php')); 52 | 53 | if (Str::contains($appConfig, $namespace.'\\Providers\\MutatorServiceProvider::class')) { 54 | return; 55 | } 56 | 57 | file_put_contents(config_path('app.php'), str_replace("{$namespace}\\Providers\EventServiceProvider::class,".PHP_EOL, "{$namespace}\\Providers\EventServiceProvider::class,".PHP_EOL." {$namespace}\Providers\MutatorServiceProvider::class,".PHP_EOL, $appConfig)); 58 | 59 | file_put_contents(app_path('Providers/MutatorServiceProvider.php'), str_replace("namespace App\Providers;", "namespace {$namespace}\Providers;", file_get_contents(app_path('Providers/MutatorServiceProvider.php')))); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Console/PublishCommand.php: -------------------------------------------------------------------------------- 1 | call('vendor:publish', [ 31 | '--tag' => 'mutators-config', 32 | '--force' => $this->option('force'), 33 | ]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Contracts/Factory.php: -------------------------------------------------------------------------------- 1 | getName(); 26 | 27 | if (property_exists($class, config('mutators.accessors_property'))) { 28 | static::$mutatorCache[$class] = array_merge(static::$mutatorCache[$class], array_keys(with(new $class())->{config('mutators.accessors_property')})); 29 | } 30 | } 31 | 32 | /** 33 | * Get a plain attribute (not a relationship). 34 | * 35 | * @param string $key 36 | * 37 | * @return mixed 38 | */ 39 | public function getAttributeValue($key) 40 | { 41 | $value = parent::getAttributeValue($key); 42 | 43 | return $this->applyAccessors($key, $value); 44 | } 45 | 46 | /** 47 | * @param $key 48 | * @param $value 49 | * 50 | * @return mixed 51 | */ 52 | protected function applyAccessors($key, $value) 53 | { 54 | // If the attribute has custom accessors, we will call them 55 | foreach ($this->getMutatorsFor($key, config('mutators.accessors_property')) as $accessor => $params) { 56 | $value = Mutator::get($accessor)($this, $value, $key, ...$params); 57 | } 58 | 59 | return $value; 60 | } 61 | 62 | /** 63 | * Set a given attribute on the model. 64 | * 65 | * @param string $key 66 | * @param mixed $value 67 | * 68 | * @return mixed 69 | */ 70 | public function setAttribute($key, $value) 71 | { 72 | $value = $this->applyMutators($key, $value); 73 | 74 | return parent::setAttribute($key, $value); 75 | } 76 | 77 | /** 78 | * @param $key 79 | * @param $value 80 | * 81 | * @return 82 | */ 83 | protected function applyMutators($key, $value) 84 | { 85 | // If the attribute has custom mutators, we will call them 86 | foreach ($this->getMutatorsFor($key, config('mutators.mutators_property')) as $mutator => $params) { 87 | $value = Mutator::get($mutator)($this, $value, $key, ...$params); 88 | } 89 | 90 | return $value; 91 | } 92 | 93 | /** 94 | * Get the value of an attribute using its mutator. 95 | * 96 | * @param string $key 97 | * @param mixed $value 98 | * 99 | * @return mixed 100 | */ 101 | protected function mutateAttribute($key, $value) 102 | { 103 | if (! array_key_exists($key, $this->{config('mutators.accessors_property')} ?: [])) { 104 | $value = parent::mutateAttribute($key, $value); 105 | } elseif (method_exists($this, 'get'.Str::studly($key).'Attribute')) { 106 | $value = parent::mutateAttribute($key, $value); 107 | } 108 | 109 | return $this->applyAccessors($key, $value); 110 | } 111 | 112 | /** 113 | * Get the mutators for a given attribute. 114 | * 115 | * @param string $key The name of the attribute we want to mutate 116 | * @param string $type The type of mutation: accessor or mutator 117 | * 118 | * @return array 119 | */ 120 | protected function getMutatorsFor($key, $type) 121 | { 122 | $mutators = $this->{$type}; 123 | if (empty($mutators) || ! is_array($mutators) || ! isset($mutators[$key])) { 124 | return []; 125 | } 126 | 127 | $result = []; 128 | foreach ((array) $mutators[$key] as $mutator => $params) { 129 | $parsed = $this->parseMutatorNameAndParams($mutator, $params); 130 | $result[$parsed[0]] = $parsed[1]; 131 | } 132 | 133 | return $result; 134 | } 135 | 136 | /** 137 | * Separates the mutator name from its optional parameters. 138 | * 139 | * @param int|string $mutator 140 | * @param string|array|mixed $params 141 | * 142 | * @return array 143 | */ 144 | protected function parseMutatorNameAndParams($mutator, $params) 145 | { 146 | if (is_int($mutator) && is_string($params)) { 147 | $params = explode(':', $params); 148 | $mutator = array_shift($params); 149 | 150 | return [$mutator, count($params) > 1 ? str_getcsv(implode(':', $params)) : []]; 151 | } 152 | 153 | return [$mutator, Arr::wrap($params)]; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Database/Eloquent/Model.php: -------------------------------------------------------------------------------- 1 | extensions[$mutator] = $extension; 29 | 30 | return $this; 31 | } 32 | 33 | public function get($name) 34 | { 35 | if (! isset($this->extensions[$name])) { 36 | throw new UnregisteredMutatorException("The mutator '{$name}' is not registered"); 37 | } 38 | 39 | return $this->extensions[$name]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/MutatorServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerPublishing(); 20 | } 21 | 22 | /** 23 | * Register the package's publishable resources. 24 | * 25 | * @return void 26 | */ 27 | private function registerPublishing() 28 | { 29 | if ($this->app->runningInConsole()) { 30 | $this->publishes([ 31 | __DIR__.'/../config/mutators.php' => config_path('mutators.php'), 32 | ], 'mutators-config'); 33 | 34 | $this->publishes([ 35 | __DIR__.'/../stubs/MutatorServiceProvider.stub' => app_path('Providers/MutatorServiceProvider.php'), 36 | ], 'mutators-provider'); 37 | } 38 | } 39 | 40 | /** 41 | * Register any package services. 42 | * 43 | * @return void 44 | */ 45 | public function register() 46 | { 47 | $this->registerBindings(); 48 | 49 | $this->registerCommands(); 50 | 51 | $this->registerDefaultExtensions(); 52 | 53 | $this->mergeConfig(); 54 | } 55 | 56 | /** 57 | * Register the application bindings. 58 | * 59 | * @return void 60 | */ 61 | private function registerBindings() 62 | { 63 | $this->app->singleton('mutator', function ($app) { 64 | return new Mutator(); 65 | }); 66 | 67 | $this->app->alias('mutator', 'Awobaz\Mutator\Mutator'); 68 | } 69 | 70 | /** 71 | * Register the application commands. 72 | * 73 | * @return void 74 | */ 75 | private function registerCommands() 76 | { 77 | $this->commands([ 78 | InstallCommand::class, 79 | PublishCommand::class, 80 | ]); 81 | } 82 | 83 | private function registerDefaultExtensions() 84 | { 85 | $extensions = [ 86 | //PHP functions 87 | 'mb_strtolower' => 'lower_case', 88 | 'mb_strtoupper' => 'upper_case', 89 | 'ucfirst' => 'capitalize', 90 | 'ucwords' => 'capitalize_words', 91 | 'trim' => 'trim_whitespace', 92 | //Framework functions 93 | 'camel_case' => 'camel_case', 94 | 'snake_case' => 'snake_case', 95 | 'kebab_case' => 'kebab_case', 96 | 'studly_case' => 'studly_case', 97 | 'title_case' => 'title_case', 98 | 'str_plural' => 'plural', 99 | 'str_singular' => 'singular', 100 | 'str_slug' => 'slug', 101 | ]; 102 | 103 | foreach ($extensions as $function => $extension) { 104 | if (function_exists($function)) { 105 | MutatorFacade::extend($extension, function ($model, $value, $key) use ($function) { 106 | return $function($value); 107 | }); 108 | } 109 | } 110 | 111 | MutatorFacade::extend('remove_extra_whitespace', function ($model, $value, $key) { 112 | return preg_replace('/\s+/', ' ', $value); 113 | }); 114 | 115 | MutatorFacade::extend('preg_replace', function ($model, $value, $key, $pattern, $replacement, $limit = -1) { 116 | return preg_replace($pattern, $replacement, $value, $limit); 117 | }); 118 | } 119 | 120 | private function mergeConfig() 121 | { 122 | $this->mergeConfigFrom(__DIR__.'/../config/mutators.php', 'mutators'); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /stubs/MutatorServiceProvider.stub: -------------------------------------------------------------------------------- 1 | ['trim_whitespace', 'remove_extra_whitespace', 'nice'], 11 | 'content' => [ 12 | 'replace_words' => ['two', 'one'], 13 | ], 14 | ]; 15 | 16 | protected $mutators = [ 17 | 'title' => ['prepend_star', 'copy_to' => 'slug'], 18 | 'slug' => 'slug', 19 | 'content' => ['replace_words:five,three,four,"one:two"', 'preg_replace' => ['/Batman/', 'Bruce Wayne']], 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /tests/MutatorTest.php: -------------------------------------------------------------------------------- 1 | title = 'Mutators are awesome '; 22 | $post->content = 'Real interesting'; 23 | $post->save(); 24 | 25 | $raw = $post->getAttributes(); 26 | $this->assertEquals('*Mutators are awesome ', $raw['title']); 27 | $this->assertEquals('*Mutators are nice', $post->title); 28 | 29 | $this->assertEquals('mutators-are-awesome', $post->slug); 30 | $this->assertEquals($post->slug, $raw['slug']); 31 | 32 | $this->assertEquals('Real interesting', $post->content); 33 | $this->assertEquals('Real interesting', $raw['content']); 34 | 35 | $post->content = 'one'; 36 | $raw = $post->getAttributes(); 37 | $this->assertEquals('two', $post->content); 38 | $this->assertEquals('one', $raw['content']); 39 | 40 | $post->content = 'three four'; 41 | $raw = $post->getAttributes(); 42 | $this->assertEquals('five five', $post->content); 43 | $this->assertEquals('five five', $raw['content']); 44 | 45 | $post->content = 'one:two'; 46 | $raw = $post->getAttributes(); 47 | $this->assertEquals('five', $post->content); 48 | $this->assertEquals('five', $raw['content']); 49 | 50 | $post->content = 'I saw Batman'; 51 | $raw = $post->getAttributes(); 52 | $this->assertEquals('I saw Bruce Wayne', $post->content); 53 | $this->assertEquals('I saw Bruce Wayne', $raw['content']); 54 | 55 | Model::unguard(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | make('Illuminate\Contracts\Console\Kernel')->bootstrap(); 21 | 22 | return $app; 23 | } 24 | 25 | /** 26 | * Setup DB before each test. 27 | * 28 | * @return void 29 | */ 30 | public function setUp(): void 31 | { 32 | parent::setUp(); 33 | 34 | $this->app->register(MutatorServiceProvider::class); 35 | 36 | $this->app['config']->set('database.default', 'sqlite'); 37 | $this->app['config']->set('database.connections.sqlite.database', ':memory:'); 38 | 39 | $this->app['config']->set('mutators.accessors_property', 'accessors'); 40 | $this->app['config']->set('mutators.mutators_property', 'mutators'); 41 | 42 | $this->app->singleton('mutator', function ($app) { 43 | return new Mutator(); 44 | }); 45 | 46 | $this->app->alias('mutator', 'Awobaz\Mutator\Mutator'); 47 | 48 | MutatorFacade::extend('trim_whitespace', function ($model, $value, $key) { 49 | return trim($value); 50 | })->extend('remove_extra_whitespace', function ($model, $value, $key) { 51 | return preg_replace('/\s+/', ' ', $value); 52 | })->extend('nice', function ($model, $value, $key) { 53 | return str_replace('awesome', 'nice', $value); 54 | })->extend('prepend_star', function ($model, $value, $key) { 55 | return '*'.$value; 56 | })->extend('copy_to', function ($model, $value, $key, $to) { 57 | $model->{$to} = $value; 58 | 59 | return $value; 60 | })->extend('replace_words', function ($model, $value, $key, $replace, ...$search) { 61 | return str_replace($search, $replace, $value); 62 | }); 63 | 64 | $this->migrate(); 65 | } 66 | 67 | /** 68 | * run package database migrations. 69 | * 70 | * @return void 71 | */ 72 | public function migrate() 73 | { 74 | $fileSystem = new Filesystem(); 75 | $fileSystem->requireOnce(__DIR__.'/migrations.php'); 76 | 77 | (new Migration())->up(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/migrations.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->text('title'); 19 | $table->text('slug'); 20 | $table->text('content')->nullable(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::drop('posts'); 33 | } 34 | } 35 | --------------------------------------------------------------------------------