whitecube / nova-flexible-content

Flexible Content & Repeater Fields for Laravel Nova
MIT License
780 stars 229 forks source link

Cannot save resource that uses Flexible field. (Call to undefined method forceFill) #464

Closed Blindmikey closed 1 year ago

Blindmikey commented 1 year ago

Upon saving I get the error: Call to undefined method Whitecube\NovaFlexibleContent\Layouts\Layout::forceFill()

I believe this is related to this issue here: https://github.com/outl1ne/nova-settings/issues/166

Followed same steps to reproduce the issue on a clean install with with whitecube/nova-flexible-content and still get this error as well.

Blindmikey commented 1 year ago

Log:

[2023-06-28 06:02:43] local.ERROR: Call to undefined method Whitecube\NovaFlexibleContent\Layouts\Layout::forceFill() {"userId":1,"exception":"[object] (Error(code: 0): Call to undefined method Whitecube\\NovaFlexibleContent\\Layouts\\Layout::forceFill() at /home/vagrant/code/vendor/laravel/nova/src/Fields/Field.php:492)
[stacktrace]
#0 /home/vagrant/code/vendor/laravel/nova/src/Fields/Field.php(475): Laravel\\Nova\\Fields\\Field->fillModelWithData()
#1 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Support/helpers.php(307): Laravel\\Nova\\Fields\\Field->Laravel\\Nova\\Fields\\{closure}()
#2 /home/vagrant/code/vendor/laravel/nova/src/Fields/Field.php(472): tap()
#3 /home/vagrant/code/vendor/laravel/nova/src/Fields/Field.php(457): Laravel\\Nova\\Fields\\Field->fillAttributeFromRequest()
#4 /home/vagrant/code/vendor/laravel/nova/src/Fields/Field.php(439): Laravel\\Nova\\Fields\\Field->fillAttribute()
#5 /home/vagrant/code/vendor/laravel/nova/src/Fields/Field.php(413): Laravel\\Nova\\Fields\\Field->fillInto()
#6 /home/vagrant/code/vendor/whitecube/nova-flexible-content/src/Layouts/Layout.php(381): Laravel\\Nova\\Fields\\Field->fill()
#7 [internal function]: Whitecube\\NovaFlexibleContent\\Layouts\\Layout->Whitecube\\NovaFlexibleContent\\Layouts\\{closure}()
#8 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Collections/Arr.php(558): array_map()
#9 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Collections/Collection.php(771): Illuminate\\Support\\Arr::map()
#10 /home/vagrant/code/vendor/whitecube/nova-flexible-content/src/Layouts/Layout.php(380): Illuminate\\Support\\Collection->map()
#11 /home/vagrant/code/vendor/whitecube/nova-flexible-content/src/Flexible.php(362): Whitecube\\NovaFlexibleContent\\Layouts\\Layout->fill()
#12 [internal function]: Whitecube\\NovaFlexibleContent\\Flexible->Whitecube\\NovaFlexibleContent\\{closure}()
#13 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Collections/Arr.php(558): array_map()
#14 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Collections/Collection.php(771): Illuminate\\Support\\Arr::map()
#15 /home/vagrant/code/vendor/whitecube/nova-flexible-content/src/Flexible.php(350): Illuminate\\Support\\Collection->map()
#16 /home/vagrant/code/vendor/whitecube/nova-flexible-content/src/Flexible.php(319): Whitecube\\NovaFlexibleContent\\Flexible->syncAndFillGroups()
#17 /home/vagrant/code/vendor/laravel/nova/src/Fields/Field.php(439): Whitecube\\NovaFlexibleContent\\Flexible->fillAttribute()
#18 /home/vagrant/code/vendor/laravel/nova/src/Fields/Field.php(413): Laravel\\Nova\\Fields\\Field->fillInto()
#19 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Collections/HigherOrderCollectionProxy.php(60): Laravel\\Nova\\Fields\\Field->fill()
#20 [internal function]: Illuminate\\Support\\HigherOrderCollectionProxy->Illuminate\\Support\\{closure}()
#21 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Collections/Arr.php(558): array_map()
#22 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Collections/Collection.php(771): Illuminate\\Support\\Arr::map()
#23 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Collections/HigherOrderCollectionProxy.php(59): Illuminate\\Support\\Collection->map()
#24 /home/vagrant/code/vendor/laravel/nova/src/FillsFields.php(100): Illuminate\\Support\\HigherOrderCollectionProxy->__call()
#25 /home/vagrant/code/vendor/laravel/nova/src/FillsFields.php(37): Laravel\\Nova\\Resource::fillFields()
#26 /home/vagrant/code/vendor/laravel/nova/src/Http/Controllers/ResourceUpdateController.php(46): Laravel\\Nova\\Resource::fillForUpdate()
#27 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Database/Concerns/ManagesTransactions.php(30): Laravel\\Nova\\Http\\Controllers\\ResourceUpdateController->Laravel\\Nova\\Http\\Controllers\\{closure}()
#28 /home/vagrant/code/vendor/laravel/nova/src/Http/Controllers/ResourceUpdateController.php(37): Illuminate\\Database\\Connection->transaction()
#29 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): Laravel\\Nova\\Http\\Controllers\\ResourceUpdateController->__invoke()
#30 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()
#31 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()
#32 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()
#33 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Router.php(799): Illuminate\\Routing\\Route->run()
#34 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()
#35 /home/vagrant/code/vendor/laravel/nova/src/Http/Middleware/Authorize.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#36 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Laravel\\Nova\\Http\\Middleware\\Authorize->handle()
#37 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#38 /home/vagrant/code/vendor/laravel/nova/src/Http/Middleware/Authenticate.php(31): Illuminate\\Auth\\Middleware\\Authenticate->handle()
#39 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Laravel\\Nova\\Http\\Middleware\\Authenticate->handle()
#40 /home/vagrant/code/vendor/whitecube/nova-flexible-content/src/Http/Middleware/InterceptFlexibleAttributes.php(33): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#41 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Whitecube\\NovaFlexibleContent\\Http\\Middleware\\InterceptFlexibleAttributes->handle()
#42 /home/vagrant/code/vendor/laravel/nova/src/Http/Middleware/BootTools.php(20): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#43 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Laravel\\Nova\\Http\\Middleware\\BootTools->handle()
#44 /home/vagrant/code/vendor/laravel/nova/src/Http/Middleware/DispatchServingNovaEvent.php(33): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#45 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Laravel\\Nova\\Http\\Middleware\\DispatchServingNovaEvent->handle()
#46 /home/vagrant/code/vendor/inertiajs/inertia-laravel/src/Middleware.php(87): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#47 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Inertia\\Middleware->handle()
#48 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#49 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()
#50 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#51 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()
#52 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#53 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()
#54 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Session\\Middleware\\StartSession->handle()
#55 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#56 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()
#57 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#58 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()
#59 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#60 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Router.php(798): Illuminate\\Pipeline\\Pipeline->then()
#61 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Router.php(777): Illuminate\\Routing\\Router->runRouteWithinStack()
#62 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Router.php(741): Illuminate\\Routing\\Router->runRoute()
#63 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Routing/Router.php(730): Illuminate\\Routing\\Router->dispatchToRoute()
#64 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch()
#65 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()
#66 /home/vagrant/code/vendor/laravel/nova/src/Http/Middleware/ServeNova.php(23): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#67 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Laravel\\Nova\\Http\\Middleware\\ServeNova->handle()
#68 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#69 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#70 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()
#71 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#72 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#73 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()
#74 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#75 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()
#76 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#77 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()
#78 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#79 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle()
#80 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#81 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle()
#82 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#83 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then()
#84 /home/vagrant/code/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
#85 /home/vagrant/code/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()
#86 {main}
"} 
Blindmikey commented 1 year ago

Found the issue - Laravel Nova 4.26.0 (released today) introduces the error. Rolling back to 4.25.1 (one release prior) resolves the issue.

jigar-dhulla commented 1 year ago

Temp fix. Not raising PR because I am not sure if its the correct way. I have done only basic testing.

--- a/src/Layouts/Layout.php
+++ b/src/Layouts/Layout.php
@@ -7,6 +7,8 @@ use Illuminate\Contracts\Support\Arrayable;
 use Illuminate\Database\Eloquent\Concerns\HasAttributes;
 use Illuminate\Database\Eloquent\Concerns\HidesAttributes;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
 use JsonSerializable;
 use Laravel\Nova\Fields\Field;
 use Laravel\Nova\Fields\FieldCollection;
@@ -701,4 +703,21 @@ class Layout implements LayoutInterface, JsonSerializable, ArrayAccess, Arrayabl
     {
         return $this->attributesToArray();
     }
+
+    /**
+     * Fill the layout with an array of attributes.
+     *
+     * @param  array  $attributes
+     * @return $this
+     */
+    public function forceFill(array $attributes)
+    {
+        foreach ($attributes as $key => $value) {
+            $attribute = Str::replace('->', '.', $key);
+
+            Arr::set($this->attributes, $attribute, $value);
+        }
+
+        return $this;
+    }
 }
