Laravel-Backpack / CRUD

Build custom admin panels. Fast!
https://backpackforlaravel.com
MIT License
3.08k stars 886 forks source link

[Bug] Multiple values are sent incorrectly as subfield #5648

Open rahman-nero opened 1 week ago

rahman-nero commented 1 week ago

Bug report

What I did

There is a particular bug I hate the most in Backpack.

So I have 'relationship'-type field with subfields. I wrote some validation for them as well, like checking whether it is unique or not. So when I open edit-page, edit those fields and send form, I expect from select2_multiple (gkCitiesFlex) field to be an array, but Backpack sends me the field as string although it's clearly an array. My validation fails because of that.

In my controller. I have defined these fields on setupUpdateOperation:

[
                'name'      => 'prices',
                'type'      => 'relationship',
                'label'     => '...',
                'tab'       => '...',
                'subfields' => [
                    [
                        'name'       => 'gkCitiesFlex',
                        'label'      => '...',
                        'type'       => 'select2_multiple',
                        'select_all' => true,
                        'wrapper'    => [
                            'class' => 'form-group col-sm-12',
                        ],
                    ],
                    [
                        'name'              => 'contr_type_id',
                        'label'             => '...',
                        'type'              => 'select2_from_array',
                        'options'           => [...],
                        'allows_null'       => true,
                        'wrapperAttributes' => [
                            'class' => 'form-group col-md-4',
                        ],
                    ],
                    [
                        'name'              => 'business_type_id',
                        'label'             => '...',
                        'type'              => 'select2_from_array',
                        'options'           => [...],
                        'allows_null'       => true,
                        'wrapperAttributes' => [
                            'class' => 'form-group col-md-4',
                        ],
                    ],
                    [
                        'name'              => 'connection_type_id',
                        'label'             => '...',
                        'type'              => 'select2_from_array',
                        'options'           => [...],
                        'allows_null'       => true,
                        'wrapperAttributes' => [
                            'class' => 'form-group col-md-4',
                        ],
                    ],
                    [
                        'name'    => 'price',
                        'label'   => '...',
                        'wrapper' => [
                            'class' => 'form-group col-sm-4',
                        ],
                    ],
                    [
                        'name'    => 'date_start',
                        'label'   => '...',
                        'type'    => 'date',
                        'wrapper' => [
                            'class' => 'form-group col-sm-4',
                        ],
                    ],
                    [
                        'name'    => 'date_end',
                        'type'    => 'date',
                        'label'   => '...',
                        'wrapper' => [
                            'class' => 'form-group col-sm-4',
                        ],
                    ],
                ],
            ],

Here is what Backpack sends my after I submit form:

{
    "_token": "MR9NHLJu2WpPjIDGPjGsdwd3lDrpfEY9WA3x5ZyJ",
    "_method": "PUT",
    "_http_referrer": "https://flex-to-plan-test.k-telecom.org/work-type",
    "id": "1",
    "name": "...",
    "alias": "...",
    "description": "...",
    "prices": [
        {
            "id": "1",
            "gkCitiesFlex": "130",
            "contr_type_id": "5",
            "business_type_id": null,
            "connection_type_id": null,
            "price": "800",
            "date_start": "2024-08-01",
            "date_end": "2024-12-31"
        },
        {
            "id": null,
            "gkCitiesFlex": [
                "18"
            ],
            "contr_type_id": "5",
            "business_type_id": null,
            "connection_type_id": null,
            "price": "35",
            "date_start": "2024-09-01",
            "date_end": "2026-09-01"
        }
    ],
    "_current_tab": "rascenki",
    "_save_action": "save_and_edit"
}

As you can see, in the first element of prices, it contains gkCitiesFlex as string, it's clearly wrong, It must be an array (because I selected like 25 values for the field in the form). I don't know why it does it.

I saw this bug even in backpack/crud 5 version, I thought it would be fixed in 6 version

I am attaching a screenshot where I show how the form was filled and sent image

What I expected to happen

I expect gkCitiesFlex to be an array cuz I defined it as select2_multiple.

What happened

Backpack sends an array as string, taking last element from selected values and gives it as value (string).

Backpack, Laravel, PHP, DB version

When I run php artisan backpack:version the output is:

### PHP VERSION:
PHP 8.3.10 (cli) (built: Jul 31 2024 13:54:49) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.10, Copyright (c) Zend Technologies

### LARAVEL VERSION:
10.46.0.0

### BACKPACK PACKAGE VERSIONS:
backpack/basset: 1.2.4
backpack/crud: 6.6.5
backpack/devtools: 2.0.3
backpack/editable-columns: 3.0.7
backpack/filemanager: 3.0.7
backpack/generators: v4.0.4
backpack/logmanager: v5.0.1
backpack/permissionmanager: 7.1.1
backpack/pro: 2.1.8
backpack/revise-operation: 2.0.0
backpack/settings: 3.1.0
backpack/theme-coreuiv2: 1.2.3
welcome[bot] commented 1 week ago

