├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── php.yml ├── src ├── Exceptions │ └── InvalidAttributeException.php ├── FluidInstanceHelper.php ├── Concerns │ ├── HasArrayAccess.php │ └── HidesAttributes.php ├── FluidFillable.php └── Fluid.php ├── phpunit.xml ├── composer.json ├── LICENSE ├── tests ├── FluidInstanceHelpersTest.php ├── FluidFillableTest.php └── FluidTest.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /build 4 | .DS_Store 5 | composer.lock -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Help me support this package 2 | 3 | ko_fi: DarkGhostHunter 4 | custom: ['https://paypal.me/darkghosthunter'] 5 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidAttributeException.php: -------------------------------------------------------------------------------- 1 | message = "Attribute [$attribute] in not set as fillable in " . 13 | substr(strrchr(get_class($instance), "\\"), 1) . '.'; 14 | 15 | parent::__construct(); 16 | } 17 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | tests 9 | 10 | 11 | 12 | 13 | src 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "darkghosthunter/fluid", 3 | "description": "Fluid utility class based on Laravel's Fluent class", 4 | "minimum-stability": "dev", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Italo Baeza Cabrera", 9 | "email": "darkghosthunter@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php" : ">=7.4", 14 | "ext-json": "*" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": "^9.3" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "DarkGhostHunter\\Fluid\\": "src" 22 | } 23 | }, 24 | "autoload-dev": { 25 | "psr-4": { 26 | "Tests\\" : "tests" 27 | } 28 | }, 29 | "scripts": { 30 | "test": "vendor/bin/phpunit" 31 | }, 32 | "config": { 33 | "sort-packages": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2018] [Italo Israel Baeza Cabrera] 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 | -------------------------------------------------------------------------------- /src/FluidInstanceHelper.php: -------------------------------------------------------------------------------- 1 | setAttributes($attributes); 31 | 32 | return $fluent; 33 | } 34 | 35 | /** 36 | * Create a new Fluid instance from a JSON string 37 | * 38 | * @param string $class 39 | * @param string $json 40 | * @return Fluid 41 | */ 42 | public static function fromJson(string $class, string $json) 43 | { 44 | return static::make($class, json_decode($json, true)); 45 | } 46 | } -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: true 13 | matrix: 14 | php: [8.0, 7.4] 15 | dependency-version: [prefer-lowest, prefer-stable] 16 | 17 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.dependency-version }} 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: ${{ matrix.php }} 27 | extensions: mbstring, intl 28 | coverage: xdebug 29 | 30 | - name: Cache dependencies 31 | uses: actions/cache@v2 32 | with: 33 | path: ~/.composer/cache/files 34 | key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 35 | restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer- 36 | 37 | - name: Install dependencies 38 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-progress --no-suggest 39 | 40 | - name: Run Tests 41 | run: composer run-script test 42 | 43 | - name: Upload Coverage to Coveralls 44 | env: 45 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | COVERALLS_SERVICE_NAME: github 47 | run: | 48 | rm -rf composer.* vendor/ 49 | composer require php-coveralls/php-coveralls 50 | vendor/bin/php-coveralls 51 | -------------------------------------------------------------------------------- /src/Concerns/HasArrayAccess.php: -------------------------------------------------------------------------------- 1 | toArray(); 15 | } 16 | 17 | /** 18 | * Whether a offset exists 19 | * 20 | * @param mixed $offset 21 | * @return boolean 22 | */ 23 | public function offsetExists($offset) 24 | { 25 | return isset($this->attributes[$offset]); 26 | } 27 | 28 | /** 29 | * Offset to retrieve 30 | * 31 | * @param mixed $offset 32 | * @return mixed 33 | */ 34 | public function offsetGet($offset) 35 | { 36 | return $this->getAttribute($offset); 37 | } 38 | 39 | /** 40 | * Offset to set 41 | * 42 | * @param mixed 43 | * @param mixed 44 | * @return void 45 | */ 46 | public function offsetSet($offset, $value) 47 | { 48 | $this->setAttribute($offset, $value); 49 | } 50 | 51 | /** 52 | * Offset to unset 53 | * 54 | * @param mixed $offset 55 | * @return void 56 | */ 57 | public function offsetUnset($offset) 58 | { 59 | unset($this->attributes[$offset]); 60 | } 61 | 62 | /** 63 | * Count elements of an object 64 | * 65 | * @return int 66 | */ 67 | public function count() 68 | { 69 | return count($this->attributes); 70 | } 71 | } -------------------------------------------------------------------------------- /src/Concerns/HidesAttributes.php: -------------------------------------------------------------------------------- 1 | hidden; 29 | } 30 | 31 | /** 32 | * Set the attributes to hide 33 | * 34 | * @param array $hidden 35 | */ 36 | public function setHidden(array $hidden) 37 | { 38 | $this->hidden = $hidden; 39 | } 40 | 41 | /** 42 | * If this instance should hide attributes on serialization 43 | * 44 | * @return bool 45 | */ 46 | public function isHiding() 47 | { 48 | return $this->shouldHide; 49 | } 50 | 51 | /** 52 | * If this instance should hide the attributes on serialization 53 | * 54 | * @param bool $shouldHide 55 | */ 56 | public function shouldHide(bool $shouldHide = true) 57 | { 58 | $this->shouldHide = $shouldHide; 59 | } 60 | 61 | /** 62 | * Should not hide the attributes on serialization 63 | * 64 | * @return void 65 | */ 66 | public function shouldNotHide() 67 | { 68 | $this->shouldHide = false; 69 | } 70 | } -------------------------------------------------------------------------------- /src/FluidFillable.php: -------------------------------------------------------------------------------- 1 | $attribute) { 25 | if (! in_array($key, $this->fillable, true)) { 26 | throw new InvalidAttributeException($key, $this); 27 | } 28 | } 29 | 30 | $this->setAttributes($attributes); 31 | } 32 | 33 | /** 34 | * Sets a fillable attribute 35 | * 36 | * @param string $key 37 | * @param $value 38 | * @return void 39 | * @throws InvalidAttributeException 40 | */ 41 | public function setAttribute(string $key, $value) 42 | { 43 | if (! in_array($key, $this->fillable, true)) { 44 | throw new InvalidAttributeException($key, $this); 45 | } 46 | 47 | parent::setAttribute($key, $value); 48 | } 49 | 50 | /** 51 | * Get the fillable attributes 52 | * 53 | * @return array 54 | */ 55 | public function getFillable() 56 | { 57 | return $this->fillable; 58 | } 59 | 60 | /** 61 | * Set the fillable attributes 62 | * 63 | * @param array $fillable 64 | */ 65 | public function setFillable(array $fillable) 66 | { 67 | $this->fillable = $fillable; 68 | } 69 | 70 | 71 | } -------------------------------------------------------------------------------- /tests/FluidInstanceHelpersTest.php: -------------------------------------------------------------------------------- 1 | attributes['foo'] = 'notFoo'; 17 | } 18 | }; 19 | 20 | $fluid = $fluid::make(['foo' => 'bar', 'baz' => 'qux']); 21 | 22 | $this->assertInstanceOf(Fluid::class, $fluid); 23 | $this->assertEquals(['foo' => 'notFoo', 'baz' => 'qux'], $fluid->toArray()); 24 | } 25 | 26 | public function testMakeRaw() 27 | { 28 | $fluid = new class() extends Fluid { 29 | public function setFooAttribute() 30 | { $this->attributes['foo'] = 'notFoo'; } 31 | }; 32 | 33 | $fluid = $fluid::makeRaw($array = ['foo' => 'bar', 'baz' => 'qux']); 34 | 35 | $this->assertInstanceOf(Fluid::class, $fluid); 36 | $this->assertEquals($array, $fluid->toArray()); 37 | } 38 | 39 | public function testFromJson() 40 | { 41 | $fluid = new class() extends Fluid { 42 | }; 43 | 44 | $fluid = $fluid::fromJson(json_encode($array = ['foo' => 'bar', 'baz' => 'qux'])); 45 | 46 | $this->assertInstanceOf(Fluid::class, $fluid); 47 | $this->assertEquals($array, $fluid->toArray()); 48 | } 49 | 50 | public function testBadStaticFunctionException() 51 | { 52 | $this->expectException(BadMethodCallException::class); 53 | 54 | $fluid = new class() extends Fluid { 55 | }; 56 | 57 | $fluid::Anything('anything'); 58 | } 59 | 60 | public function testCanOverrideInstanceHelpers() 61 | { 62 | $fluid = new class() extends Fluid { 63 | public static function make() 64 | { 65 | return 'overriden'; 66 | } 67 | public static function makeRaw() 68 | { 69 | return 'overriden'; 70 | } 71 | public static function fromJson() 72 | { 73 | return 'overriden'; 74 | } 75 | }; 76 | 77 | $this->assertEquals('overriden', $fluid::make()); 78 | $this->assertEquals('overriden', $fluid::makeRaw()); 79 | $this->assertEquals('overriden', $fluid::fromJson()); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /tests/FluidFillableTest.php: -------------------------------------------------------------------------------- 1 | foo = 'bar'; 19 | $fluid->baz = 'qux'; 20 | 21 | $this->assertEquals('bar', $fluid->foo); 22 | $this->assertEquals('qux', $fluid->baz); 23 | } 24 | 25 | public function testExceptionOnConstructWithUnfillable() 26 | { 27 | $this->expectException(InvalidAttributeException::class); 28 | 29 | $fluid = new class(['alpha' => 'bravo']) extends FluidFillable { 30 | protected $fillable = ['foo', 'baz']; 31 | }; 32 | } 33 | 34 | public function testFill() 35 | { 36 | $fluid = new class extends FluidFillable { 37 | protected $fillable = ['foo', 'baz']; 38 | }; 39 | 40 | $fluid->fill(['foo' => 'bar', 'baz' => 'qux']); 41 | 42 | $this->assertEquals('bar', $fluid->foo); 43 | $this->assertEquals('qux', $fluid->baz); 44 | } 45 | 46 | public function testExceptionOnSetAttributesNotFillable() 47 | { 48 | $this->expectException(InvalidAttributeException::class); 49 | 50 | $fluid = new class extends FluidFillable { 51 | protected $fillable = ['foo', 'baz']; 52 | }; 53 | 54 | $fluid->fill(['foo' => 'bar', 'baz' => 'qux', 'alpha' => 'bravo']); 55 | } 56 | 57 | public function testSetAttribute() 58 | { 59 | $fluid = new class extends FluidFillable { 60 | protected $fillable = ['foo', 'baz']; 61 | }; 62 | 63 | $fluid->setAttribute('foo', 'bar'); 64 | $fluid->setAttribute('baz', 'qux'); 65 | 66 | $this->assertEquals('bar', $fluid->foo); 67 | $this->assertEquals('qux', $fluid->baz); 68 | } 69 | 70 | public function testExceptionOnSetAttributeNotFillable() 71 | { 72 | $this->expectException(InvalidAttributeException::class); 73 | 74 | $fluid = new class extends FluidFillable { 75 | protected $fillable = ['foo', 'baz']; 76 | }; 77 | 78 | $fluid->setAttribute('alpha', 'bravo'); 79 | } 80 | 81 | public function testGetAndSetFillable() 82 | { 83 | $fluid = new class extends FluidFillable { 84 | protected $fillable = ['foo', 'baz']; 85 | }; 86 | 87 | $this->assertEquals(['foo', 'baz'], $fluid->getFillable()); 88 | 89 | $fluid->setFillable(['bar', 'qux']); 90 | 91 | $this->assertEquals(['bar', 'qux'], $fluid->getFillable()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Fluid.php: -------------------------------------------------------------------------------- 1 | fill($attributes); 38 | } 39 | 40 | /** 41 | * Fill the instance with attributes using setters 42 | * 43 | * @param array $attributes 44 | */ 45 | public function fill(array $attributes) 46 | { 47 | foreach ($attributes as $key => $attribute) { 48 | $this->setAttribute($key, $attribute); 49 | } 50 | } 51 | 52 | /** 53 | * Get all the raw attributes 54 | * 55 | * @return mixed 56 | */ 57 | public function getAttributes() 58 | { 59 | return $this->attributes; 60 | } 61 | 62 | /** 63 | * Set a Raw Attribute 64 | * 65 | * @param array $attributes 66 | */ 67 | public function setAttributes(array $attributes) 68 | { 69 | $this->attributes = $attributes; 70 | } 71 | 72 | /** 73 | * Returns an attribute 74 | * 75 | * @param string $key 76 | * @param null $default 77 | * @return mixed 78 | */ 79 | public function getAttribute(string $key, $default = null) 80 | { 81 | if (method_exists($this, $method = 'get' . ucfirst($key) . 'Attribute')) { 82 | return $this->{$method}(); 83 | } 84 | 85 | if (isset($this->attributes[$key])) return $this->attributes[$key]; 86 | 87 | return is_callable($default) ? $default() : $default; 88 | } 89 | 90 | /** 91 | * Sets an attribute 92 | * 93 | * @param string $key 94 | * @param $value 95 | * @return void 96 | */ 97 | public function setAttribute(string $key, $value) 98 | { 99 | if (method_exists($this, $method = 'set' . ucfirst($key) . 'Attribute')) { 100 | $this->{$method}($value); 101 | return; 102 | } 103 | 104 | $this->attributes[$key] = $value; 105 | } 106 | 107 | /** 108 | * Return only selected attributes 109 | * 110 | * @param array $only 111 | * @return array 112 | */ 113 | public function only(array $only) 114 | { 115 | return array_intersect_key($this->attributes, array_flip($only)); 116 | } 117 | 118 | /** 119 | * Return all the attributes except those indicated 120 | * 121 | * @param array $except 122 | * @return array 123 | */ 124 | public function except(array $except) 125 | { 126 | return array_diff_key($this->attributes, array_flip($except)); 127 | } 128 | 129 | /** 130 | * Returns an array representation of this instance. 131 | * 132 | * @return array 133 | */ 134 | public function toArray() 135 | { 136 | // If we're hiding attributes, then return those not hidden 137 | $array = $this->shouldHide && is_array($this->hidden) 138 | ? $this->except($this->hidden) 139 | : $this->attributes ?? []; 140 | 141 | // Use the getter, if its set, for each attribute 142 | foreach ($array as $key => $value) { 143 | $array[$key] = method_exists($this, $method = 'get' . ucfirst($key) . 'Attribute') 144 | ? $this->{$method}() 145 | : $value; 146 | } 147 | 148 | return $array; 149 | } 150 | 151 | /** 152 | * Returns a JSON representation of the instance 153 | * 154 | * @return string 155 | */ 156 | public function toJson() 157 | { 158 | return json_encode($this->toArray()); 159 | } 160 | 161 | /** 162 | * Dynamically get an attribute 163 | * 164 | * @param $name 165 | * @return mixed 166 | */ 167 | public function __get($name) 168 | { 169 | return $this->getAttribute($name); 170 | } 171 | 172 | /** 173 | * Dynamically set an attribute 174 | * 175 | * @param $name 176 | * @param $value 177 | * @return void 178 | */ 179 | public function __set($name, $value) 180 | { 181 | $this->setAttribute($name, $value); 182 | } 183 | 184 | /** 185 | * Returns a string representation of this instance 186 | * 187 | * @return false|string 188 | */ 189 | public function __toString() 190 | { 191 | return $this->toJson(); 192 | } 193 | 194 | /** 195 | * Check if the attribute is set 196 | * 197 | * @param $name 198 | * @return bool 199 | */ 200 | public function __isset($name) 201 | { 202 | return $this->offsetExists($name); 203 | } 204 | 205 | /** 206 | * Unset an attribute 207 | * 208 | * @param $name 209 | * @return void 210 | */ 211 | public function __unset($name) 212 | { 213 | $this->offsetUnset($name); 214 | } 215 | 216 | 217 | /** 218 | * Dynamically set an attribute as a fluent method 219 | * 220 | * @param $name 221 | * @param $arguments 222 | * @return $this 223 | */ 224 | public function __call($name, $arguments) 225 | { 226 | if (count($arguments) === 1) { 227 | $this->setAttribute($name, $arguments[0]); 228 | return $this; 229 | } 230 | 231 | throw new BadMethodCallException( 232 | "Method [$name] does not exist in " . get_class($this) 233 | ); 234 | } 235 | 236 | /** 237 | * Dynamically create an instance using the Instance Helper 238 | * 239 | * @param $name 240 | * @param $arguments 241 | * @return mixed 242 | */ 243 | public static function __callStatic($name, $arguments) 244 | { 245 | if (in_array($name, ['make', 'makeRaw', 'fromJson'])) { 246 | return FluidInstanceHelper::{$name}(static::class, ...$arguments); 247 | } 248 | 249 | throw new BadMethodCallException( 250 | 'Error : Call to undefined method ' . static::class . '::' . $name 251 | ); 252 | } 253 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Aaron Burden - Unsplash (UL) #Kp9z6zcUfGw](https://images.unsplash.com/photo-1471879832106-c7ab9e0cee23?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1280&h=400&q=80) 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/darkghosthunter/fluid/v/stable)](https://packagist.org/packages/darkghosthunter/fluid) [![License](https://poser.pugx.org/darkghosthunter/fluid/license)](https://packagist.org/packages/darkghosthunter/fluid) 4 | ![](https://img.shields.io/packagist/php-v/darkghosthunter/fluid.svg) [![Coverage Status](https://coveralls.io/repos/github/DarkGhostHunter/Fluid/badge.svg?branch=master)](https://coveralls.io/github/DarkGhostHunter/Fluid?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/75d03e2ee12a047b8a02/maintainability)](https://codeclimate.com/github/DarkGhostHunter/Fluid/maintainability) 5 | 6 | # Fluid 7 | 8 | A flexible class based on the famous Laravel's [Fluent](https://github.com/Illuminate/Support/blob/master/Fluent.php) and [Eloquent Model](https://github.com/laravel/framework/blob/master/src/Illuminate/Database/Eloquent/Model.php) class. 9 | 10 | Fluid will allow you to flexible manipulate a class as a bag of properties (or array keys), and allow simple serialization while hiding sensible data from your users. 11 | 12 | ## Installation 13 | 14 | Fire up composer and require it into your project. 15 | 16 | ```bash 17 | composer require darkghosthunter/fluid 18 | ``` 19 | 20 | Otherwise, you can just download this as a ZIP file and require it manually in your code: 21 | 22 | ```php 23 | 'bar']); 49 | 50 | $otherFluid = Fluid::make(['foo' => 'bar']); 51 | 52 | $otherEmptyFluid = Fluid::make(); 53 | ``` 54 | 55 | You can also use `fromJson()` if you need to make an instance from a JSON string: 56 | 57 | ```php 58 | foo; // 'bar' 65 | ``` 66 | 67 | To be totally safe to use, these static helper methods will return your class that extended the `Fluid` instead of the base class. So using `Oil::make()` will return an instance of `Oil`. 68 | 69 | ```php 70 | 'bar']); 119 | 120 | $fluid->foo = 'notBar'; 121 | $fluid['baz'] = 'qux'; 122 | 123 | echo $fluid->foo; // 'notBar'; 124 | echo $fluid['baz']; // 'qux' 125 | 126 | echo $fluid['thisAttributeDoesNotExists']; // null 127 | echo $fluid->thisAlsoDoesNotExists; // null 128 | ``` 129 | 130 | For convenience, if a property or array key doesn't exists it will return null. 131 | 132 | ### Serializing 133 | 134 | Serializing means taking the class to another representation, like an array or string. 135 | 136 | To serialize as an array, use the `toArray()` method. 137 | 138 | ```php 139 | 'bar']); 144 | 145 | $array = $fluid->toArray(); 146 | 147 | echo $fluid['foo']; // 'bar' 148 | ``` 149 | 150 | > Since there is no magic for using `(array)$fluid`, the latter will serialize every property, so to avoid that. 151 | 152 | Serializing to a string will output JSON, as with the `toJson()` method. 153 | 154 | ```php 155 | 'bar']); 160 | 161 | $json = (string)$fluid; 162 | $moreJson = $fluid->toJson(); 163 | 164 | echo $json; // "{"foo":"bar"}" 165 | echo $moreJson; // "{"foo":"bar"}" 166 | ``` 167 | 168 | ### Hiding attributes from serialization 169 | 170 | Sometimes is handy to hide attributes from serialization, like application keys, API secrets, user credentials or certificate locations. 171 | 172 | You can turn this on using `shouldHide()` method, or if you're extending `Fluid`, setting `$shouldHide` to false. 173 | 174 | ```php 175 | shouldHide(); 201 | 202 | ``` 203 | 204 | Set the attributes to hide inside the `$hidden` property. Alternatively, you can use the `setHidden()` method after is instanced. 205 | 206 | ```php 207 | 'bar', 'baz' => 'qux']); 212 | 213 | $fluid->setHidden(['baz']); 214 | 215 | $fluid->shouldHide(); 216 | 217 | echo $fluid->baz; // 'qux' 218 | echo $fluid['baz']; // 'qux' 219 | 220 | print_r($fluid->toArray()); // Array( ['foo' => 'bar'] ) 221 | echo (string)$fluid; // "{"foo":"bar"}" 222 | ``` 223 | 224 | ### Fillable 225 | 226 | Sometimes you want to ensure the user doesn't fill anything more than some predetermined attributes. You can use the `FluidFillable` class to enforce this. 227 | 228 | You can put the attributes allowed to be set in the `$fillable` array or use `setFillable()` afterwards. 229 | 230 | ```php 231 | 'bar', 'baz' => 'qux']); 236 | 237 | $fluid->setFillable(['foo', 'baz']); 238 | 239 | $fluid->alpha = 'bravo'; 240 | 241 | /* 242 | * [!] DarkGhostHunter\Fluid\Exceptions\InvalidAttributeException 243 | * 244 | * Attribute [foo] in not set as fillable in FluidFillable. 245 | */ 246 | ``` 247 | 248 | The user will get a `InvalidAttributeException` when trying to set an attribute which is not fillable in the class. 249 | 250 | You can use this to force developers to only allow certain attributes inside an instance, allowing you to displace any filtering logic once the instance is processed in your application. 251 | 252 | ## License 253 | 254 | This package is licenced by the [MIT License](LICENSE). -------------------------------------------------------------------------------- /tests/FluidTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Fluid::class, $fluid); 16 | 17 | $fluid = new Fluid(['foo' => 'bar']); 18 | $this->assertEquals('bar', $fluid->getAttribute('foo')); 19 | } 20 | 21 | public function testSetAndGetAttributes() 22 | { 23 | $fluid = new class extends Fluid { 24 | public function setFooAttribute($value) 25 | { $this->attributes['foo'] = 'notBar'; } 26 | public function getFooAttribute() 27 | { return 'alsoNotBar'; } 28 | }; 29 | 30 | $fluid->setAttributes([ 31 | 'foo' => 'bar' 32 | ]); 33 | 34 | $this->assertEquals('alsoNotBar', $fluid->getAttribute('foo')); 35 | 36 | $this->assertEquals(['foo' => 'bar'], $fluid->getAttributes()); 37 | } 38 | 39 | public function testSetAndGetAttribute() 40 | { 41 | $fluid = new class(['baz' => 'qux']) extends Fluid { 42 | public function setFooAttribute($value) 43 | { $this->attributes['foo'] = 'notBar'; } 44 | public function getFooAttribute() 45 | { return 'alsoNotBar'; } 46 | }; 47 | 48 | $this->assertEquals('qux', $fluid->getAttribute('baz')); 49 | 50 | $fluid->setAttribute('foo', 'anything'); 51 | 52 | $this->assertEquals('alsoNotBar', $fluid->getAttribute('foo')); 53 | 54 | $this->assertNull($fluid->getAttribute('undefinedKey')); 55 | $this->assertEquals('default', $fluid->getAttribute('undefinedKey', 'default')); 56 | 57 | $this->assertEquals('callable', $fluid->getAttribute('undefinedKey', function () { 58 | return 'callable'; 59 | })); 60 | 61 | $fluid->setAttribute('zero', 0); 62 | $this->assertEquals(0, $fluid->zero); 63 | $fluid->setAttribute('emptyArray', []); 64 | $this->assertEquals([], $fluid->emptyArray); 65 | $fluid->setAttribute('emptyString', ''); 66 | $this->assertEquals('', $fluid->emptyString); 67 | } 68 | 69 | public function testSetAndGetHidden() 70 | { 71 | $fluid = new Fluid(); 72 | 73 | $fluid->setHidden(['foo']); 74 | $this->assertEquals(['foo'], $fluid->getHidden()); 75 | } 76 | 77 | public function testIsHiding() 78 | { 79 | $fluid = new Fluid(); 80 | 81 | $fluid->shouldHide(true); 82 | $this->assertTrue($fluid->isHiding()); 83 | 84 | $fluid->shouldHide(false); 85 | $this->assertFalse($fluid->isHiding()); 86 | } 87 | 88 | public function testHidesAttributes() 89 | { 90 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux']); 91 | 92 | $fluid->setHidden(['foo']); 93 | 94 | $this->assertFalse($fluid->isHiding()); 95 | 96 | $this->assertArrayHasKey('foo', $fluid->toArray()); 97 | $this->assertArrayHasKey('baz', $fluid->toArray()); 98 | 99 | $fluid->shouldHide(); 100 | 101 | $this->assertArrayNotHasKey('foo', $fluid->toArray()); 102 | $this->assertArrayHasKey('baz', $fluid->toArray()); 103 | 104 | $fluid->shouldHide(false); 105 | 106 | $this->assertArrayHasKey('foo', $fluid->toArray()); 107 | $this->assertArrayHasKey('baz', $fluid->toArray()); 108 | } 109 | 110 | public function testOnly() 111 | { 112 | $fluid = new class(['foo' => 'bar', 'baz' => 'qux']) extends Fluid { 113 | public function getFooAttribute() 114 | { 115 | return 'quuz'; 116 | } 117 | }; 118 | 119 | $array = $fluid->only(['foo']); 120 | 121 | $this->assertArrayNotHasKey('baz', $array); 122 | $this->assertEquals('bar', $array['foo']); 123 | } 124 | 125 | 126 | public function testExcept() 127 | { 128 | $fluid = new class(['foo' => 'bar', 'baz' => 'qux']) extends Fluid { 129 | public function getFooAttribute() 130 | { 131 | return 'quuz'; 132 | } 133 | }; 134 | 135 | $this->assertEquals(['foo' => 'bar'], $fluid->except(['baz'])); 136 | } 137 | 138 | 139 | public function testToArray() 140 | { 141 | $fluid = new Fluid($array = ['foo' => 'bar', 'baz' => 'qux']); 142 | 143 | $this->assertEquals($array, $fluid->toArray()); 144 | 145 | $fluid = new Fluid(); 146 | 147 | $this->assertIsArray($fluid->toArray()); 148 | $this->assertEmpty($fluid->toArray()); 149 | } 150 | 151 | public function testToArrayHidesAttributes() 152 | { 153 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux']); 154 | 155 | $fluid->setHidden(['foo']); 156 | $fluid->shouldHide(); 157 | 158 | $this->assertEquals(['baz' => 'qux'], $fluid->toArray()); 159 | } 160 | 161 | public function testToDoesntHidesAttributes() 162 | { 163 | $fluid = new Fluid($array = ['foo' => 'bar', 'baz' => 'qux']); 164 | 165 | $fluid->setHidden(['foo']); 166 | $fluid->shouldNotHide(); 167 | 168 | $this->assertEquals($array, $fluid->toArray()); 169 | } 170 | 171 | public function testToJson() 172 | { 173 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux']); 174 | 175 | $fluid->setHidden(['foo']); 176 | $fluid->shouldHide(); 177 | 178 | $this->assertJson($fluid->toJson()); 179 | $this->assertStringNotContainsString('foo', $fluid->toJson()); 180 | } 181 | 182 | public function testDynamicGetAndSet() 183 | { 184 | $fluid = new Fluid(); 185 | 186 | $fluid->foo = 'bar'; 187 | 188 | $this->assertEquals('bar', $fluid->foo); 189 | } 190 | 191 | public function testToString() 192 | { 193 | $fluid = new class(['foo' => 'bar', 'baz' => 'qux']) extends Fluid { 194 | public function getFooAttribute() 195 | { 196 | return 'quuz'; 197 | } 198 | }; 199 | 200 | $fluid->setHidden(['baz']); 201 | $fluid->shouldHide(); 202 | 203 | $this->assertJson((string)$fluid); 204 | $this->assertStringNotContainsString('bar', (string)$fluid); 205 | $this->assertStringNotContainsString('baz', (string)$fluid); 206 | } 207 | 208 | public function testIsSet() 209 | { 210 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux']); 211 | 212 | $this->assertTrue(isset($fluid->foo)); 213 | $this->assertFalse(isset($fluid->undefinedKey)); 214 | } 215 | 216 | public function testUnset() 217 | { 218 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux']); 219 | 220 | unset($fluid->foo); 221 | 222 | $this->assertNull($fluid->foo); 223 | } 224 | 225 | public function testJsonSerialize() 226 | { 227 | $fluid = new class(['foo' => 'bar', 'baz' => 'qux', 'quuz' => 'quux']) extends Fluid { 228 | public function getFooAttribute() 229 | { 230 | return 'notFoo'; 231 | } 232 | }; 233 | 234 | $fluid->setHidden(['quuz']); 235 | $fluid->shouldHide(); 236 | 237 | $this->assertJson(json_encode($fluid)); 238 | $this->assertStringContainsString('notFoo', json_encode($fluid)); 239 | $this->assertStringNotContainsString('quuz', json_encode($fluid)); 240 | } 241 | 242 | public function testOffsetExists() 243 | { 244 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux']); 245 | 246 | $this->assertTrue(isset($fluid['foo'])); 247 | $this->assertFalse(isset($fluid['invalidKey'])); 248 | } 249 | 250 | public function testOffsetGetAndSet() 251 | { 252 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux']); 253 | 254 | $this->assertEquals('bar', $fluid['foo']); 255 | 256 | $fluid['baz'] = 'notQux'; 257 | 258 | $this->assertEquals('notQux', $fluid['baz']); 259 | } 260 | 261 | public function testOffsetUnset() 262 | { 263 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux']); 264 | 265 | unset($fluid['foo']); 266 | 267 | $this->assertNull($fluid['foo']); 268 | $this->assertEquals('qux', $fluid['baz']); 269 | } 270 | 271 | public function testDynamicFluentMethod() 272 | { 273 | $fluid = new Fluid(); 274 | 275 | $fluid->foo('bar')->baz('qux'); 276 | 277 | $this->assertEquals('bar', $fluid->foo); 278 | $this->assertEquals('qux', $fluid->baz); 279 | } 280 | 281 | public function testDynamicFluentMethodInvalidOnMoreParameters() 282 | { 283 | $this->expectException(BadMethodCallException::class); 284 | 285 | $fluid = new Fluid(); 286 | 287 | $fluid->foo('bar', 'shouldNotHaveSecondParameter'); 288 | } 289 | 290 | public function testMake() 291 | { 292 | $fluid = new class() extends Fluid { 293 | public function setFooAttribute() 294 | { 295 | $this->attributes['foo'] = 'notFoo'; 296 | } 297 | }; 298 | 299 | $fluid = $fluid::make(['foo' => 'bar', 'baz' => 'qux']); 300 | 301 | $this->assertInstanceOf(Fluid::class, $fluid); 302 | $this->assertEquals(['foo' => 'notFoo', 'baz' => 'qux'], $fluid->toArray()); 303 | } 304 | 305 | public function testMakeRaw() 306 | { 307 | $fluid = new class() extends Fluid { 308 | public function setFooAttribute() 309 | { $this->attributes['foo'] = 'notFoo'; } 310 | }; 311 | 312 | $fluid = $fluid::makeRaw($array = ['foo' => 'bar', 'baz' => 'qux']); 313 | 314 | $this->assertInstanceOf(Fluid::class, $fluid); 315 | $this->assertEquals($array, $fluid->toArray()); 316 | } 317 | 318 | public function testFromJson() 319 | { 320 | $fluid = Fluid::fromJson(json_encode(['foo' => 'bar', 'baz' => 'qux'])); 321 | 322 | $this->assertInstanceOf(Fluid::class, $fluid); 323 | $this->assertEquals(['foo' => 'bar', 'baz' => 'qux'], $fluid->toArray()); 324 | } 325 | 326 | public function testCount() 327 | { 328 | $fluid = new Fluid(['foo' => 'bar', 'baz' => 'qux', 'quuz' => 'quux']); 329 | 330 | $this->assertCount(3, $fluid); 331 | } 332 | 333 | } 334 | --------------------------------------------------------------------------------