Confusion in DI variables

caught-in-a-nette
Member | 19
+
0
-

I have been building this app for several weeks. It has been functioning well. I have been testing it on my iPad frequently.
Recently it stopped working on the iPad – Server Error 500.
Log/exception.log says: TypeError: App\UI\Dashboard\DashboardPresenter::__construct(): Argument #2 ($settingsFacade) must be of type App\Model\SettingsFacade, App\Forms\AddWorkoutForm given, called in /Users/.me./Projects/nette/workout3/temp/cache/nette.configurator/Container_bf083af21a.php on line 276 in /Users/.me./Projects/nette/workout3/app/UI/Dashboard/DashboardPresenter.php:30

I have a few presenters (like DashboardPresenter) that extend my BasePresenter. Each has: use App\Model\SettingsFacade; then in __construct I have a private variable to reference the facade (private readonly SettingsFacade $settingsFacade,). (and some other references.). Under that reference I have a separate reference to AddWorkoutForm (private AddWorkoutForm $addWorkoutForm,). (BTW in services.neon I have added *Form in search > classes (along with *Factory & *Facade.))

I tried renaming the references, recording them and adding readonly; also within the AddWorkoutForm class I renamed its methods to not contain ‘Form’. No difference – same error! the only thing that removed the error was to comment-out the reference, but it is needed.

(Also I get a similar error for other presenters…)

Why are these references suddenly being confused? Am I missing a vital thing?

Marek Bartoš
Nette Blogger | 1275
+
0
-

My guess is your app is in production mode (no Tracy bar visible), you've added new dependency to the DashboardPresenter constructor and didn't delete cache. Cached DI container is not automatically reloaded in production mode and you have to delete it when deploying your app to generate a new one.

Also when you forget to delete cache, while the error will show up in production mode, testing in debug mode will not cause the same error at all because the DIC for both modes is generated separately.

Nette DI resolves all references when DIC is compiled and therefore most errors will occur immediatelly in debug mode and right after deleting the cache folder (/temp/cache/nette.configurator) in production mode.

Last edited by Marek Bartoš (2024-10-25 21:27)

caught-in-a-nette
Member | 19
+
0
-

Hi

I forgot to mention the app works fine in Safari (and Chrome) on my desktop and only fails on iPad. (The desktop shows tracy bar and shows a red banner with all other errors. So it must be in Dev. mode; Yet my iPad never does and thus Prod. mode. Side question do you know how to specify whole network [ie. 192.168.1.0/24] in Bootstrap Configurator setDebugMode? ATM I have array of different hostnames.)

This is explained “… the DIC for both modes is generated separately.”. But I knew that, but it not at the front-of-my-brain.

Anyway I frequently do rm -rf temp/cache and yet the error persists.
Moreover I put those DI references in ages ago…

So do you think there could be an (non explicit) error in the AddWorkoutForm class that could be causing the (above) exception that I see?

Marek Bartoš
Nette Blogger | 1275
+
0
-

Perhaps caching in the browser might be a problem?
Or opcode cache? You can flush it via http call to a script calling opcache_reset();. Console usually has separate (and disabled) opcache

There is no reason why different dependencies would be pased by Nette while you are on another device. You can check variables that are used to generate unique DIC. And even if it was the case, (flushing opcache and) deleting generated DIC would help.

Side question do you know how to specify whole network [ie. 192.168.1.0/24] in Bootstrap Configurator setDebugMode?

Current implementation of IP address detection does not support that. But setDebugMode() accepts boolean, so you can use practically anything for detection and just pass true to enable the debug mode (of false to disable). I've implemented various methods for that (here), but the simplest one would be to set environment variable in your local webserver and console configuration and check if the variable exists and is set to 1/true/whatever

So do you think there could be an (non explicit) error in the AddWorkoutForm class that could be causing the (above) exception that I see?

Nothing else that I am aware of. If the previous steps don't help, perhaps sharing bootstrap file and neon configuration would

Last edited by Marek Bartoš (2024-10-25 23:23)

caught-in-a-nette
Member | 19
+
0
-

OK Thanks I (first got server error when I un-commented that error-causing reference, then) added `php opcache_reset();` inside DashboardPresenter::__construct. Refreshed the page and it showed OK.

Also thanks for the explanation of setDebugMode(). I will experiment with similar options.
So in summary you have been very helpful!

caught-in-a-nette
Member | 19
+
0
-

Actually can I ask a related question?

I want BasePresenter to get the settings via SettingsFacade, like how DashboardPresenter does. But when I try and set $this->template… (along with use and DI ref.) it won't show. Why?

caught-in-a-nette
Member | 19
+
0
-

More information:

BasePresenter.php:

<?php
use App\Model\SettingsFacade;

abstract class BasePresenter extends Presenter
{
    public function __construct(
        private readonly SettingsFacade $settingsFacade,
    )
    {
    }

    public function renderDefault(): void
    {
        $this->template->settings = $this->settingsFacade->getAll();
    }

}
?>

gives error:
Typed property App\BasePresenter::$settingsFacade must not be accessed before initialization

Marek Bartoš
Nette Blogger | 1275
+
0
-

added php opcache_reset(); inside DashboardPresenter::__construct. Refreshed the page and it showed OK.

Don't do that on a regular page. It will reset cache of all php files available (that is all php files on the server, in the simplest setup) and slow down application significantly.

Instead configure the opcache on your local environment to refresh automatically. And call it on a separate page during deploy to server. If you already have deploy automated, this is my automated version (syntax is specific to Makefile):

	@echo "Reset OPCache"
	@RAND_STR=$$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1); \
	echo "<?php opcache_reset();" > "opcache-reset-$${RAND_STR}.php"; \
	wget --no-cache --spider https://$(PROJECT_URL)/opcache-reset-$${RAND_STR}.php; \
	rm -f opcache-reset-$${RAND_STR}.php

I want BasePresenter to get the settings via SettingsFacade, like how DashboardPresenter does. But when I try and set $this->template… (along with use and DI ref.) it won't show. Why?

Hard to say, I don't have what “does not show” means. Do you have the dependency injected? In which BasePresenter method do you access $this->template?

Last edited by Marek Bartoš (2024-10-26 00:22)

Marek Bartoš
Nette Blogger | 1275
+
0
-

Typed property App\BasePresenter::$settingsFacade must not be accessed before initialization

That's PHP behavior. You've overriden constructor of BasePresenter in DashboardPresenter. That means you have to call parent::__construct() in constructor of DashboardPresenter. Otherwise, constructor of BasePresenter is never called and variable is therefore never initialized.

In Nette, base presenters can easily get to have quite a lot of dependencies. Instead of constantly modifying child constructors, you can use inject feature in the BasePresenter and keep the children simpler.

caught-in-a-nette
Member | 19
+
0
-

I am a dummy – I should've done that!
Thanks all good now.
Just gotta pass $settingsFacade from DashboardPresenter back to parent constructor