flobbos/laravel-crudable

一个用于在 CRUD 操作上节省时间的 Laravel 包


README

Laravel Crudable

如果您想在 CRUD 操作上节省时间

此 Laravel 包与存储库或服务结合使用时,可以节省 CRUD 操作的时间。特性涵盖了运行简单 CRUD 操作所需的基本功能。它还附带了一个契约,您可以通过自动上下文绑定将其绑定到您的服务。

文档

安装

安装包

通过执行以下命令将包添加到您的 composer.json 中。

composer require flobbos/laravel-crudable

接下来,如果您计划使用自动绑定契约,请将服务提供者添加到 app/config/app.php

Flobbos\Crudable\CrudableServiceProvider::class,

配置

发布配置文件

Laravel 5.*

php artisan vendor:publish

自动绑定

自动绑定用于遍历下面的实现列表。如果设置为 true,则将使用自动绑定功能。

'use_auto_binding' => false

服务命名空间

在这里,您可以设置您的服务或存储库类的默认命名空间。

'default_namespace' => 'Services'

资源控制器命名空间

如果您希望为资源控制器设置默认命名空间,请使用此选项。在资源生成器的静默模式下将使用此选项。

'default_resource' => 'Admin',

选择 CSS 框架

由于 Laravel 8 切换到 Tailwind CSS,因此可以生成两种视图选项。只需将配置设置为 bootstrap 或 tailwind,具体取决于您需要什么。

'css_framework' => 'tailwind',

实现

根据您的需求更新配置。配置文件中提供了一个示例配置。

return [
    'implementations' => [
        [
            //This is where you set the requesting class
            'when' => \App\Http\Controllers\Admin\UserController::class,
            //This is where you can define your own contracts
            'needs' => \Your\Own\Contract::class,
            //This is where you send out the implementation
            'give' => \App\Services\UserService::class
        ]
    ]
];

固定绑定

如果您正在使用自己的契约,可能希望使用固定绑定而不是上述上下文绑定。这将自动将您的实现绑定到您特别设计的契约上。

'bindings' => [
        [
            //'contract' => \App\Contracts\YourContract,
            //'target' => \App\Services\YourService
        ]
    ]

生成器

服务生成器

您可以生成自己的服务/存储库类,这些类实现了模型并已使用 Crudable 特性。非常简单。

php artisan crud:service CountryService

上面的命令将在 App\Services 中生成一个服务类(取决于您上述的配置设置),如下所示

namespace App\Services;

use App\Country;
use Flobbos\Crudable\Contracts\Crud;
use Flobbos\Crudable;

class CountryService implements Crud {

    use Crudable\Crudable;

    public function __construct(Country $country) {
        $this->model = $country;
    }

}

添加选项 --contract 将使用提供的服务类名称加载服务类。

控制器生成器

您可以生成空白控制器或完整的资源控制器。

php artisan crud:controller

这将根据 Crudable 功能生成资源控制器,其中包含所有必要的标准函数。

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Flobbos\Crudable\Contracts\Crud;
use Exception;

class CountryController extends Controller{

    protected $country;

