I'm encountering an error in my Symfony application related to accessing the $container property of Symfony\Bundle\FrameworkBundle\Controller\AbstractController. The error message I'm receiving is:
Error: Typed property Symfony\Bundle\FrameworkBundle\Controller\AbstractController::$container must not be accessed before initialization
D:\Development\omegofleet\vendor\symfony\framework-bundle\Controller\AbstractController.php:343
D:\Development\omegofleet\src\Controller\UserController.php:203
D:\Development\omegofleet\tests\UserTest.php:185
Here's an example of my controller class:
<?php
namespace App\Controller;
use App\Entity\Company;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Firebase\JWT\JWT;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class UserController extends AbstractController
{
private EntityManagerInterface $entityManager;
private UserRepository $userRepository;
private TokenStorageInterface $tokenStorage;
public function __construct(EntityManagerInterface $entityManager, UserRepository $userRepository, TokenStorageInterface $tokenStorage)
{
$this->entityManager = $entityManager;
$this->userRepository = $userRepository;
$this->tokenStorage = $tokenStorage;
}
#[Route('/api/users/{id}', name: 'user_delete', methods: ['DELETE'])]
public function delete(User $user): Response
{
$currentUser = $this->getUser();
if (!$currentUser) {
return $this->json(['error' => 'User not authenticated'], Response::HTTP_UNAUTHORIZED);
}
// Load user's role
$userRole = $this->getUserRole($currentUser->getUsername());
if (!in_array($userRole, ['ROLE_SUPER_ADMIN'])) {
return $this->json(['error' => 'Access denied'], Response::HTTP_FORBIDDEN);
}
And here's part of my usertest class:
#[AllowDynamicProperties] class UserTest extends WebTestCase
{
protected function setUp(): void
{
parent::setUp(); // Ensure parent setUp method is called
$this->entityManagerMock = Mockery::mock(EntityManagerInterface::class);
$this->userRepositoryMock = Mockery::mock(UserRepository::class);
$this->tokenStorageMock = Mockery::mock(TokenStorageInterface::class); // Add this line
}
public function tearDown(): void
{
parent::tearDown();
Mockery::close();
}
public function testLoginSuccessful()
{
$mockRequest = Mockery::mock(Request::class);
$mockRequest->allows('getContent')->andReturns(json_encode(['name' => 'john.doe']));
$mockUser = $this->createMockUser('john.doe', 'ROLE_SUPER_ADMIN');
$mockUserRepository = Mockery::mock(UserRepository::class);
$mockUserRepository->allows('findOneBy')->with(['name' => 'john.doe'])->andReturns($mockUser);
$this->entityManagerMock
->allows('getRepository')
->with(User::class)
->andReturns($mockUserRepository);
$mockEntityManager = Mockery::mock(EntityManagerInterface::class);
$mockEntityManager->allows('getRepository')->with(User::class)->andReturns($mockUserRepository);
// Create mock UserController with required dependencies
$mockController = Mockery::mock(UserController::class, [$this->entityManagerMock, $this->userRepositoryMock, $this->tokenStorageMock]) // Pass $this->tokenStorageMock here
->makePartial(); // Allow calling actual `generateToken`
// $mockController->allows('generateToken')->with('john.doe')->andReturns('test_token');
// Call the login method
$response = $mockController->login($mockRequest);
$this->assertInstanceOf(JsonResponse::class, $response);
$responseData = json_decode($response->getContent(), true);
$this->assertArrayHasKey('token', $responseData);
$this->assertIsString($responseData['token']);
// Decode and verify the JWT token
$secretKey = $_ENV['JWT_SECRET'];
$decodedToken = JWT::decode($responseData['token'], new Key($secretKey, 'HS256'));
$this->assertEquals('john.doe', $decodedToken->name);
// Retrieve the role from the UserRepository
$userRepositoryMock = Mockery::mock(UserRepository::class);
$userRepositoryMock->allows('loadUserByRole')->with('john.doe')->andReturn('ROLE_USER_ADMIN');
// Mock the EntityManagerInterface
$mockEntityManager = Mockery::mock(EntityManagerInterface::class);
$mockEntityManager->allows('getRepository')->with(User::class)->andReturn($userRepositoryMock);
// Call the login method
$response = $mockController->login($mockRequest);
// Assert the response
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
return $response;
}
protected function createMockUser(string $name, string $role): User
{
$mockUser = new User();
$mockUser->setName($name);
$mockUser->setId(1);
$mockUser->setRole($role);
return $mockUser;
}
public function testDeleteAsSuperAdmin(): void
{
// Dummy login process
$loginResponse = $this->testLoginSuccessful();
$token = json_decode($loginResponse->getContent(), true)['token'];
// Create a user using the createMockUser method
$user = $this->createMockUser('Test User', 'ROLE_SUPER_ADMIN');
$user->setId(1);
$this->entityManagerMock
->allows('getRepository')
->with(User::class)
->andReturns($this->userRepositoryMock);
// Create a DELETE request to delete the user
$request = Request::create('/api/users/' . $user->getId(), 'DELETE');
$request->headers->set('Authorization', 'Bearer ' . $token);
// Mock EntityManager to remove the user
$this->entityManagerMock->allows('remove')->with($user);
$this->entityManagerMock->allows('flush');
// Create an instance of UserController with mocked dependencies
$controller = new UserController($this->entityManagerMock, $this->userRepositoryMock, $this->tokenStorageMock);
// Expectation on the userRepositoryMock should be set before calling the delete method
$this->userRepositoryMock
->allows('find')
->with($user->getId())
->andReturns($user);
// Call the delete method with the fetched user object
$response = $controller->delete($user);
// Assert the response status code and content
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
$this->assertJsonStringEqualsJsonString('{"message": "User deleted"}', $response->getContent());
}
protected function generateDummyToken(string $username): string
{
// Generate a dummy JWT token for testing purposes
$payload = [
'name' => $username,
'exp' => time() + 3600 // Token expiration time (1 hour)
];
$secretKey = 'dummy_secret_key'; // Replace with your actual secret key
return JWT::encode($payload, $secretKey, 'HS256');
}
And here is the line from AbstractController :
protected function getUser(): ?UserInterface
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
and here is the service.yml :
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
Despite this setup, I'm still encountering the error. What could be causing this issue, and how can I resolve it?
Any insights or suggestions would be greatly appreciated. Thank you!