定制 weapnl/laravel-junction 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

weapnl/laravel-junction

Composer 安装命令:

composer require weapnl/laravel-junction

包简介

Easily create a REST API with extended functionality, such as eager loading, searching, filtering, and more.

README 文档

README

This project allows you to easily create a REST API with Laravel. It has extended functionality, such as eager loading, searching, filtering, and more.

Installation

composer require weapnl/laravel-junction

JS/TS Support

Laravel-Junction has a companion JavaScript/TypeScript package called JS-Junction! This package extends the functionality of our Laravel package to the front end, offering a seamless integration for your web applications.

Development

In order to easily work on this package locally and use it in another local project, do the following:

  1. Add a repository in the composer.json file of the project you want to include Laravel-Junction in:
"repositories": {
    "laravel-junction": {
        "type": "path",
        "url": "./laravel-junction",
        "options": {
            "symlink": true
        }
    }
}
  1. If your other project runs in docker, add a volume referencing the folder where Laravel-Junction resides:
services:
  api:
    image: ...
    volumes:
      - ../laravel-junction:/var/www/laravel-junction
  1. Install the local package. The symlink option you set on the repository earlier makes sure that you only need to do this once as opposed to every code change.
composer require weapnl/laravel-junction dev-main

Quick Start

// app/Http/Controllers/Api/UserController.php
namespace App\Http\Controllers\Api;

use Weap\Junction\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * The class name of the model for which the controller should implement CRUD actions.
     *
     * @var string
     */
    public $model = User::class;

    /**
     * Define the relations which can be loaded in a request using "array" notation.
     *
     * @return array
     */
    public function relations(): array
    {
        return [
            'orders',
        ];
    }
// routes/api.php
Junction::apiResource('users', 'UserController');

You're all set and ready to go now. You can now perform requests to the /api/users endpoint. Try a post request to create a new user, or a get request to retrieve all users.

Usage

Setting up the Controller

To make the controller accessible through the api, you need to extend the Weap\Junction\Http\Controllers\Controller class. This class extends the default Laravel controller, and adds some extra functionality. Defining the controller is pretty straightforward, check the Quick start section for a basic example. We will now go over some of the extra functionality.

// app/Http/Controllers/Api/UserController.php
namespace App\Http\Controllers\Api;

use Weap\Junction\Http\Controllers\Controller; // Make sure to import the Controller class from the Weap/Junction package.

class UserController extends Controller
{
    /**
     * The class name of the model for which the controller should implement CRUD actions.
     *
     * @var string
     */
    public $model = User::class;
    
    /**
     * The class name of Resource to be used for the show and index methods.
     *
     * @var string $resource
     */
    public $resource = UserResource::class;