Hello there! Thanks for opening your first issue on this repo!

Just a heads-up: Here at Backpack we use GitHub Issues only for tracking bugs. Talk about new features is also acceptable. This helps a lot in keeping our focus on improving Backpack. If you issue is not a bug/feature, please help us out by closing the issue yourself and posting in the appropriate medium (see below). If you're not sure where it fits, it's ok, a community member will probably reply to help you with that.

Backpack communication channels:

Please keep in mind Backpack offers no official / paid support. Whatever help you receive here, on Gitter, Slack or Stackoverflow is thanks to our awesome awesome community members, who give up some of their time to help their peers. If you want to join our community, just start pitching in. We take pride in being a welcoming bunch.

Thank you!

-- Justin Case The Backpack Robot

rahman-nero commented 1 week ago

I checked the theory of select2_multiple being the problem but I was wrong.

I redefined gkCitiesFlex without specifying the type:

            [
                'name'      => 'prices',
                'type'      => 'relationship',
                'label'     => '...',
                'tab'       =>  '...',
                'subfields' => [
                    [
                        'name'    => 'gkCitiesFlex',
                        'label'   => '...',
                        'wrapper' => [
                            'class' => 'form-group col-sm-12',
                        ],
                     ...
                    ],

It doesn't work as well. Got the same error

rahman-nero commented 1 week ago

I think I found what might cause this problem.

Before sending form, I deleted some existing items (prices)

If I remove prices - I get the error. However, if I just add new prices, I don't get the error. Like deleting existing ones cause the problem. I am sure 100% of it

I recorded video: https://drive.google.com/file/d/1ZXB8KPlQCZyxskH46YV76euQaNCCQt7S/view?usp=sharing

pxpm commented 1 week ago

Hello @rahman-nero thanks for the report and all the details.

Where do you get the gkCitiesFlex options for the select ? What kind of relationship is prices ? What's your validation rule for that field ?

Cheers

rahman-nero commented 1 week ago

@pxpm

Main model Order where I defined prices:

    /**
     * Prices
     * @return HasMany
     */
    public function prices(): HasMany
    {
        return $this->hasMany(WorkTypePrice::class);
    }

WorkTypePrice model:

class WorkTypePrice extends Model
{
    use CrudTrait;

    protected $fillable = [
        'order_id',
        'contr_type_id',
        'business_type_id',
        'connection_type_id',
        'price',
        'date_start',
        'date_end',
    ];

    /**
     * Relationship to Cities through pivot-table.
     * @return BelongsToMany
     */
    public function gkCitiesFlex(): BelongsToMany
    {
        $model = new WorkTypePriceGkCity();
        $table = "{$model->getConnection()->getDatabaseName()}.{$model->getTable()}";

        return $this->belongsToMany(GkCitiesFlex::class, $table);
    }
}

WorkTypePriceGkCity (just pivot-table) between cities and prices. One price can have multiple cities:

class WorkTypePriceGkCity extends Model
{
    protected $fillable = [
        'work_type_price_id',
        'gk_cities_flex_id',
    ];

    /**
     * @return BelongsTo
     */
    public function gkCitiesFlex(): BelongsTo
    {
        $model = new GkCitiesFlex();
        $table = "{$model->getConnection()->getDatabaseName()}.{$model->getTable()}";

        return $this->belongsTo($table);
    }
}

I think the validation doesn't matter but here is:

   'prices'                          => [
                'nullable',
                'array',
                $this->checkIfPricesValidate(...),
            ],
            'prices.*.gkCitiesFlex'           => [
                'required',
                'array',
            ],
            'prices.*.contr_type_id'          => [
                'nullable',
                'in:' . $this->createInRule(ToServiceTypeField::CONTR_TYPE),
            ],
            'prices.*.business_type_id'       => [
                'nullable',
                'in:' . $this->createInRule(ToServiceTypeField::BUSINESS_TYPE),
            ],
            'prices.*.connection_type_id'     => [
                'nullable',
                'in:' . $this->createInRule(ToServiceTypeField::CONNECTION_TYPE),
            ],
            'prices.*.price'                  => [
                'required',
                'numeric',
                'min:0',
                'regex:/^\d+(\.\d{1,2})?$/'
            ],
            'prices.*.date_start'             => [
                'required',
                'date_format:Y-m-d',
            ],
            'prices.*.date_end'               => [
                'required',
                'date_format:Y-m-d',
                'after_or_equal:prices.*.date_start',
            ]

I also do custom checking for prices, but it doesn't matter, even if I remove it, the validation will fail due to not being an array.

rahman-nero commented 23 hours ago

Hello! @pxpm

I would like to know if there is any updates on the issue?