IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    使用 Laravel 的服务容器来优化读写数据库中的 options

    printempw发表于 2016-09-16 12:54:04
    love 0

    原文地址:https://prinzeugen.net/optimi...

    这篇文章应该算是心得体会一类的吧,因为并没有什么卵用(笑

    就像前两篇文章说的一样,在我把项目框架改为 Laravel 后,自然要最大限度地利用 Laravel 的特性来提升我们应用的性能(虽然使用 Laravel 本身就是在降低性能了),并且让我们的代码看起来更优雅 装逼。

    其中我们可以最方便地利用的一个特性就是 Laravel 的服务容器了。在这里我不多赘述 Service Container 是个啥,想了解的可以自行搜索。不想了解的就只要大致知道它是个可以 绑定/取出 实例的东西就好了(当然服务容器可不止这么点功能)。

    相信很多 Web 应用都会在数据库建立一个 options 表来储存一些用户的配置信息,而为了方便,我们通常会封装一个 Option 类来方便地进行数据的 get、set 操作。而通常的做法是把这些操作做成类内静态方法来调用。

    但是这样的弊端就是每一次的操作都要去查询数据库,这对于性能还是有挺大影响的,尤其是在一次响应中要使用很多 options 的情况下。

    那么在 Laravel 下我们可以怎么优化呢?

    蛤?你说 Eloquent?你 TM 长点脑子啊 用了 ORM 那 tm 还叫优化?你们呀,不要听的风是得雨,看到 Laravel 就想到 Eloquent!

    好吧好吧,再强行 +1s 是要出事的,我们回到正题。没错,我们正是要把 options 放到 Laravel 的服务容器里去。

    这样的话我们只需要在应用启动的时候实例化一个 OptionRepository 类,在构造函数里读入所有的 options 并放到类内属性上,get 方法直接从该属性里取值,而调用 set 操作的时候则对该属性进行修改,同时 push 修改过的 key 到一个 $items_modified 数组里去,在对象析构的时候再真正执行数据库操作,写入所有修改过的 options。

    下面我们来定义 OptionRepository 类:

    <?php
    
    namespace App\Services;
    
    use DB;
    use ArrayAccess;
    use Illuminate\Support\Arr;
    use Illuminate\Contracts\Config\Repository as ConfigContract;
    
    class OptionRepository implements ArrayAccess, ConfigContract
    {
        /**
         * All of the option items.
         *
         * @var array
         */
        protected $items = [];
    
        protected $items_modified = [];
    
        /**
         * Create a new option repository.
         *
         * @return void
         */
        public function __construct()
        {
            $options = DB::table('options')->get();
    
            foreach ($options as $option) {
                $this->items[$option->option_name] = $option->option_value;
            }
    
        }
    
        /**
         * Determine if the given option value exists.
         *
         * @param  string  $key
         * @return bool
         */
        public function has($key)
        {
            return Arr::has($this->items, $key);
        }
    
        /**
         * Get the specified option value.
         *
         * @param  string  $key
         * @param  mixed   $default
         * @return mixed
         */
        public function get($key, $default = null)
        {
            return Arr::get($this->items, $key, $default);
        }
    
        /**
         * Set a given option value.
         *
         * @param  array|string  $key
         * @param  mixed   $value
         * @return void
         */
        public function set($key, $value = null)
        {
            if (is_array($key)) {
                foreach ($key as $innerKey => $innerValue) {
                    Arr::set($this->items, $innerKey, $innerValue);
                    $this->items_modified[] = $innerKey;
                }
            } else {
                Arr::set($this->items, $key, $value);
                $this->items_modified[] = $key;
            }
        }
    
        protected function save()
        {
            $this->items_modified = array_unique($this->items_modified);
    
            foreach ($this->items_modified as $key) {
                if (!DB::table('options')->where('option_name', $key)->first()) {
                    DB::table('options')
                        ->insert(['option_name' => $key, 'option_value' => $this[$key]]);
                } else {
                    DB::table('options')
                            ->where('option_name', $key)
                            ->update(['option_value' => $this[$key]]);
                }
            }
        }
    
        /**
         * Prepend a value onto an array option value.
         *
         * @param  string  $key
         * @param  mixed  $value
         * @return void
         */
        public function prepend($key, $value)
        {
            $array = $this->get($key);
    
            array_unshift($array, $value);
    
            $this->set($key, $array);
        }
    
        /**
         * Push a value onto an array option value.
         *
         * @param  string  $key
         * @param  mixed  $value
         * @return void
         */
        public function push($key, $value)
        {
            $array = $this->get($key);
    
            $array[] = $value;
    
            $this->set($key, $array);
        }
    
        /**
         * Get all of the option items for the application.
         *
         * @return array
         */
        public function all()
        {
            return $this->items;
        }
    
        /**
         * Determine if the given option option exists.
         *
         * @param  string  $key
         * @return bool
         */
        public function offsetExists($key)
        {
            return $this->has($key);
        }
    
        /**
         * Get a option option.
         *
         * @param  string  $key
         * @return mixed
         */
        public function offsetGet($key)
        {
            return $this->get($key);
        }
    
        /**
         * Set a option option.
         *
         * @param  string  $key
         * @param  mixed  $value
         * @return void
         */
        public function offsetSet($key, $value)
        {
            $this->set($key, $value);
        }
    
        /**
         * Unset a option option.
         *
         * @param  string  $key
         * @return void
         */
        public function offsetUnset($key)
        {
            $this->set($key, null);
        }
    
        /**
         * Save all modified options into database
         */
        public function __destruct()
        {
            $this->save();
        }
    
    }
    

    可以看到我们顺便实现了 ArrayAccess 接口,所以我们在拿到 OptionRepository 的实例后就可以使用类似于 $option['fuck'] 的形式来获取数据了(其实我后来一次也没用到)。

    不过光是实现了一个 Repository 还是不够的,我们还需要把它绑定到服务容器里,同时注册个 Facade 给它,让我们能够更优雅地调用仓库类的相关方法:

    class AppServiceProvider extends ServiceProvider
    {
        /**
         * Register any application services.
         *
         * @return void
         */
        public function register()
        {
            // 绑定单例到服务容器上
            $this->app->singleton('option', \App\Services\OptionRepository::class);
        }
    }

    新建一个 Option 类并集成 Laravel 的 Facade 基类:

    <?php
    
    namespace App\Services\Facades;
    
    use \Illuminate\Support\Facades\Facade;
    
    class Option extends Facade
    {
        /**
         * Get the registered name of the component.
         *
         * @return string
         */
        protected static function getFacadeAccessor()
        {
            return 'option';
        }
    }

    然后我们在 config/app.php 中加入我们 Facade 的别名:

    <?php
    
    return [
        'aliases' => [
            'Option'    => App\Services\Facades\Option::class
        ],
    
    ];

    大功告成!我们现在又可以像以前那样方便地使用类似于 Option::get('shit') 的方法获取数据,而且又大幅减少了数据库操作,是不是很棒呢~ (=゚ω゚)=

    好吧其实本文也没讲啥有用的东西,不过希望对从其他框架迁移到 Laravel 的开发者们有所启示~



沪ICP备19023445号-2号
友情链接