laravel 批量设置 fiilable的办法

家人们,之前用hyperf,在用命令创建model的时候,会自动填充文件的fillable,
但是laravel要自己写,字段很多,模型文件也很多,欲哭无泪啊

咋就是说我能不能通过动态方式去设置呢?

fillable 对model 内部是可见的,那我写个基类修改fillable不就好了

<?php

namespace App\Models;
use Illuminate\Support\Facades\Schema;

class Model extends \Illuminate\Database\Eloquent\Model
{
    public function __construct()
    {
        parent::__construct();
        // 设置fillable
        $this->fillable = $this->getTableColumns($this->getTable());
    }


    protected function getTableColumns($tableName)
    {
        Schema::connection()->getColumnListing($tableName);
    }

}

咋一看好像没问题,但是因为是基类,一步小心sql就执行的巨多

laravel 批量设置 fiilable的办法

啊啊啊,全是查表结构对的

于是进行优化,我们可以把表名和字段映射成一个map,做成静态的,直接读取就好了

<?php

namespace App\Models;
use Illuminate\Support\Facades\Schema;

class Model extends \Illuminate\Database\Eloquent\Model
{
    protected static $tableColumnsMap;

    public function __construct()
    {
        parent::__construct();
        // 设置fillable
        $this->fillable = $this->getTableColumns($this->getTable());
    }


    protected function getTableColumns($tableName)
    {
        if (isset(self::$tableColumnsMap[$this->getTable()])) return self::$tableColumnsMap[$this->getTable()];
        self::$tableColumnsMap[$this->getTable()] =  Schema::connection()->getColumnListing($tableName);

        return self::$tableColumnsMap[$this->getTable()];
    }

}

啊啊啊,这下舒服了,舒服了!

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 39

不应该用guarded空数组吗

1年前 评论
Image 晏南风 (楼主) 1年前
Image 欲速不达 1年前

直接guarded = ['id']不就行了~

1年前 评论
Image 不负岁月 1年前
    /**
     * Fill the model with an array of attributes.
     *
     * @param  array  $attributes
     * @return $this
     *
     * @throws \Illuminate\Database\Eloquent\MassAssignmentException
     */
    public function fill(array $attributes)
    {
        $totallyGuarded = $this->totallyGuarded();

        foreach ($this->fillableFromArray($attributes) as $key => $value) {
            $key = $this->removeTableFromKey($key);

            // The developers may choose to place some attributes in the "fillable" array
            // which means only those attributes may be set through mass assignment to
            // the model, and all others will just get ignored for security reasons.
            if ($this->isFillable($key)) {
                $this->setAttribute($key, $value);
            } elseif ($totallyGuarded) {
                throw new MassAssignmentException(sprintf(
                    'Add [%s] to fillable property to allow mass assignment on [%s].',
                    $key, get_class($this)
                ));
            }
        }

        return $this;
    }


    /**
     * Get the fillable attributes of a given array. * * @param array $attributes
      * @return array
     */
    protected function fillableFromArray(array $attributes)
    {
        if (count($this->getFillable()) > 0 && ! static::$unguarded) {
            return array_intersect_key($attributes, array_flip($this->getFillable()));
        }

        return $attributes;
    }


    /**
 * Create or update a record matching the attributes, and fill it with values. * * @param array $attributes
  * @param array $values
  * @return \Illuminate\Database\Eloquent\Model|static
 */
public function updateOrCreate(array $attributes, array $values = [])
{
    return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
        $instance->fill($values)->save();
    });
}
1年前 评论
Image 晏南风 (作者) (楼主) 1年前
Image 打酱油的和尚 1年前

protected static $unguarded = true;

禁用这个功能呢?虽然不太建议

Illuminate\Database\Eloquent\Concerns\GuardsAttributes

在这里设置的。

1年前 评论
Image 晏南风 (楼主) 1年前

$guarded=[] 不就完事了嘛

1年前 评论
李铭昕

FPM 下这么玩,会多查一次库啊。

为啥不起一个 Hyperf 项目,生成模型直接复制过来呢?

1年前 评论
Image 寞小陌 1年前
Image 李铭昕 (作者) 1年前

有点舍近求远了, $guarded 了解一下

1年前 评论

boot下全局设置guarded 就完事了, :kissing_heart:

1年前 评论

用Laravel Idea 插件,完美 :see_no_evil:

1年前 评论
sanders

楼上都讲了 getTableColumns() 会导致每次请求都多一次类似 select fields from ... 的查询,非常得不偿失。况且一些场景下 information_schema 库的权限你也不一定有。

较好的办法是用 $guarded 黑名单属性设置 ['id']

1年前 评论
Image 芝麻开门 1年前

// 在 app 目录下创建一个基类 BaseModel.php namespace App\Models;

use Illuminate\Database\Eloquent\Model;

这样子的方法可以么 class BaseModel extends Model { protected $guarded = ['id']; }

// 然后在其他 Model 中继承这个基类 namespace App\Models;

use App\Models\BaseModel;