voidgraphics commented 1 year ago

Can more people chime in on this fix? I have no time to investigate and I'm fine merging a PR if enough people can confirm that it solves the problem.

I really wish Nova would be more careful with breaking changes on minor and patch releases.

jigar-dhulla commented 1 year ago

Screenshot 2023-06-28 183827

Investigation: In Nova's Field class, method fillAttributeFromRequest() with object $model is passed to \Illuminate\Database\Eloquent\Model|\Illuminate\Support\Fluent $model of fillModelWithData().

Layout object doesn't have method forceFill() like Model which Nova team has utilized in the 4.26 release. In earlier realeases, hydration was happening at fillAttributeFromRequest() itself.

Solution(Hacking) thought process: Nova team member has suggested to use Fluent instead of \stdClass for other package which has forceFill(). I have copied the same method from Fluent class. Property $this->attributes is being used by both Layout and Field so function(above patch) is working without any change.

HeadStudios commented 1 year ago

This is affecting me too - hopefully Nova provides a fix in their next update - I can confirm rolling back and updating composer.json to:

"laravel/nova": "4.25.1",

Has fixed the issue for me. Thank you so much to everyone who has inputted.

davidhemphill commented 1 year ago

I really wish Nova would be more careful with breaking changes on minor and patch releases.

Hey folks! Sorry to hear the latest update broke this package. I'm curious, though, what could we have done in this instance? Our codebase has extensive test coverage, and we didn't introduce any backward-incompatible changes according to our tests.

