orchidsoftware / platform

Orchid is a @laravel package that allows for rapid application development of back-office applications, admin/user panels, and dashboards.
https://orchid.software
MIT License
4.44k stars 652 forks source link

Laravel Octane support #2871

Closed 9inelemons closed 2 months ago

9inelemons commented 3 months ago

Describe the bug Facing some bugs while using with Laravel Octane.

To Reproduce Steps to reproduce the behavior: Describe method in Screen class

 public function save(Request $request, Model $model)
    {
        $request->validate([
            'model.some_field' => 'string',
            //
        ]);

        $model->fill($request->get('model'));

        if ($model->save()) {
            return redirect()->route('platform.model.list');
        }

        Alert::error('Error while saving');
        return back()->withInput($request->all());
    }

Describe button on screen to call this method

Button::make('Save')
                ->class('btn btn-primary')
                ->method('save'),

After pushing save button page is freezed, loader on button is scrolling. After refreshing page it gives me raw json response.

Expected behavior save request as usual returns 303 HTTP code and redirects to other route

Logs Here is response data which i am getting after pushing save button and after refreshing the page.

Request Method: GET
Status Code: 200 OK
HTTP/1.1 200 OK
Cache-Control: no-cache, private
Content-Type: application/json
Server: swoole-http-server
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 1061
//
{raw json model data}

Desktop (please complete the following information):

Server (please complete the following information):

Additional context May be this strange behaviour only if there is Upload field with attachments. Need to discover

tabuna commented 3 months ago

Hi, I haven’t been able to reproduce the issue yet, possibly because my environment is different — I’m using RR instead of Swoole. However, I plan to try again.

It’s strange that you’re receiving JSON, as your methods don’t seem to return anything like that. Could you clarify what content you’re getting? I’d appreciate your confirmation.

9inelemons commented 3 months ago

Here i will just dump my screen code that is causing this behaviour, hope it helps to reproduce it.

//routes/platform.php

Route::screen('/contacts/edit/{contact?}', About\Contact\ContactEditScreen::class)->name(About\Contact\ContactEditScreen::ROUTE_NAME);
//App\Orchid\Screens\About\Contact\ContactEditScreent.php

<?php
namespace App\Orchid\Screens\About\Contact;

use App\Models\Boutique;
use App\Models\Contact;
use App\Models\Permission;
use App\Models\Role;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Orchid\Screen\Action;
use Orchid\Screen\Actions\Button;
use Orchid\Screen\Fields\Code;
use Orchid\Screen\Fields\Group;
use Orchid\Screen\Fields\Input;
use Orchid\Screen\Fields\TextArea;
use Orchid\Screen\Fields\Upload;
use Orchid\Screen\Screen;
use Orchid\Support\Facades\Alert;
use Orchid\Support\Facades\Layout;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

class ContactEditScreen extends Screen
{
    /**
     * Query data.
     *
     * @return array
     */
    public const ROUTE_NAME = 'platform.contacts.edit';
    /** @var Contact $contact */
    public $contact;

    public function query(Contact $contact): iterable
    {
        return [
            'contact' => $contact
        ];
    }

    /**
     * Button commands.
     *
     * @return Action[]
     */
    public function commandBar(): iterable
    {
        return [
            Button::make('delete')
                ->name('Удалить')
                ->class('btn btn-danger')
                ->confirm(__('Вы уверены что хотите удалить контакт ' . $this->contact->id . '?'))
                ->hidden(!$this->contact->exists)
                ->method('delete'),
        ];
    }

    /**
     * Display header name.
     *
     * @return string|null
     */
    public function name(): ?string
    {
        return isset($this->contact->exists) ? 'Изменить контакт' : 'Добавить контакт';
    }

