This is going to be a little bit tricky and it took me a while to fully understand why it happens and I still don't really know how to prevent it.
I'm not really sure if this is an issue on your end (because it's technically only caused by plugins (such as Relabel or CpFieldInspect))
When you call Craft::$app->getUser()->getIdentity() in a plugins init method or in the Plugins::EVENT_AFTER_LOAD_PLUGINS event strange things happen in certain scenarios. I already created an issue for that in CpFieldInspect but I'll explain the same thing a little bit more detailed again.
When you call Craft::$app->getUser()->getIdentity() it will create a new ElementQuery to fetch the user (if it's the first time)
The ElementQuery will then call Craft::$app->getFields()->getAllFields() because it needs to select the correct columns for the users content.
Craft::$app->getFields()->getAllFields() will then create a Query to load all the fields and now comes the tricky part
try {
/** @var Field $field */
$field = ComponentHelper::createComponent($config, FieldInterface::class);
} catch (MissingComponentException $e) {
$config['errorMessage'] = $e->getMessage();
$config['expectedType'] = $config['type'];
unset($config['type']);
$field = new MissingField($config);
}
ComponentHelper::createComponent will then try to create the other plugin and if another plugin then calls Craft::$app->getFields()->getAllFields() or Craft::$app->getUser()->getIdentity() again the function will only return the fields that are currently created.
So for example: if you have a plugin that has a custom field type and you call this fields name/handle a it might be the very first field that is fetched.
When Craft fetches that field it will load all plugins during that very first process but Fields::_fields only contains that one single field. So When a plugin during that whole process calls getAllFields or something that calls getAllFields like every ElementQuery it returns that "cached" result instead of all fields.
In my case I had a custom field called accessToken which leaves my currentUsers fields totally empty because
protected function customFields(): array
{
// todo: remove this after the next breakpoint
if (Craft::$app->getUpdates()->getIsCraftDbMigrationNeeded()) {
return [];
}
$contentService = Craft::$app->getContent();
$originalFieldContext = $contentService->fieldContext;
$contentService->fieldContext = 'global';
$fields = Craft::$app->getFields()->getAllFields(); // <---- contains only one field
$contentService->fieldContext = $originalFieldContext;
return $fields;
}
aaaaElementsQuery in the same or another plugins init function or in your Plugins::EVENT_AFTER_LOAD_PLUGINS eventgetAllFieldsAs I said, I don't know how to solve it entirely, but a solution to make it at least a little bit better would be to implement a getAllFieldHandlesInContentTable (Or something shorter) so at least Elements will be propagated correctly
I'm not good in explaining, I hope you get the point. Maybe it's easier with a stack trace...
聽 | #0 craft\services\Fields->getAllFields() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/elements/db/ElementQuery.php:1934]
-- | --
// get all fields is called a second time -> it only contains one cached field, so it's not null and will return that one field
聽 | #1 craft\elements\db\ElementQuery->customFields() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/elements/db/ElementQuery.php:1353]
聽 | #2 craft\elements\db\ElementQuery->prepare() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/db/QueryBuilder.php:227]
聽 | #3 yii\db\QueryBuilder->build() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/db/Query.php:146]
聽 | #4 yii\db\Query->createCommand() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/db/Query.php:274]
聽 | #5 yii\db\Query->one() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/db/Query.php:177]
聽 | #6 craft\db\Query->one() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/elements/db/ElementQuery.php:1502]
聽 | #7 craft\elements\db\ElementQuery->one() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/elements/User.php:383]
聽 | #8 craft\elements\User::findIdentity() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/web/User.php:690]
聽 | #9 yii\web\User->renewAuthStatus() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/web/User.php:490]
聽 | #10 craft\web\User->renewAuthStatus() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/web/User.php:192]
聽 | #11 yii\web\User->getIdentity() called at [/var/www/myspa/htdocs/vendor/anubarak/craft-relabel/src/Relabel.php:196]
// My Relabel Plugin is created, but it could very well be every other Plugin that calls `getIdentity` or something now
聽 | #12 anubarak\relabel\Relabel->init() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/base/BaseObject.php:109]
聽 | #13 yii\base\BaseObject->__construct() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/base/Module.php:158]
聽 | #14 yii\base\Module->__construct() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/base/Plugin.php:127]
聽 | #15 craft\base\Plugin->__construct()
聽 | #16 ReflectionClass->newInstanceArgs() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/di/Container.php:385]
聽 | #17 yii\di\Container->build() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/di/Container.php:157]
聽 | #18 yii\di\Container->get() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/BaseYii.php:349]
聽 | #19 yii\BaseYii::createObject() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/services/Plugins.php:897]
聽 | #20 craft\services\Plugins->createPlugin() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/services/Plugins.php:230]
聽 | #21 craft\services\Plugins->loadPlugins() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/services/Plugins.php:779]
聽 | #22 craft\services\Plugins->isPluginEnabled() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/helpers/Component.php:64]
聽 | #23 craft\helpers\Component::validateComponentClass() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/helpers/Component.php:106]
// the field is a custom one, initialize all plugins in Plugins::loadAllPlugins()
聽 | #24 craft\helpers\Component::createComponent() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/services/Fields.php:560]
聽 | #25 craft\services\Fields->createField() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/services/Fields.php:590]
聽 | #26 craft\services\Fields->getAllFields() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/elements/db/ElementQuery.php:1934]
// My Element Query created ealier will grab all the fields
聽 | #27 craft\elements\db\ElementQuery->customFields() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/elements/db/ElementQuery.php:1353]
聽 | #28 craft\elements\db\ElementQuery->prepare() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/db/QueryBuilder.php:227]
聽 | #29 yii\db\QueryBuilder->build() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/db/Query.php:146]
聽 | #30 yii\db\Query->createCommand() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/db/Query.php:237]
聽 | #31 yii\db\Query->all() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/db/Query.php:161]
聽 | #32 craft\db\Query->all() called at [/var/www/myspa/htdocs/vendor/craftcms/cms/src/elements/db/ElementQuery.php:1487]
聽 | #33 craft\elements\db\ElementQuery->all() called at [/var/www/myspa/htdocs/modules/myspa/services/Stores.php:125]
聽 | #34 modules\myspa\services\Stores->init() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/base/BaseObject.php:109]
聽 | #35 yii\base\BaseObject->__construct()
聽 | #36 ReflectionClass->newInstanceArgs() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/di/Container.php:377]
聽 | #37 yii\di\Container->build() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/di/Container.php:157]
聽 | #38 yii\di\Container->get() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/BaseYii.php:345]
聽 | #39 yii\BaseYii::createObject() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/di/ServiceLocator.php:137]
聽 | #40 yii\di\ServiceLocator->get() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/base/Module.php:745]
聽 | #41 yii\base\Module->get() called at [/var/www/myspa/htdocs/modules/myspa/base/PluginComponentTrait.php:46]
聽
// I need to fetch an Element in my Modules component -> one could say this is wrong, but I'm not sure if you really forbit to fetch elements during the whole process of initialization.
| #42 modules\myspa\MYSPA->getStores() called at [/var/www/myspa/htdocs/modules/myspa/eventhandlers/DiscountEvents.php:40]
聽 | #43 modules\myspa\eventhandlers\DiscountEvents::attachEventHandlers() called at [/var/www/myspa/htdocs/modules/myspa/base/EventHandlers.php:50]
聽 | #44 modules\myspa\base\EventHandlers::attachEventHandlers() called at [/var/www/myspa/htdocs/modules/myspa/MYSPA.php:311]
聽 | #45 modules\myspa\MYSPA->init() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/base/BaseObject.php:109]
聽 | #46 yii\base\BaseObject->__construct() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/base/Module.php:158]
聽 | #47 yii\base\Module->__construct() called at [/var/www/myspa/htdocs/modules/myspa/MYSPA.php:256]
聽 | #48 modules\myspa\MYSPA->__construct()
聽 | #49 ReflectionClass->newInstanceArgs() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/di/Container.php:377]
聽 | #50 yii\di\Container->build() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/di/Container.php:157]
聽 | #51 yii\di\Container->get() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/BaseYii.php:345]
聽 | #52 yii\BaseYii::createObject() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/base/Module.php:427]
// get all the modules
聽 | #53 yii\base\Module->getModule() called at [/var/www/myspa/htdocs/vendor/yiisoft/yii2/base/Application.php:315]
Edit:
my simple question is: is this an issue of Plugin developers that try to grab the Users Identity in their plugins init function to add certain events or something (so faulty usage of Craft) or an unexpected behavior?
I think this is a plugin issue; plugins are loaded one by one during the bootstrap process, so plugins shouldn鈥檛 assume that the app is fully bootstrapped yet from their init() methods, and hold off until EVENT_AFTER_LOAD_PLUGINS before executing any element queries.
In hindsight, element queries should probably be ensuring that the app is fully bootstrapped before executing, and throw an exception if not, to ensure this sort of thing isn鈥檛 possible. Too late for Craft 3 now, but I will go ahead and mark this as an enhancement for Craft 4.
Great investigation/writeup, though, Robin @Anubarak . And I have had to use EVENT_AFTER_LOAD_PLUGINS myself to avoid the area of problems. Best result is that Brandon's going to put in a guarantee.
Let鈥檚 keep this open so it doesn鈥檛 get overlooked when we鈥檙e working on v4 :)
Most helpful comment
I think this is a plugin issue; plugins are loaded one by one during the bootstrap process, so plugins shouldn鈥檛 assume that the app is fully bootstrapped yet from their
init()methods, and hold off untilEVENT_AFTER_LOAD_PLUGINSbefore executing any element queries.In hindsight, element queries should probably be ensuring that the app is fully bootstrapped before executing, and throw an exception if not, to ensure this sort of thing isn鈥檛 possible. Too late for Craft 3 now, but I will go ahead and mark this as an enhancement for Craft 4.