Default model not available in controller

I’m having a really odd problem. I have a Users controller that has worked perfectly fine with no issues for months. I have been working on other aspects of the system I’m developing (baking new models etc.) and today I returned to the Users section of the system and received the following error:

Call to a member function find() on null

It seems that the Users model is no longer available in the Users controller. The odd thing is that this has been working for months and I haven’t touched any of the code in this area at all. The code has been around since CakePHP 3 and has been updated a several times and now runs on CakePHP 5.3.5.

I did update from CakePHP 5.3.4 to 5.3.5 since I last used the Users functionality, so maybe there’s something there?

If I add a $Users class variable and add the following in the initialize() function:

$this->Users = $this->fetchTable('Users');

Then everything works again. The controller and user management pages in the system work perfectly.

So for some reason the default model is not available to the controller.

Obviously I could just ignore this as it’s now working, but I’m concerned about what actually happened here. I haven’t changed any of the code in the Users table, entity or controller. I have been changing a lot of other code in the project, but it’s all been baking new models and adding code to those models and controllers.

I’m assuming I’ve changed something elsewhere that’s broken this. Any ideas what I could have changed that would cause the problem? All other models are working perfectly, it’s just Users.

The top of the Users controller (with the fetchTable fix) looks like this (it has an ‘Admin’ prefix):

namespace App\Controller\Admin;

use App\Controller\AppController;

/**
 * Users Controller
 *
 * @property \App\Model\Table\UsersTable $Users
 *
 * @method \App\Model\Entity\User[] paginate($object = null, array $settings = [])
 */
class UsersController extends AppController {
    
    var $Users;
    
	public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Filter');
        
        $this->Users = $this->fetchTable('Users');
        
    }

	/**
	 * Before Filter method
	 */
	function beforeFilter(\Cake\Event\EventInterface $event) {
		parent::beforeFilter($event);
		$this->viewBuilder()->setLayout('admin');

		// Set some default meta
		$this->set_meta(['meta_title' => 'Users', 'meta_description' => 'Users']);

	}

The Entity file is:

declare(strict_types=1);

namespace App\Model\Entity;

use Cake\ORM\Entity;
use Authentication\PasswordHasher\DefaultPasswordHasher;

/**
 * User Entity
 *
 * @property int $id
 * @property string $email
 * @property string $password
 * @property int $access_group_id
 * @property \Cake\I18n\DateTime $created
 * @property \Cake\I18n\DateTime $modified
 *
 * @property \App\Model\Entity\AccessGroup $access_group
 * @property \App\Model\Entity\UserProfile[] $user_profiles
 */
class User extends Entity
{

    /**
     * Fields that can be mass assigned using newEntity() or patchEntity().
     *
     * Note that when '*' is set to true, this allows all unspecified fields to
     * be mass assigned. For security purposes, it is advised to set '*' to false
     * (or remove it), and explicitly make individual fields accessible as needed.
     *
     * @var array
     */
    protected array $_accessible = [
        '*' => true,
        'id' => false
    ];

    /**
     * Fields that are excluded from JSON versions of the entity.
     *
     * @var array
     */
    protected array $_hidden = [
        'password'
    ];
    
    protected function _setPassword($value) {
        $hasher = new DefaultPasswordHasher();
        return $hasher->hash($value);
    }
    
}

And the top of the Table file is:

namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\TableRegistry;
use Cake\Core\Configure;

/**
 * Users Model
 *
 * @property \App\Model\Table\AccessGroupsTable|\Cake\ORM\Association\BelongsTo $AccessGroups
 * @property \App\Model\Table\UserProfilesTable|\Cake\ORM\Association\HasMany $UserProfiles
 *
 * @method \App\Model\Entity\User get($primaryKey, $options = [])
 * @method \App\Model\Entity\User newEntity($data = null, array $options = [])
 * @method \App\Model\Entity\User[] newEntities(array $data, array $options = [])
 * @method (\App\Model\Entity\User | bool) save(\Cake\Datasource\EntityInterface $entity, $options = [])
 * @method \App\Model\Entity\User patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
 * @method \App\Model\Entity\User[] patchEntities($entities, array $data, array $options = [])
 * @method \App\Model\Entity\User findOrCreate($search, callable $callback = null, $options = [])
 *
 * @mixin \Cake\ORM\Behavior\TimestampBehavior
 */
class UsersTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config): void
    {
        parent::initialize($config);

        $this->setTable('users');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

        $this->addBehavior('Timestamp');

        $this->belongsTo('AccessGroups', [
            'foreignKey' => 'access_group_id',
            'joinType' => 'INNER'
        ]);
        $this->hasMany('UserProfiles', [
            'foreignKey' => 'user_id'
        ]);
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator): \Cake\Validation\Validator
    {
        $validator
            ->integer('id')
            ->allowEmptyString('id', null, 'create');

        $validator
            ->email('email')
            ->requirePresence('email', 'create')
            ->notEmptyString('email');
		
		$validator
            ->requirePresence('username', 'create')
            ->notEmptyString('username');
            
		$validator
			->notEmptyString('firstname', __('A first name is required'));
		
		$validator
			->notEmptyString('surname', __('A surname is required'));
			
        $validator
            ->scalar('password')
            ->requirePresence('password', 'create')
            ->notEmptyString('password')
			->add('password', 'length', [
		        'rule' => ['minLength', 6],
		        'message' => 'Passwords must be at least 6 characters long.'
		    ]);
		
		$validator->add('confirm_password', 'custom', [
	        'rule' => function ($value, $context) {
		        if (!empty($context['data']['password']) and $context['data']['password'] <> $value) {
			        return false;
		        }else{
			    	return true;
			    }
	        },
	        'message' => 'Passwords entered are not the same.',
	    ]);
           
		
        return $validator;
    }
	
	public function customValidationMethod($check, array $context)
	{
	    $userid = $context['providers']['passed']['userid'];
	}

    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     *
     * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules): \Cake\ORM\RulesChecker
    {
        $rules->add($rules->isUnique(['username']));
        $rules->add($rules->existsIn(['access_group_id'], 'AccessGroups'));

        return $rules;
    }

As I say, all of this code has worked perfectly for months and has not been touched.