    /**
     * Views.
     *
     * @return Layout[]|string[]
     * @throws Throwable
     */
    public function layout(): iterable
    {
        return [
            Layout::rows([
                Input::make('contact.name')
                    ->required()
                    ->title('Название')
                    ->type('text'),

                Input::make('contact.sort')
                    ->title('Сортировка')
                    ->type('number'),

                Input::make('contact.code')
                    ->required()
                    ->title('Код (отображается в ссылке)')
                    ->type('text'),

                Input::make('contact.coords')
                    ->required()
                    ->title('Координаты в формате X,Y')
                    ->type('text'),

                Upload::make('contact.attachments')
                    ->title('Фотографии')
                    ->acceptedFiles('image/*')
                    ->form(false)
                    ->path(config('app.name') . '/contacts/'),

                Input::make('contact.orders_phone')
                    ->title('Телефон для приема заказов')
                    ->type('text'),

                Input::make('contact.cooperation_phone')
                    ->title('Телефон по вопросам сотрудничества')
                    ->type('text'),

                Input::make('contact.orders_email')
                    ->title('Электронная почта для приема заказов и предложений')
                    ->type('text'),

                Input::make('contact.pr_email')
                    ->title('Электронная почта по вопросам рекламы и PR')
                    ->type('text'),

                Code::make('contact.legal_info')
                    ->title('Юридическая информация'),

                Code::make('contact.bank_details')
                    ->title('Банковские реквизиты'),

                Input::make('contact.address')
                    ->title('Адрес')
                    ->type('text'),

                Input::make('contact.working_hours')
                    ->title('Режим работы')
                    ->type('text'),
            ])->title(__('Основная информация')),

            Layout::rows([
                TextArea::make('contact.seo_title')
                    ->title('Заголовок (Title)')
                    ->type('text'),

                TextArea::make('contact.seo_keywords')
                    ->title('Ключевые слова (keywords)')
                    ->type('text'),

                TextArea::make('contact.seo_description')
                    ->title('Описание (description)')
                    ->type('text'),
            ])->title('SEO информация'),

            Layout::rows([
                Group::make([
                    Button::make('save')
                        ->name(($this->contact->exists) ? 'Сохранить' : 'Добавить')
                        ->class('btn btn-primary')
                        ->method('save'),

                    Button::make('lalala')
                        ->name('Применить')
                        ->class('btn btn-primary')
                        ->hidden(!$this->contact->exists)
                        ->method('update')
                ])->autoWidth(),
            ])
        ];
    }

    /**
     * @throws Exception
     */
    public function update(Contact $contact, Request $request)
    {
        try {
            $validatedData = $request->validate(
                [
                    'contact.name' => 'string',
                    'contact.code' => 'string',
                    'contact.coords' => 'string',
                    'contact.orders_phone' => 'string',
                    'contact.cooperation_phone' => 'string',
                    'contact.orders_email' => 'string',
                    'contact.pr_email' => 'string',
                    'contact.legal_info' => 'string',
                    'contact.bank_details' => 'string',
                    'contact.address' => 'string',
                    'contact.working_hours' => 'string',
                    'contact.seo_keywords' => 'nullable|string',
                    'contact.seo_title' => 'nullable|string',
                    'contact.seo_description' => 'nullable|string',
                    'contact.sort' => 'nullable|integer',
                ]
            );

            $contact->name = $validatedData['contact']['name'];
            $contact->code = $validatedData['contact']['code'];
            $contact->coords = $validatedData['contact']['coords'];
            $contact->orders_phone = $validatedData['contact']['orders_phone'];
            $contact->cooperation_phone = $validatedData['contact']['cooperation_phone'];
            $contact->orders_email = $validatedData['contact']['orders_email'];
            $contact->pr_email = $validatedData['contact']['pr_email'];
            $contact->legal_info = $validatedData['contact']['legal_info'];
            $contact->bank_details = $validatedData['contact']['bank_details'];
            $contact->address = $validatedData['contact']['address'];
            $contact->working_hours = $validatedData['contact']['working_hours'];
            $contact->seo_keywords = $validatedData['contact']['seo_keywords'];
            $contact->seo_title = $validatedData['contact']['seo_title'];
            $contact->seo_description = $validatedData['contact']['seo_description'];
            $contact->sort = $validatedData['contact']['sort'];

            if ($contact->save()) {
                $contact->attachments()->syncWithoutDetaching($request->input('contact.attachments', []));
                Alert::success("Данные успешно изменены");
            }
        } catch (Exception $e) {
            Alert::error($e->getMessage());
        }
    }