jigar-dhulla commented 1 year ago

I really wish Nova would be more careful with breaking changes on minor and patch releases.

Hey folks! Sorry to hear the latest update broke this package. I'm curious, though, what could we have done in this instance? Our codebase has extensive test coverage, and we didn't introduce any backward-incompatible changes according to our tests.

I might be wrong as I can't think from Nova maintainence or Flexible maintanence point of view.

According to me forceFill() should have been avoided and implementation to hydrate attributes should have been kept the same.

Package maintainers already have taken advantage of this object $model where they had freedom to create object of their any of the classes. And suddenly in 4.26, Nova is expecting them to have forceFill().

jsmit99 commented 1 year ago

Is there any ETA on when this package will be working again with newest versions of Nova?

alies-dev commented 1 year ago

@davidhemphill There is a package that can find you BC breaks: https://github.com/Roave/BackwardCompatibilityCheck (and CI integration example https://github.com/spatie/calendar-links/blob/master/.github/workflows/backward-compatibility-check.yml) , but it's too strict for Nova project I think. It will encourage you a lot to use final keyword for classes and methods - there is a room for such improvements on Nova and such changes of course will be introduce BC breaks for A LOT packages, but IMO this is a required step to avoid issues like this in the future.

Blindmikey commented 1 year ago

