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 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 36

不应该用guarded空数组吗

4周前 评论
晏南风 (楼主) 4周前
欲速不达 4周前

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

4周前 评论
不负岁月 4周前
    /**
     * 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();
    });
}
4周前 评论
晏南风 (作者) (楼主) 4周前
打酱油的和尚 4周前

protected static $unguarded = true;

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

Illuminate\Database\Eloquent\Concerns\GuardsAttributes

在这里设置的。

4周前 评论
晏南风 (楼主) 4周前

$guarded=[] 不就完事了嘛

4周前 评论
李铭昕

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

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

4周前 评论
寞小陌 3周前
李铭昕 (作者) 3周前

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

4周前 评论

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

4周前 评论

用Laravel Idea 插件,完美 :see_no_evil:

4周前 评论
sanders

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

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

4周前 评论
芝麻开门 4周前

// 在 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 的其他定义 }

4周前 评论

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

4周前 评论
nff93

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

4周前 评论
晏南风 (楼主) 3周前
陈先生

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

3周前 评论
晏南风 (楼主) 3周前
陈先生 (作者) 3周前

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

3周前 评论
leirhy

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

2周前 评论

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周前 评论
晏南风 (楼主) 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文件的时候,加到代码里面。还是这样来的舒服。

6天前 评论
nff93 6天前
dryang (作者) 5天前
nff93 5天前
dryang (作者) 4天前

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