whitecube/nova-flexible-content

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

Blindmikey opened this issue ยท 27 comments

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

I believe this is related to this issue here: outl1ne/nova-settings#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.

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}
"} 

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.

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;
+    }
 }

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.

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.

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.

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 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().

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

@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.

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.

PR submitted contributions welcome.

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

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

I can confirm, 10.9 did not fix the issue

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

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

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

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

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

@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

@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

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

No tests, no licenses, I feel it's like 200X ๐Ÿ˜‚

Thanks a lot guys, it works now! ๐Ÿ’ช

Working with new version. Thank you.

@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!

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! laravel/nova-issues#4911 (comment).