    /**
     * Define the relations which can be loaded in a request using "array" notation.
     *
     * @return array
     */
    public function relations(): array
    {
        return [
            'orders',
            // Define all your relations here with should be accessible through the API.
        ];
    }

Sample usage

/api/users?orders[0][column]=id&orders[0][direction]=asc&&search_value=john&search_columns[]=name&search_columns[]=email

Sample response

The response always contains the properties items, total and page, even if you're not using pagination.

{
  "items": [
    {
      "id": 2,
      "name": "John Doe",
      "email": "john.doe@app.com",
      "orders": [],
      "comments": [
        {
          "id": 1,
          "body": "Hello world!"
        }
      ]
    }
  ],
  "total": 1, // Total amount of items
  "page": 1 // The current page
}

Filters

Filters are applied to the query. Filters are defined using array keys. Available filters:

Key Example Description
limit limit=10 Limit the maximum amount of results.
orders orders[][column]=name,orders[][direction]=asc Columns to order on.
with with=[orders,comments] Relations to load.
scopes scopes[0][name]=hasName&scopes[0][params][0]=John Scopes to apply with the given parameters.
search_value search_value=john Search for the given value.
search_columns search_columns[]=id&search_columns[]=name The columns to search in. (optional: defaults to the searchable variable on your controller.)
wheres wheres[0][column]=name&wheres[0][operator]=%3D&wheres[0][value]=John (%3D = '=', ASCII Encoding) Apply where clauses.
where_in where_in[0][column]=id&where_in[0][values][0]=1&where_in[0][values][1]=2 Apply where in clause. (Where id is 1 or 2)
where_not_in where_not_in[0][column]=id&where_not_in[0][values][0]=1&where_not_in[0][values][1]=2 Apply where not in clause. (Where id is not 1 or 2)

Modifiers

Modifiers are applied after the query has run. Available modifiers:

Key Example Description
appends appends[]=fullname&appends[]=identity.age Add appends to each model in the result.
hidden_fields hidden_fields[]=id&hidden_fields[]=address Hide the given fields for each model in the result.
pluck pluck[]=id&pluck[]=address.house_number Only return the given fields for each model in the result. (Only available for index and show routes)

Pagination

Pagination is applied on database-level (after applying all filters). The following parameters can be used to setup pagination:

Key Example Description
paginate paginate=25 Paginate the result. This also specifies the amount of items per page.
page page=1 The page to get. Defaults to 1. Requires paginate to be set.
page_for_id page_for_id=1 This will search the correct page based on the given model id. page is used as a fallback if the given id can not be found. Requires paginate to be set.

Simple pagination

Simple pagination almost the same as the pagination above. But the simple pagination doesn't return the total amount of items or a page number. This is useful for large database tables where the normal pagination is too slow.

Key Example Description
paginate paginate=25 This specifies the amount of items per page.
simple_pagination simple_pagination=true This defines that simple pagination should be used.

Relations

To limit the relations which can be loaded using the with filter, you can override the relations method on your controller. This method should return an array containing relations (dot-notation is supported). To add filters to the relation query, you can use the key as relation name and a closure as the value.

Note: When using dot-notation, if a closure is given for one of the higher-level relations in your controller, that closure will be applied to the query. For example with relations implemented like below, loading the relation user.activities, will apply the isAdmin scope to the user query.

public function relations()
{
    return [
        'user' => fn ($query) => $query->isAdmin(),
        'user.activities',
    ];
}

Extensions

To modify the relations array before it is parsed, you can use the following in a service provider;

app(\Weap\Junction\Extensions\RelationExtension::class)->add(function (array $relations, \Illuminate\Routing\Controller $controller) {
    // Only get activities of the past week
    $relations['activities'] = fn ($query) => $query->where('created_at', '>', now()->subWeek());

    return $relations;
});

This example will add or replace the activities relation on any controller. Note that it only sets the relation on the controller; it does not actually load the relation unless it is requested.

Accessors

To append accessors to models in the response, you can use the appends modifier. This modifier allows dot-notation for relations. These relations will be eager loaded (relation closures defined in the controller are applied as well).

In some cases you may want an accessor to return a value of one of its relations, which would normally cause a lazy load when the accessor is executed. To eager load the relation, you could add it to the request using the with filter, which means the frontend is responsible for proper eager loading. To shift this responsibility to the backend, you can use the Junction::makeAttribute() function to make an accessor instead of Attribute::make():

use Weap\Junction\Junction;

/**
 * @return Attribute<string, never>
 */
protected function name(): Attribute
{
    return Junction::makeAttribute(
        get: fn (): string => $this->contact->name,
        with: ['contact'],
    );
}

In this case, the contact relation would be eager loaded before the accessor is executed, which can prevent N+1 queries. The with array also allows dot-notation and closures to customize the query:

use Weap\Junction\Junction;

/**
 * @return Attribute<string, never>
 */
protected function name(): Attribute
{
    return Junction::makeAttribute(
        get: function (): string {
            return match ($this->subjectable::class) {
                Employee::class => $this->subjectable->contact?->name,
                Freelancer::class => $this->subjectable->company?->name,
                default => null,
            } ?: '';
        },
        with: [
            'subjectable' => function ($query) {
                $query->morphWith([
                    Employee::class => ['contact'],
                    Freelancer::class => ['company'],
                ]);
            },
        ],
    );
}

Search

This package supports search functionality for given models and relations. On your controller, add a searchable property like defined below. When you want to search a model, add "search_value" to your request. Optionally you can add "search_columns" to override the columns from your controller.

public $searchable = [
    'id',
    'name',
    'orders.order_number',
];

Resources

To use resources, set the resource variable in your controller. Your resource must extend \Weap\Junction\Http\Controllers\Resources.

This allows you to specify which attributes, accessors and relations will be returned. To do this, override the corresponding method:

  • availableAttributes. Return an array of strings, specifying which attributes will be returned. The primary key is always included.
  • availableAccessors. Return an array of strings, specifying which accessors will be returned.
  • availableRelations. Return an array of key/value pairs, where the key is the name of the relation, and the value is another resource.

Return null in any of these methods to allow ALL attributes/accessors/relations to be returned.

Example:

class UserResource extends BaseResource
{
    /**
     * @return array|null
     */
    protected function availableAttributes(): ?array
    {
        return [
            'first_name'
        ];
    }

    /**
     * @return array|null
     */
    protected function availableAccessors(): ?array
    {
        return [
            'fullName'
        ];
    }

