|
|
@@ -0,0 +1,293 @@
|
|
|
+# AP2I Project Guidelines
|
|
|
+
|
|
|
+## Project Overview
|
|
|
+
|
|
|
+AP2I is a Symfony 7.3-based API application that is part of the OpenTalent ecosystem.
|
|
|
+It's a comprehensive API platform built with modern PHP (8.2+) and leverages API Platform 4.1 for REST/GraphQL API capabilities.
|
|
|
+
|
|
|
+### Key Technologies
|
|
|
+- **Framework**: Symfony 7.3
|
|
|
+- **API Platform**: 4.1 (REST/GraphQL APIs)
|
|
|
+- **PHP**: 8.2+ (strict requirement)
|
|
|
+- **ORM**: Doctrine 3.3 with migrations
|
|
|
+- **Authentication**: JWT (Lexik JWT Authentication Bundle)
|
|
|
+- **Database**: Uses Doctrine DBAL 3.9 and a mariadb 10.4.26 DB
|
|
|
+- **Messaging**: Symfony Messenger for async processing
|
|
|
+- **Testing**: PHPUnit 9.6
|
|
|
+- **Code Quality**: PHPStan, PHP CS Fixer
|
|
|
+
|
|
|
+### Business Domains
|
|
|
+The application handles multiple business domains including:
|
|
|
+- Organization management
|
|
|
+- Education systems
|
|
|
+- Billing and payments
|
|
|
+- User profiles and access control
|
|
|
+- Network management
|
|
|
+- Booking systems
|
|
|
+- Export functionality
|
|
|
+- Shop/e-commerce features
|
|
|
+
|
|
|
+## Project Structure
|
|
|
+
|
|
|
+```
|
|
|
+src/
|
|
|
+├── Message/ # Symfony Messenger messages and handlers
|
|
|
+├── DataFixtures/ # Database fixtures for testing/development
|
|
|
+├── Validator/ # Custom validation logic
|
|
|
+├── Enum/ # Domain-specific enumerations
|
|
|
+├── Service/ # Business logic services
|
|
|
+└── ... # Other domain-specific directories
|
|
|
+
|
|
|
+tests/
|
|
|
+├── Fixture/ # Test data fixtures and factories
|
|
|
+├── Application/ # Application-level integration tests
|
|
|
+├── Unit/ # Unit tests organized by domain
|
|
|
+└── ...
|
|
|
+
|
|
|
+config/ # Symfony configuration
|
|
|
+public/ # Web root directory
|
|
|
+templates/ # Twig templates
|
|
|
+migrations/ # Doctrine database migrations
|
|
|
+```
|
|
|
+
|
|
|
+## Development Guidelines
|
|
|
+
|
|
|
+### Testing
|
|
|
+- **Always run tests** before submitting changes to ensure correctness
|
|
|
+- The project uses PHPUnit for testing with comprehensive unit and application tests
|
|
|
+- Tests are organized by domain to mirror the application structure
|
|
|
+- Do not modify the SUT, except for converting private methods into protected ones, or because there is an obvious error in it
|
|
|
+- Take `tests/Unit/Service/Typo3/Typo3ServiceTest.php` as the reference example for writing service tests
|
|
|
+
|
|
|
+#### Test Class Structure
|
|
|
+Each test class should follow this structure:
|
|
|
+
|
|
|
+```php
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Tests\Unit\Service\MyDomain;
|
|
|
+
|
|
|
+use App\Service\MyDomain\MyService;
|
|
|
+use PHPUnit\Framework\TestCase;
|
|
|
+use SomeDependency\Interface;
|
|
|
+use PHPUnit\Framework\MockObject\MockObject;
|
|
|
+
|
|
|
+// Create testable class for protected methods access
|
|
|
+class TestableMyService extends MyService
|
|
|
+{
|
|
|
+ public function protectedMethod(): mixed
|
|
|
+ {
|
|
|
+ return parent::protectedMethod();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class MyServiceTest extends TestCase
|
|
|
+{
|
|
|
+ private Interface|MockObject $dependency;
|
|
|
+
|
|
|
+ public function setUp(): void
|
|
|
+ {
|
|
|
+ // Mock all dependencies in setUp
|
|
|
+ $this->dependency = $this->getMockBuilder(Interface::class)
|
|
|
+ ->disableOriginalConstructor()
|
|
|
+ ->getMock();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getMyServiceMockFor(string $methodName): TestableMyService|MockObject
|
|
|
+ {
|
|
|
+ return $this->getMockBuilder(TestableMyService::class)
|
|
|
+ ->setConstructorArgs([$this->dependency])
|
|
|
+ ->setMethodsExcept([$methodName])
|
|
|
+ ->getMock();
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### Mocking Strategy
|
|
|
+- **Use `setMethodsExcept()`** to mock all methods except the one being tested (ignore deprecation warnings)
|
|
|
+- **Create a dedicated mock factory method** following the pattern `getMyClassMockFor(string $methodName)`
|
|
|
+- **Mock factory method should:**
|
|
|
+ - Create the SUT mock using `getMockBuilder()`
|
|
|
+ - Inject dependency mocks (created in `setUp`)
|
|
|
+ - Define methods to exclude from mocking via `setMethodsExcept()`
|
|
|
+ - Return the configured mock object
|
|
|
+
|
|
|
+#### Method Mocking Rules
|
|
|
+- **Only mock methods that are expected to be called** during test execution
|
|
|
+- **Exception:** Use `expects(self::never())` when verifying a method should NOT be called
|
|
|
+- **Mock each method called** in the currently tested method, including SUT methods
|
|
|
+- **Do not mock methods** that are not involved in the test scenario
|
|
|
+
|
|
|
+Example:
|
|
|
+```php
|
|
|
+public function testClearSiteCache(): void
|
|
|
+{
|
|
|
+ $typo3Service = $this->getTypo3ServiceMockFor('clearSiteCache');
|
|
|
+
|
|
|
+ $response = $this->getMockBuilder(ResponseInterface::class)
|
|
|
+ ->disableOriginalConstructor()
|
|
|
+ ->getMock();
|
|
|
+
|
|
|
+ $typo3Service->expects(self::once())
|
|
|
+ ->method('sendCommand')
|
|
|
+ ->with('/otadmin/site/clear-cache', ['organization-id' => 1])
|
|
|
+ ->willReturn($response);
|
|
|
+
|
|
|
+ $typo3Service->clearSiteCache(1);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### Visibility Issues
|
|
|
+- **For protected methods:** Create an intermediate testable class (see `TestableTypo3Service` example)
|
|
|
+- **For private methods in SUT:** Change visibility to protected to enable testing
|
|
|
+- **Testable class pattern:**
|
|
|
+
|
|
|
+```php
|
|
|
+class TestableMyService extends MyService
|
|
|
+{
|
|
|
+ public function protectedMethodToTest(string $param): ResponseInterface
|
|
|
+ {
|
|
|
+ return parent::protectedMethodToTest($param);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### Test Naming Convention
|
|
|
+- **Primary test** covering most common usage: `testMethod` (where "Method" is the actual method name)
|
|
|
+- **Variant tests** append specific situation:
|
|
|
+ - `testMethodWhenNoInput`
|
|
|
+ - `testMethodWithInvalidContext`
|
|
|
+ - `testMethodWithSpecialCondition`
|
|
|
+
|
|
|
+#### Test Documentation
|
|
|
+- **Use @see annotations** to reference the tested method:
|
|
|
+```php
|
|
|
+/**
|
|
|
+ * @see MyService::clearSiteCache()
|
|
|
+ */
|
|
|
+public function testClearSiteCache(): void
|
|
|
+```
|
|
|
+
|
|
|
+#### Test Structure Best Practices
|
|
|
+- **One test per execution branch/path**
|
|
|
+- **Use assertions** when the method should return a result
|
|
|
+- **Use expectations** when the method should call other methods
|
|
|
+- **Use `expects(self::never())`** when verifying a method should NOT be called
|
|
|
+- **Cover edge cases and error scenarios**
|
|
|
+- **Test method variants** (e.g., with/without optional parameters)
|
|
|
+
|
|
|
+#### Dependencies and Isolation
|
|
|
+- **All dependencies must be mocked** in `setUp` method
|
|
|
+- **Tests must be isolated and independent**
|
|
|
+- **Use `disableOriginalConstructor()`** for mock builders
|
|
|
+- **Mock return values** using `willReturn()` for expected behaviors
|
|
|
+
|
|
|
+#### Example Test Patterns
|
|
|
+
|
|
|
+**Simple method expectation:**
|
|
|
+```php
|
|
|
+public function testCreateSite(): void
|
|
|
+{
|
|
|
+ $typo3Service = $this->getTypo3ServiceMockFor('createSite');
|
|
|
+
|
|
|
+ $response = $this->getMockBuilder(ResponseInterface::class)
|
|
|
+ ->disableOriginalConstructor()->getMock();
|
|
|
+
|
|
|
+ $typo3Service->expects(self::once())
|
|
|
+ ->method('sendCommand')
|
|
|
+ ->with('/otadmin/site/create', ['organization-id' => 1])
|
|
|
+ ->willReturn($response);
|
|
|
+
|
|
|
+ $typo3Service->createSite(1);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**Method with conditional parameters:**
|
|
|
+```php
|
|
|
+public function testSetSiteDomainWithRedirection(): void
|
|
|
+{
|
|
|
+ $typo3Service = $this->getTypo3ServiceMockFor('setSiteDomain');
|
|
|
+
|
|
|
+ $response = $this->getMockBuilder(ResponseInterface::class)
|
|
|
+ ->disableOriginalConstructor()->getMock();
|
|
|
+
|
|
|
+ $typo3Service->expects(self::once())
|
|
|
+ ->method('sendCommand')
|
|
|
+ ->with('/otadmin/site/set-domain', [
|
|
|
+ 'organization-id' => 1,
|
|
|
+ 'domain' => 'new-domain',
|
|
|
+ 'redirect' => 1
|
|
|
+ ])
|
|
|
+ ->willReturn($response);
|
|
|
+
|
|
|
+ $typo3Service->setSiteDomain(1, 'new-domain', true);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### Running Tests
|
|
|
+```bash
|
|
|
+# Run all unit tests
|
|
|
+docker exec ap2i php -d memory_limit=-1 vendor/phpunit/phpunit/phpunit --testsuite unit --configuration phpunit.xml.dist
|
|
|
+
|
|
|
+# Run application tests (adjust testsuite as needed)
|
|
|
+docker exec ap2i vendor/bin/phpunit --configuration phpunit.xml.dist
|
|
|
+
|
|
|
+# Run specific test class
|
|
|
+docker exec ap2i vendor/bin/phpunit tests/Unit/Service/MyDomain/MyServiceTest.php
|
|
|
+
|
|
|
+# Run with coverage (if configured)
|
|
|
+docker exec ap2i vendor/bin/phpunit --coverage-html coverage/
|
|
|
+```
|
|
|
+
|
|
|
+### Code Quality
|
|
|
+The project enforces strict code quality standards:
|
|
|
+
|
|
|
+#### PHPStan (Static Analysis)
|
|
|
+```bash
|
|
|
+# Run analysis
|
|
|
+docker exec ap2i vendor/bin/phpstan analyse
|
|
|
+
|
|
|
+# Clear cache if needed
|
|
|
+docker exec ap2i vendor/bin/phpstan clear-result-cache
|
|
|
+```
|
|
|
+
|
|
|
+#### PHP CS Fixer (Code Style)
|
|
|
+```bash
|
|
|
+# Check code style
|
|
|
+docker exec ap2i php vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php
|
|
|
+
|
|
|
+# Fix code style automatically
|
|
|
+docker exec ap2i php vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php
|
|
|
+```
|
|
|
+
|
|
|
+### Messenger (Async Processing)
|
|
|
+The application uses Symfony Messenger for async processing:
|
|
|
+
|
|
|
+```bash
|
|
|
+# Start consuming messages
|
|
|
+docker exec ap2i php bin/console messenger:consume async
|
|
|
+
|
|
|
+# Setup transports if needed
|
|
|
+docker exec ap2i php bin/console messenger:setup-transports
|
|
|
+```
|
|
|
+
|
|
|
+### Build Process
|
|
|
+- No special build process required beyond standard Symfony practices
|
|
|
+- Run `docker exec ap2i composer install` for dependencies
|
|
|
+- Run database migrations: `docker exec ap2i php bin/console doctrine:migrations:migrate`
|
|
|
+- Clear cache: `docker exec ap2i php bin/console cache:clear`
|
|
|
+
|
|
|
+### Code Style Guidelines
|
|
|
+- Follow PSR-12 coding standards (enforced by PHP CS Fixer)
|
|
|
+- Use strict typing where possible (PHP 8.2+ features encouraged)
|
|
|
+- Follow Symfony best practices for service organization
|
|
|
+- Organize code by business domain
|
|
|
+- Write comprehensive tests for all business logic
|
|
|
+- Use PHPStan level checks for type safety
|
|
|
+
|
|
|
+### Important Notes
|
|
|
+- This is a proprietary project (license: proprietary)
|
|
|
+- Uses custom OpenTalent packages and private GitLab repositories
|
|
|
+- Requires PHP 8.2 minimum
|
|
|
+- Database changes must be done via Doctrine migrations
|
|
|
+- Always run the full test suite before submitting changes
|