Perhaps this is already being done, or perhaps it's not feasible - however a pre-release notification to 3rd party package developers with a window to test and communicate breaking changes and collaborate on fixes before public release should alleviate issues like this.

LorenzoSapora commented 1 year ago

PR submitted contributions welcome.

voidgraphics commented 1 year ago

This should be fixed in v1.0.9 thanks to @LorenzoSapora. Can someone report back? I will reopen if needed.

m-lotze commented 1 year ago

v.10.9 did not fix the issue. Still getting the error call to undefined method Whitecube\NovaFlexibleContent\Layouts\Layout::forceFill()

alies-dev commented 1 year ago

I can confirm, 10.9 did not fix the issue

voidgraphics commented 1 year ago

Okay, apologies for that, we could not test the fix because our license has expired. Still looking for someone who could submit a PR with a fix.

Jaspur commented 1 year ago

Okay, apologies for that, we could not test the fix because our license has expired. Still looking for someone who could submit a PR with a fix.

You can use Nova 4 locally without License. @voidgraphics

voidgraphics commented 1 year ago

Okay, apologies for that, we could not test the fix because our license has expired. Still looking for someone who could submit a PR with a fix.

You can use Nova 4 locally without License. @voidgraphics

Interesting, that did not work for me earlier, composer refused to download the package. I will try again in a bit. Thanks for the link!

LTKort commented 1 year ago

@voidgraphics I proposed a PR #470 This fixes the problem on our version of the package

voidgraphics commented 1 year ago

Thanks @LTKort. I just tagged v1.0.10 with your fix. Please report back again @m-lotze @lptn

alies-dev commented 1 year ago

@voidgraphics Almost there! 😂 Just need to add missing imports and it will work:

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

PS: hey @davidhemphill

@voidgraphics wrote: Okay, apologies for that, we could not test the fix because our license has expired. Still looking for someone who could submit a PR with a fix.

Interesting, that did not work for me earlier, composer refused to download the package. I will try again in a bit. Thanks for the link!

I have an idea: provide free licenses to maintainers of some popular nova packages just to keep ecosystem healthy and avoid issues like this

LTKort commented 1 year ago

@voidgraphics Almost there! 😂 Just need to add missing imports and it will work:

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

PS: hey @davidhemphill

@voidgraphics wrote: Okay, apologies for that, we could not test the fix because our license has expired. Still looking for someone who could submit a PR with a fix.

Interesting, that did not work for me earlier, composer refused to download the package. I will try again in a bit. Thanks for the link!

I have an idea: provide free licenses to maintainers of some popular nova packages just to keep ecosystem healthy and avoid issues like this

@lptn My bad, forgot them. @voidgraphics I added them in the new pr #471

voidgraphics commented 1 year ago

Totally missed the imports 😂 sorry, this is a bit of a mess. I added the imports and tagged yet another new version.

alies-dev commented 1 year ago

No tests, no licenses, I feel it's like 200X 😂

Thanks a lot guys, it works now! 💪

m-lotze commented 1 year ago

Working with new version. Thank you.

alies-dev commented 1 year ago

@voidgraphics I just created 2 PRs to avoid such issues: one with a test, another one with improved PHPDoc (to help Psalm and PHPStan to find issues like this). Can you please review them?

472

473

Thanks!

davidhemphill commented 1 year ago

PS: hey @davidhemphill

I have an idea: provide free licenses to maintainers of some popular nova packages just to keep ecosystem healthy and avoid issues like this

We already do! https://github.com/laravel/nova-issues/discussions/4911#discussioncomment-3961246.