    /**
     * @return array|null
     */
    protected function availableRelations(): ?array
    {
        return [
            'orders' => OrderResource::class,
        ];
    }
}

Actions

This package also supports action routes.

Add the action method in your controller:

/**
 * @param null|Model $model
 */
protected function actionSomeName($model = null)
{
    //
}
  • If you are using policies, your policy should implement the action policy, which receives the model as parameter.
  • Now, you can call the following route as a PUT request: /api/users. In the body, add the following (the id is optional):
{
    "action": "someName",
    "id": 1
}
  • You can add as many actions as you want. Just make sure to prefix the method with action.

Index route options

Enforce order by model key

To enable the option to enforce an order by on the query, set the junction.route.index.enforce_order_by_model_key config value to true. This will add an "order by" clause to the query based on the model's key name, if no "order by" is already present for the key name. The default order direction is asc, but if you want it to use desc, update the junction.route.index.enforce_order_by_model_key_direction config value to desc.

Validation

FormRequest validation

To validate the incoming request, you can create a FormRequest and extend the Weap\Junction\Http\Controllers\Requests\DefaultFormRequest class. This class extends the default Laravel FormRequest class, and adds some extra functionality.

Standard validation

To validate the request, create a request file for your model and add this to the controller.

/**
 * The class name of FormRequest to be used for the store and update methods.
 *
 * @var string
 */
public $formRequest = ModelRequest::class;
  • Add rules to the rules() method in the ModelRequest.
/**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return [
        'first_name' => 'required',
    ];
}

/**
 * Define validation rule messages for store and update requests.
 *
 * @return array
 */
public function messages()
{
    return [
        'first_name.required' => 'The first name is required.',
    ];
}

Save fillable attributes

By default, only validated attributes are saved when calling store/update routes. To save fillable attributes instead, set the following on your controller:

/**
 * Set to true to save fillable instead of validated attributes in store/update methods.
 *
 * @var bool
 */
protected $saveFillable = true;

Using Temporary Media Files with Spatie Medialibrary

Step 1: Uploading Files via the API

To upload files through the API, use the /media/upload endpoint. Include an array of files under the files key in the request body. These files will be temporarily stored in the media library and linked to a MediaTemporaryUpload model.

Example Upload Request:

{
    "files": [<uploaded file here>, ...]
}

Step 2: Attaching Files to a Model

Example A: Updating an Existing Model

To attach files to an existing model, include the media IDs in a PUT request. For instance, if you want to attach an identity document to an employee, your PUT request to /employees/3 might look like this, assuming the identity files are stored in the IdentityFiles collection on the Employee model:

{
    "first_name": "John",
    "last_name": "Doe",
    "_media": {
        "IdentityFiles": [1]
    }
}

The API will search the request body for the _media key and associate the specified media IDs with the correct collection. In this example, media ID 1 will be attached to the IdentityFiles collection of the Employee with ID 3.

Example B: Creating a New Model

You can also attach files when creating a new model using a POST request. For example, if you are creating a new Employee and want to attach a profile picture, your POST request to /employees might look like this:

{
    "first_name": "Jane",
    "last_name": "Smith",
    "_media": {
        "ProfilePicture": [2]
    }
}

This will attach media ID 2 to the ProfilePicture collection of the newly created Employee.

Using the _media Key in Nested Structures

The _media key can also be nested within the request body, for example, inside a contact key. In this case, the API will attach the media files to the contact relationship of the Employee, whether you are creating a new model or updating an existing one.

Example Request with Nested _media Key (for creation):

{
    "first_name": "Jane",
    "last_name": "Smith",
    "contact": {
        "phone": "123-456-7890",
        "_media": {
            "ProfilePicture": [3]
        }
    }
}

In this example, when creating a new Employee, media ID 3 will be attached to the ProfilePicture collection within the contact relationship of the new Employee.

Using a Custom Temporary Upload Model

By default, temporary uploads use the Weap\Junction\Models\MediaTemporaryUpload model and the media_temporary_uploads table. If you need extra columns, relations or other behavior on the temporary upload record, you can swap in your own model.

1. Create a subclass of MediaTemporaryUpload:

// app/Models/MyMediaTemporaryUpload.php
namespace App\Models;

use Weap\Junction\Models\MediaTemporaryUpload;

class MyMediaTemporaryUpload extends MediaTemporaryUpload
{
    // Add your custom columns, relations, casts, scopes, etc.
}

2. Register it in config/junction.php:

'route' => [
    'media' => [
        // ...
        'media_temporary_upload_model' => \App\Models\MyMediaTemporaryUpload::class,
    ],
],

Your subclass inherits the default table and the Spatie HasMedia integration, so no further setup is required. Once registered, Junction uses your model everywhere temporary uploads are read or written.

weapnl/laravel-junction 适用场景与选型建议

weapnl/laravel-junction 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 14.17k 次下载、GitHub Stars 达 22, 最近一次更新时间为 2023 年 11 月 09 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 weapnl/laravel-junction 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 weapnl/laravel-junction 我们能提供哪些服务?
定制开发 / 二次开发

基于 weapnl/laravel-junction 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 14.17k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 22
  • 点击次数: 21
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 22
  • Watchers: 1
  • Forks: 8
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2023-11-09