    public function __construct(Crud $country) {
        $this->country = $country;
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(){
        return view('admin.countries.index')->with(['country'=>$this->country->get()]);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create(){
        return view('admin.countries.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request){
        $this->validate($request, []);

        try{
            $this->country->create($request->all());
            return redirect()->route('')->withMessage(trans('crud.record_created'));
        } catch (Exception $ex) {
            return redirect()->back()->withErrors($ex->getMessage())->withInput();
        }
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id){
        return view('admin.countries.show')->with(['country'=>$this->country->find($id)]);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id){
        return view('admin.countries.edit')->with(['country'=>$this->country->find($id)]);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id){
        $this->validate($request, []);

        try{
            $this->country->update($id, $request->all());
            return redirect()->route('admin.countries.index')->withMessage(trans('crud.record_updated'));
        } catch (Exception $ex) {
            return redirect()->back()->withInput()->withErrors($ex->getMessage());
        }
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id){
        try{
            $this->country->delete($id);
            return redirect()->route('admin.countries.index')->withMessage(trans('crud.record_deleted'));
        } catch (Exception $ex) {
            return redirect()->route('admin.countries.index')->withErrors($ex->getMessage());
        }
    }
}

这当然只涵盖了非常基本的函数,但可以节省您反复编写相同样板代码的时间。

如果您只需要一个包含已实现服务的空白控制器,请使用空白选项如下

php artisan crud:controller --blank

添加选项 --contract 将在控制器中放入一个以控制器类名命名的自定义契约。

视图生成器

您可以根据 Laravel 随附的 Bootstrap 版本生成基本的创建/编辑/索引视图。

php artisan crud:views YourModelName

上面的命令将生成一个基本列表模板,用于创建新资源的表单模板,当然还有编辑的表单模板。假设您的视图位于“admin”文件夹中,位于 resources/views。

资源生成器

如果你是从头开始,你可能希望生成整个资源,包括模型、服务、资源控制器和视图。

php artisan crud:resource Country

所有必要的后缀(控制器、服务)将自动添加。生成器会询问你想要创建哪些部分。如果你只想生成所有内容而不被打断,请使用静默模式。

php artisan crud:resource Country --silent

您还可以使用 --contract 选项来自定义合同版本。

合同生成器

如果您想使用自己的自定义合同与 Crudable 一起使用,可以简单地生成一个样板。

php artisan crud:contract YourContract

此命令将以下内容放入您的 App\Contracts 文件夹中

namespace App\Contracts;

use Flobbos\Crudable\Contracts\Crud;

interface CountryContract extends Crud{
    //your custom code here
}

翻译

翻译信息

处理翻译基于我的其他包 Laravel Translatable-DB,其中翻译存储在单独的表中,并由语言 ID 或语言代码标识。

翻译的基本选项

如果您计划使用这些函数处理翻译,您需要在您的服务类中设置一些基本设置

use Flobbos\Crudable\Contracts\Crud;
use Flobbos\Crudable;
use Flobbos\Crudable\Contracts\Translation;

class CategoryService implements Crud,Translation{

    use Crudable\Crudable;
    use \Flobbos\Crudable\Translations\Translatable;

    //only necessary if your translation relation is named something else
    //than 'translations' in your model
    protected $translation_name = 'my_translations';
    //optional array of fields that HAVE to be present to save a translation
    protected $required_trans;

}

当生成资源时添加 --translated 选项时,该包将自动生成模型翻译类。

如何使用翻译函数

您需要将语言选项作为 foreach 循环运行,并将翻译数据作为数组发送,如下(示例基于 Bootstrap)

<div class="form-group">
    <ul class="nav nav-tabs" role="tablist">
        @foreach($languages as $k=>$lang)
        <li role="presentation" @if($k == 0)class="active"@endif>
            <a href="#{{$lang->code}}" aria-controls="{{$lang->code}}" role="tab" data-toggle="tab">{{$lang->name}}</a>
        </li>
        @endforeach
    </ul>
    <div class="tab-content">
        @foreach($languages as $k=>$lang)
        <div role="tabpanel" id="{{$lang->code}}" @if($k==0)class="tab-pane active" @else class="tab-pane" @endif id="{{$lang->code}}">
            <input type="hidden" name="translations[{{$lang->id}}][language_id]" value="{{$lang->id}}" />
            <div class="row">
                <div class="col-md-12">
                    <label for="name{{$lang->id}}" class="control-label">Category Name ({{$lang->code}})</label>
                    <input id="name{{$lang->id}}" type="text" class="form-control" name="translations[{{$lang->id}}][name]" value="{{ old('translations.'.$lang->id.'.name') }}">
                </div>
            </div>
        </div>
        @endforeach
    </div>
</div>

隐藏输入用于设置与对应语言的正确关系。

然后,数据通过两步处理。首先,翻译数据通过此函数进行过滤和打包成数组

    public function processTranslations(
            array $translations,
            $trans_key = null,
            $language_key = 'language_id'){

        $approved = [];

        foreach($translations as $trans){
            //Check for translation key
            if(!is_null($trans_key)){
                unset($trans[$trans_key]);
            }
            //Filter out empty fields
            if(!isset($this->required_trans) && !empty($this->filterNull($trans,$language_key))){
                $approved[] = $trans;
            }
            //Check if required translations are present for saving
            elseif(isset($this->required_trans) && $this->checkRequired($trans)){
                $approved[] = $trans;
            }
        }
        return $approved;
    }

此函数返回一个数组,然后将其放入保存函数中

    public function saveTranslations(
            \Illuminate\Database\Eloquent\Model $model,
            array $translations){

        if(empty($translations))
            throw new MissingTranslationsException;

        return $model->{$this->translation_name}()->saveMany($translations);
    }

在编辑函数中,您还需要引用用于标识现有翻译的翻译 ID

<ul class="nav nav-tabs" role="tablist">
    @foreach($languages as $k=>$lang)
    <li role="presentation" @if($k == 0)class="active"@endif>
        <a href="#{{$lang->code}}" aria-controls="{{$lang->code}}" role="tab" data-toggle="tab">{{$lang->name}}</a>
    </li>
    @endforeach
</ul>
<div class="tab-content">
    @foreach($languages as $k=>$lang)
    <div role="tabpanel" id="{{$lang->code}}" @if($k==0)class="tab-pane active" @else class="tab-pane" @endif id="{{$lang->code}}">
        <input type="hidden" name="translations[{{$lang->id}}][language_id]" value="{{$lang->id}}" />
        <input type="hidden" name="translations[{{$lang->id}}][category_translation_id]" value="get the ID from the translation here" />
        <div class="row">
            <div class="col-md-12">
                <label for="name{{$lang->id}}" class="control-label">Category Name ({{$lang->code}})</label>
                <input id="name{{$lang->id}}" type="text" class="form-control" name="translations[{{$lang->id}}][name]" value="{{ old('translations.'.$lang->id.'.name',get_translation($category->translations,'name',$lang->id)) }}">
            </div>
        </div>
    </div>
    @endforeach
</div>

这样,您就可以更新现有翻译并在必要时创建新的翻译。

处理翻译的可用函数

以下函数可用

public function processTranslations(
            array $translations,
            $trans_key = null,
            $language_key = 'language_id');

此函数将输入数组中的翻译处理成可用于附加到模型的可用的数据数组。

您需要提供一个翻译数据数组($translations),翻译键名和语言键名;

public function saveTranslations(
            \Illuminate\Database\Eloquent\Model $model,
            array $translations,
            $relation_name = 'translations');

此函数将之前处理过的翻译附加到现有模型。

public function updateTranslations(
            array $translations,
            \Illuminate\Database\Eloquent\Model $model,
            $translation_key,
            $translation_class);

此函数更新翻译并在当前模型中没有现有翻译时创建新的翻译。

public function checkRequired(array $arr);

您可以通过使用 $this->required_trans 在您的存储库/服务中设置必需字段,以确保始终获得最低限度的翻译数据。

public function filterNull(array $arr, $except = null);

在这里,您可以简单地过滤掉表单中留下的所有空字段,如果您没有上述提到的必需的最小字段。

别名

轻松处理缩略名

如果您希望服务类为您自动处理缩略名,只需将以下变量设置为要转换成缩略名的字段即可

protected $slug_field = 'title';

同样,像这样实现 Sluggable 接口

use Flobbos\Crudable\Contracts\Sluggable;

class YourServiceClass implements Crud,Sluggable{}

创建缩略名

在示例场景中,字段 'title' 将被转换成 URL 缩略名并保存到数据库中的默认 'slug' 字段。

如果您希望将缩略名字段命名为不同的名称,则需要将以下变量设置为您偏好的字段名称

protected $slug_name = 'url_slug';

通过缩略名检索资源

当然,您需要能够从相应的翻译缩略名检索数据库资源。别担心,我们已经考虑到了这一点。使用接口和特质

use Flobbos\Crudable\Contracts\Sluggable;
use Flobbos\Crudable\Contracts\Slugger;

class YourServiceClass implements Crud,Sluggable,Slugger{}

use Crudable\Slugs\Slugger;

这将为您提供以下函数的访问权限

public function getResourceIdFromTranslatedSlug(string $slug): int;

使用此函数,您可以从给定的翻译 URL 缩略名中检索请求的资源 ID。

public function getTranslatedSlugFromResourceId(int $id, int $language_id): string;

如果您需要获取特定资源在特定语言的 URL 缩略名,只需使用此函数即可。

同样,也存在用于非翻译缩略名的相同函数。以防万一。

使用方法

存储库/服务实现

将包添加到您希望使用特质的存储库或服务中。

use App\Country;

class CountryService {

    use \Flobbos\Crudable\Crudable;

    //protected $model; in version 2.0 and higher this no longer needs to be set

    public function __construct(Country $country){
        $this->model = $country;
    }

}

通过将模型注入到服务或存储库并将它分配给受保护的 $this->model,特质现在可以访问您的模型并发挥其神奇作用。

自动绑定说明

您可以选择在配置中使用自动绑定,它将自动将 Crud 协议绑定到您的实现。

namespace App\Http\Controllers;

use Flobbos\Crudable\Contracts\Crud;

class SomeController extends Controller {
    protected $crud;

    public function __construct(Crud $crud){
        $this->crud = $crud;
    }
}

服务提供者会自动将 Crud 接口绑定到配置中指定的实现,具体说明如下。

使用您自己的协议

namespace App\Contracts;

use Flobbos\Crudable\Contracts\Crud;

interface MyOwnContract extends Crud{
    //place your custom code here
}

通过简单地扩展 Crud 协议,您可以在不需要重新声明 Crudable 已经提供的一切的情况下定义自己的逻辑。

函数

get

这将执行对模型资源的简单获取调用。您可以可选地传递一个 ID 给该函数,它将执行与 find 相同的操作。

    return $yourService->get();

find

这将获取给定 ID 的资源。

    return $yourService->find($id);

first

first 将从您的资源中获取第一行,它几乎与原始 Laravel 版本相同,并增加了 Crudable 的好处。

    return $yourService->first();

Where

where 函数接受参数并将它们传递给 Eloquent 的 where 方法。它返回 $this,以便您可以将其链接到其他方法。

    return $yourService->where('some_field','some_value');

setRelation

setRelation 方法类似于您从 Eloquent 习惯的 with() 语句。只需传入一个包含回调等完整懒加载语句的数组。也可以进行链式操作。

    return $yourService->setRelation(['some_stuff'])->get();

with

这与 setRelation 类似,但您可以选择传递一个字符串或一个数组。

    return $yourService->with('some_stuff')->get();

orderBy

此方法只是将您的 orderBy 语句传递给 Eloquent。

withHasMany

此方法将 hasMany 数据添加到您的 create 语句中。

    return $yourService->withHasMany($data,'App\YourRelatedModel','related_model')->create($model);

withBelongsToMany

使用此方法,您可以自动保存多对多关系中的相关数据。只需传递要同步的数据的数组以及您的模型中的关系名称作为第二个参数。

    return $yourService->withBelongsToMany($data,'some_relation')->create($model);

delete

delete 将从数据库中删除一个模型,除非使用软删除。如果您想永久删除一个模型,您需要将 true 作为第二个参数传递。

    return $yourService->delete($id); //Normal delete
    return $yourService->delete($id,true); //if you want to force delete

restore

restore 函数仅恢复之前软删除的模型。

    return $yourService->restore($id);

handleUpload

处理上传相对繁琐,因此我想包括一个易于使用的函数来处理单个文件图像上传。您只需传递请求对象、照片上传的字段名、照片应放置的文件夹、存储磁盘,并且您可以可选地让该函数随机化照片名称,以防止文件被覆盖。默认值如下。

    $yourService->handleUpload($request, $fieldname = 'photo', $folder = 'images', $storage_disk = 'public', $randomize = true);

异常

MissingRelationData

如果尝试保存带有空数据的 hasMany 或 belongsToMany 关系,将抛出此异常。

MissingRequiredFields

当尝试调用 required_trans 而未设置其必要数据时,发生此异常。

MissingTranslations

如果所有字段都为空或未填写所需字段,则抛出此异常。

MissingSlugField

当在服务类中忘记定义您的 slug 字段时,将抛出此异常。

SlugNotFound

当尝试从一个正常或翻译的 slug 获取资源 ID 时,抛出 SlugNotFoundException。

Laravel 兼容性

注意:如果您计划在 Laravel <5.3 中使用自动绑定,您需要更新配置文件以反映正确的用法。请参阅 Laravel 文档

享受 CRUD 的乐趣!:-)