class User extends BaseModel { // User Model 的其他定义 }

1年前 评论

我的guarded不生效,必须设置 $unguarded 才管用

1年前 评论
nff93

使用 Laravel Idea 插件,焦点放到 $fillable 里,然后 Control + EnterAdd all fields 就自动生成了,不用这么麻烦

1年前 评论
Image 晏南风 (楼主) 1年前

我觉得这个做法有点离谱,你都这么做了,为什么不能一次查出来结果写进 model 呢,类似 @李铭昕 说的在别处生成 Model 复制过来的操作。

1年前 评论
Image 晏南风 (楼主) 1年前
Image 陈先生 (作者) 1年前

直接 $guarded = ['id']; 就可以了 只有在创建或者更新的时候才会查一次表的字段 如果你当前请求里面有多次 也只会在第一次的时候查一次表结构 因为是静态属性 查一次这个不影响什么性能 而且只是查表结构(一个项目中最多也就几百个表吧 也就几百条数据) 不是几百万的表数据 这个比更新或者创建操作快多了 这个是读一个是写 如果在laravel框架中还在乎这一次查询的话 建议使用octane 这样只会在最初的一次请求中就加载好了 只是会每次加字段的时候 需要reload一下octane 都加字段了 业务肯定也有调整 那肯定是避免不了这次重载的

1年前 评论
leirhy

我都是用AI工具帮我自动补全

1年前 评论

1.先建立这个Trait

<?php declare(strict_types=1);

namespace App\Extensions\Database\Schema;

use Illuminate\Support\Facades\Cache;

trait BuilderTrait
{
    public function getColumnListing($table)
    {
        $cacheKey = $this->connection->getName() . ':' . $this->connection->getDatabaseName() . ':' . $table;
        if (Cache::tags('schema')->has($cacheKey)) {
            $columnListing = Cache::tags('schema')->get($cacheKey);
        } else {
            $columnListing = parent::getColumnListing($table);
            Cache::tags('schema')->forever($cacheKey, $columnListing);
        }
        return $columnListing;
    }
}

2.每个驱动都引用一下

file

3.增加以下Providers

<?php declare(strict_types=1);

namespace App\Providers;

use App\Extensions\Database\Schema\MySqlBuilder;
use App\Extensions\Database\Schema\PostgresBuilder;
use App\Extensions\Database\Schema\SQLiteBuilder;
use App\Extensions\Database\Schema\SqlServerBuilder;
use Carbon\Carbon;
use Closure;
use DateTime;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Env;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;

/**
 * @method skip(float|int $param)
 */
class DatabaseProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        if (Env::get('APP_DEBUG', false) === false) {
            $this->app->bind('db.schema', function ($app) {
                if (is_null($app['db']->connection()->getSchemaGrammar())) {
                    $app['db']->connection()->useDefaultSchemaGrammar();
                }
                return match ($app['db']->connection()->getDriverName()) {
                    'mysql' => new MySqlBuilder($app['db']->connection()),
                    'pgsql' => new PostgresBuilder($app['db']->connection()),
                    'sqlite' => new SQLiteBuilder($app['db']->connection()),
                    'sqlsrv' => new SqlServerBuilder($app['db']->connection()),
                    default => throw new InvalidArgumentException("Unsupported driver [{$app['db']->connection()->getDriverName()}]."),
                };
            });
        }
    }

这样生产环境就一直从缓存redis读取字段就可以了,避免一直读数据库,我不会排版不知道咋搞的,你凑合看吧

1年前 评论
Image 晏南风 (楼主) 1年前

忘记在哪个帖子看到的,生成fillable然后复制到模型里

<?php

namespace App\Console\Commands\Util;

use DB;
use Illuminate\Console\Command;

class GetTableColumns extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'util:get-table-columns
                            {table : The table name}
                            {con? : The table connection}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '获取数据库字段';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $table = $this->argument('table');

        $connection = $this->argument('con');

        $con = $connection ?: 'mysql';

        $db = config('database.connections.' . $con . '.database');

        $columns = DB::select('select column_name from information_schema.columns where table_name= ? and TABLE_SCHEMA = ? order by `ordinal_position`',
            [$table, $db]);

        $columns = array_column($columns, 'COLUMN_NAME');

        $columns = array_diff($columns, ['id', 'created_at', 'updated_at', 'deleted_at']);

        $this->newLine();

        $this->info('protected $fillable = [');

        foreach ($columns as $column) {
            $this->info("    '" . $column . "',");
        }

        $this->info('];');

        return 0;
    }
}
1年前 评论

楼上方法好像都不太科学,我觉得可以重写 make:model 命令,直接在生成model文件的时候,加到代码里面。还是这样来的舒服。

1年前 评论
Image nff93 1年前
Image dryang (作者) 1年前
Image nff93 1年前
Image dryang (作者) 1年前

yii 有个gii工具直接生成字段及字段注释 可以参考一下它的源码来弄

1年前 评论

file

直接临时禁用掉黑名单不行吗?

1年前 评论

编写 Command 来自动生成,对代码无侵入性

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!