    /**
     * @throws Exception
     */
    public function save(Contact $contact, Request $request)
    {
        try {
            $validatedData = $request->validate(
                [
                    'contact.name' => 'string',
                    'contact.code' => 'string',
                    'contact.coords' => 'string',
                    'contact.orders_phone' => 'string',
                    'contact.cooperation_phone' => 'string',
                    'contact.orders_email' => 'string',
                    'contact.pr_email' => 'string',
                    'contact.legal_info' => 'string',
                    'contact.bank_details' => 'string',
                    'contact.address' => 'string',
                    'contact.working_hours' => 'string',
                    'contact.seo_keywords' => 'nullable|string',
                    'contact.seo_title' => 'nullable|string',
                    'contact.seo_description' => 'nullable|string',
                    'contact.sort' => 'nullable|integer',
                ]
            );

            $contact->name = $validatedData['contact']['name'];
            $contact->code = $validatedData['contact']['code'];
            $contact->coords = $validatedData['contact']['coords'];
            $contact->orders_phone = $validatedData['contact']['orders_phone'];
            $contact->cooperation_phone = $validatedData['contact']['cooperation_phone'];
            $contact->orders_email = $validatedData['contact']['orders_email'];
            $contact->pr_email = $validatedData['contact']['pr_email'];
            $contact->legal_info = $validatedData['contact']['legal_info'];
            $contact->bank_details = $validatedData['contact']['bank_details'];
            $contact->address = $validatedData['contact']['address'];
            $contact->working_hours = $validatedData['contact']['working_hours'];
            $contact->seo_keywords = $validatedData['contact']['seo_keywords'];
            $contact->seo_title = $validatedData['contact']['seo_title'];
            $contact->seo_description = $validatedData['contact']['seo_description'];
            $contact->sort = $validatedData['contact']['sort'];

            if ($contact->save()) {
                $contact->attachments()->syncWithoutDetaching($request->input('contact.attachments', []));
                return redirect()->route(ContactListScreen::ROUTE_NAME);
            }
        } catch (Exception $e) {
            Alert::error($e->getMessage());
        }
    }

    public function delete(Contact $contact)
    {
        try {
            if ($contact->exists) {
                $contact->delete();
            }
            return redirect()->route(ContactListScreen::ROUTE_NAME);
        } catch (Exception $exception) {
            Alert::error($exception->getMessage());
        }
    }

    public function permission(): ?iterable
    {
        return [Permission::PLATFORM_ABOUT];
    }
}

Response Headers

HTTP/1.1 200 OK
Cache-Control: no-cache, private
Date: Tue, 13 Aug 2024 07:15:41 GMT
Content-Type: application/json
Server: swoole-http-server
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 1070

Response Content

{
    "contact": {
        "id": 1,
        "name": "some string",
        "code": "kazandfss",
        "coords": "some string",
        "orders_phone": "some string",
        "cooperation_phone": "some string",
        "orders_email": "test@test.ru",
        "pr_email": "test@test.ru",
        "legal_info": "some string",
        "bank_details": "some string"
        "address": "some string",
        "working_hours": "some string",
        "created_at": "2023-01-17T12:06:07.000000Z",
        "updated_at": "2024-08-13T07:13:47.000000Z",
        "seo_title": "some string",
        "seo_keywords": "12321",
        "seo_description": "some string",
        "sort": 1123333
    }
}

In browser itself it showing just raw json data that is is response.

Also, save and update methods are working fine for couple times, but after like ~5 requests it seems just to be cached or what and this behaviour appears.

Hope this helps!

9inelemons commented 2 months ago

@tabuna Any updates?

tabuna commented 2 months ago

I have tested several times with different variations, but unfortunately, I was unable to reproduce the issue. Could you please provide a minimal reproducible example, such as a repository with the issue, using Laravel Sail?

May be this strange behaviour only if there is Upload field with attachments. Need to discover

I also confirmed that the Upload field is present.

(By the way, I changed the name from attachments to attachment to make the example work correctly.)

Also, save and update methods are working fine for couple times, but after like ~5 requests it seems just to be cached or what and this behaviour appears.

I also clicked "save" and "refresh" more than 20 times and did not encounter this behavior.

Please help me reproduce the issue so that I can assist you better.

9inelemons commented 2 months ago

My apologies for long answer. But problem itself is resolved. Things that I've changed is in api/platform.php

Route::screen('edit', CouponScreens\EditScreen::class);
Route::screen('edit/{coupon?}', CouponScreens\EditScreen::class)->name(CouponScreens\EditScreen::ROUTE_NAME);

So basically we have two routes to be able to call save method on new model without ID. Maybe it fixed problem or something was wrong with my environment.