├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── src └── Traits │ └── AmountTrait.php └── tests └── AmountTraitTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .idea 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 hao-li 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-amount 2 | Total Downloads 3 | Latest Stable Version 4 | License 5 | 6 | ## 背景 7 | 系统中涉及到金额的字段,View 层表现的时候一般都是以**元**为单位使用小数形式展示,不过 Domain 层存储时从空间、性能、容错角度出发,经常以**分**为单位,用整型来存储。 8 | 9 | 在 Lavarel 中,可以在 Model 中添加属性方法进行转换 10 | 11 | ```php 12 | public function getAmountAttribute($value) 13 | { 14 | return $value / 100; 15 | } 16 | 17 | public function setAmountAttribute($value) 18 | { 19 | $this->attributes['amount'] = (int)($value * 100); 20 | } 21 | ``` 22 | 23 | 不过涉及金额的字段比较多时就需要定义很多相同逻辑的函数,本项目即将该逻辑抽出为 Trait,简化金额字段相关的处理。 24 | > 除了金额外,小数位数固定的**面积**、**长度**等场景使用起来也很方便。 25 | 26 | ## 原理 27 | 28 | 将转换逻辑封装在 AmountTrait 中,覆写 Model 类的 getMutatedAttributes, mutateAttributeForArray, getAttributeValue 及 setAttribute 方法,当访问相关字段时自动进行转换处理。 29 | 30 | ```php 31 | public function getMutatedAttributes() 32 | { 33 | $attributes = parent::getMutatedAttributes(); 34 | 35 | return array_merge($attributes, $this->getAmountFields()); 36 | } 37 | 38 | protected function mutateAttributeForArray($key, $value) 39 | { 40 | return (in_array($key, $this->getAmountFields())) 41 | ? $value / $this->getAmountTimes($key) 42 | : parent::mutateAttributeForArray($key, $value); 43 | } 44 | 45 | public function getAttributeValue($key) 46 | { 47 | $value = parent::getAttributeValue($key); 48 | if (in_array($key, $this->getAmountFields())) { 49 | $value = $value / $this->getAmountTimes($key); 50 | } 51 | 52 | return $value; 53 | } 54 | 55 | public function setAttribute($key, $value) 56 | { 57 | if (in_array($key, $this->getAmountFields())) { 58 | $value = (int) round($value * $this->getAmountTimes($key)); 59 | } 60 | parent::setAttribute($key, $value); 61 | } 62 | 63 | public function getAmountFields() 64 | { 65 | return (property_exists($this, 'amountFields')) ? $this->amountFields : []; 66 | } 67 | 68 | public function getAmountTimes($key) 69 | { 70 | $ret = 100; 71 | 72 | if (property_exists($this, 'amountTimes')) { 73 | if (is_array($this->amountTimes) && array_key_exists($key, $this->amountTimes)) { 74 | $ret = $this->amountTimes[$key]; 75 | } elseif (is_numeric($this->amountTimes)) { 76 | $ret = $this->amountTimes; 77 | } 78 | } 79 | 80 | return $ret; 81 | } 82 | ``` 83 | 84 | ## 依赖 85 | Laravel >= 5.2 86 | 87 | ## 安装 88 | ``` 89 | composer require "hao-li/laravel-amount:dev-master" 90 | ``` 91 | 92 | ## 使用 93 | 94 | 1. 在 Model 中引用 AmountTrait 95 | 96 | ```php 97 | use HaoLi\LaravelAmount\Traits\AmountTrait; 98 | ``` 99 | 100 | 2. 使用 AmountTrait 101 | 102 | ```php 103 | use AmountTrait; 104 | ``` 105 | 106 | 3. 定义金额字段(本例中为 amount) 107 | 108 | ```php 109 | protected $amountFields = ['amount']; 110 | ``` 111 | 4. 通过 `$amountTimes` 指定金额字段的倍数(可选,默认 100) 112 | * 各金额字段使用相同的倍数 113 | ```php 114 | protected $amountTimes = 100; 115 | ``` 116 | * 不同金额字段设置不同倍数 117 | ```php 118 | protected $amountTimes = [ 119 | 'amount' => 100, 120 | ] 121 | ``` 122 | 5. 完成 123 | 124 | 之后读取 amount 字段时,该字段的内容会自动从数据库的**分**转换为**元**,向其赋值时反之从**元**转换为**分**。 125 | 126 | ## FAQ 127 | 128 | ### 和别的 trait 中方法冲突 129 | 130 | 以 setRawAttributes 为例(此为之前方案,目前并未覆写此方法,仅为举例,其他方法原理相同) 131 | 132 | 1. 将冲突的方法分别重命名 133 | ```php 134 | use AmountTrait, BTrait { 135 | AmountTrait::setRawAttributes as amountTraitSetRawAttributes; 136 | BTrait::setRawAttributes as BTraitSetRawAttributes; 137 | } 138 | ``` 139 | 140 | 2. 在 Model 中定义该冲突的方法,根据情况分别调用别名方法 141 | ```php 142 | public function setRawAttributes(array $attributes, $sync = false) 143 | { 144 | $this->BTraitSetRawAttributes($attributes, $sync); 145 | $attributes = $this->getAttributes(); 146 | $this->amountTraitSetRawAttributes($attributes, $sync); 147 | } 148 | ``` 149 | > 注意这里 $attributes 可能已被改变,所以再次使用时要重新取得最新值 150 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hao-li/laravel-amount", 3 | "keywords": ["laravel"], 4 | "license": "MIT", 5 | "require": { 6 | }, 7 | "autoload": { 8 | "psr-4": { 9 | "HaoLi\\LaravelAmount\\": "src/" 10 | } 11 | }, 12 | "authors": [ 13 | { 14 | "name": "hao li", 15 | "email": "haoli.sand@gmail.com" 16 | } 17 | ], 18 | "require-dev": { 19 | "phpunit/phpunit": "^7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Traits/AmountTrait.php: -------------------------------------------------------------------------------- 1 | getAmountFields()); 12 | } 13 | 14 | protected function mutateAttributeForArray($key, $value) 15 | { 16 | return (in_array($key, $this->getAmountFields())) 17 | ? $value / $this->getAmountTimes($key) 18 | : parent::mutateAttributeForArray($key, $value); 19 | } 20 | 21 | public function getAttributeValue($key) 22 | { 23 | $value = parent::getAttributeValue($key); 24 | if (is_numeric($value) && in_array($key, $this->getAmountFields())) { 25 | $value = $value / $this->getAmountTimes($key); 26 | } 27 | 28 | return $value; 29 | } 30 | 31 | public function setAttribute($key, $value) 32 | { 33 | if (is_numeric($value) && in_array($key, $this->getAmountFields())) { 34 | $value = (int) round($value * $this->getAmountTimes($key)); 35 | } 36 | parent::setAttribute($key, $value); 37 | } 38 | 39 | public function getAmountFields() 40 | { 41 | return (property_exists($this, 'amountFields')) ? $this->amountFields : []; 42 | } 43 | 44 | public function getAmountTimes($key) 45 | { 46 | $ret = 100; 47 | 48 | if (property_exists($this, 'amountTimes')) { 49 | if (is_array($this->amountTimes) && array_key_exists($key, $this->amountTimes)) { 50 | $ret = $this->amountTimes[$key]; 51 | } elseif (is_numeric($this->amountTimes)) { 52 | $ret = $this->amountTimes; 53 | } 54 | } 55 | 56 | return $ret; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/AmountTraitTest.php: -------------------------------------------------------------------------------- 1 | amountTimes = [ 18 | 'test1' => 1000, 19 | 'test2' => 10000, 20 | ]; 21 | $this->assertEquals(1000, $this->getAmountTimes('test1')); 22 | $this->assertEquals(10000, $this->getAmountTimes('test2')); 23 | $this->assertEquals(100, $this->getAmountTimes('test3')); 24 | } 25 | 26 | public function testNumericGetAmountTimes() 27 | { 28 | $this->amountTimes = 1000; 29 | $this->assertEquals(1000, $this->getAmountTimes('test1')); 30 | $this->assertEquals(1000, $this->getAmountTimes('test2')); 31 | $this->assertEquals(1000, $this->getAmountTimes('test3')); 32 | 33 | $this->amountTimes = '1000'; 34 | $this->assertEquals(1000, $this->getAmountTimes('test1')); 35 | $this->assertEquals(1000, $this->getAmountTimes('test2')); 36 | $this->assertEquals(1000, $this->getAmountTimes('test3')); 37 | } 38 | 39 | public function testOtherGetAmountTimes() 40 | { 41 | $this->amountTimes = 'a'; 42 | $this->assertEquals(100, $this->getAmountTimes('test1')); 43 | $this->assertEquals(100, $this->getAmountTimes('test2')); 44 | $this->assertEquals(100, $this->getAmountTimes('test3')); 45 | 46 | $this->amountTimes = null; 47 | $this->assertEquals(100, $this->getAmountTimes('test1')); 48 | $this->assertEquals(100, $this->getAmountTimes('test2')); 49 | $this->assertEquals(100, $this->getAmountTimes('test3')); 50 | } 51 | } 52 | --------------------------------------------------------------------------------