From c9d37b218e9876986931c563a3cac54fe31a958f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:51:45 +0000 Subject: [PATCH 1/5] Initial plan From 853200d8f1198192a8a895b254f83f5bec7e5833 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:57:43 +0000 Subject: [PATCH 2/5] Add cookbook recipe for replicating Yii2 advanced app structure with Yii3 Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/.vitepress/config.js | 1 + src/cookbook/index.md | 1 + ...replicating-yii2-advanced-app-structure.md | 1059 +++++++++++++++++ 3 files changed, 1061 insertions(+) create mode 100644 src/cookbook/replicating-yii2-advanced-app-structure.md diff --git a/src/.vitepress/config.js b/src/.vitepress/config.js index 1cd9fd08..b50caa10 100644 --- a/src/.vitepress/config.js +++ b/src/.vitepress/config.js @@ -157,6 +157,7 @@ export default { text: 'Cookbook', items: [ {text: 'Preface', link: '/cookbook/preface'}, + {text: 'Replicating Yii2 Advanced App Structure', link: '/cookbook/replicating-yii2-advanced-app-structure'}, {text: 'Making HTTP Requests', link: '/cookbook/making-http-requests'}, {text: 'Disabling CSRF Protection', link: '/cookbook/disabling-csrf-protection'}, {text: 'Sentry Integration', link: '/cookbook/sentry-integration'} diff --git a/src/cookbook/index.md b/src/cookbook/index.md index a16f5e03..1663c154 100644 --- a/src/cookbook/index.md +++ b/src/cookbook/index.md @@ -13,6 +13,7 @@ This book conforms to the [Terms of Yii Documentation](https://www.yiiframework. --- - [Preface](preface.md) +- [Replicating Yii2 advanced app structure](replicating-yii2-advanced-app-structure.md) - [Structuring code by use-case with vertical slices](organizing-code/structuring-by-use-case-with-vertical-slices.md) - [Making HTTP requests](making-http-requests.md) - [Disabling CSRF protection](disabling-csrf-protection.md) diff --git a/src/cookbook/replicating-yii2-advanced-app-structure.md b/src/cookbook/replicating-yii2-advanced-app-structure.md new file mode 100644 index 00000000..74d2e858 --- /dev/null +++ b/src/cookbook/replicating-yii2-advanced-app-structure.md @@ -0,0 +1,1059 @@ +# Replicating Yii2 advanced app structure with Yii3 + +In Yii2, the [advanced application template](https://github.com/yiisoft/yii2-app-advanced) provided a structure for applications that require multiple entry points, such as frontend, backend, console, and API applications. This recipe explains how to achieve a similar structure in Yii3. + +## Understanding the differences + +Yii3 takes a fundamentally different approach to application structure compared to Yii2: + +### Yii2 advanced template structure + +The Yii2 advanced template was organized around multiple application directories: + +``` +yii2-app-advanced/ +├── backend/ # Administration panel application +├── frontend/ # Public-facing application +├── console/ # Console commands +├── api/ # API application (optional) +├── common/ # Shared code between applications +│ ├── models/ +│ ├── config/ +│ └── mail/ +├── environments/ # Environment-specific configurations +└── vendor/ +``` + +### Yii3 approach + +Yii3 uses a package-based architecture with a single application that can have multiple entry points: + +``` +yii3-app/ +├── config/ # Configuration files +│ ├── common/ # Shared configuration +│ ├── web/ # Web application config +│ └── console/ # Console application config +├── public/ # Web root with entry scripts +│ └── index.php +├── src/ # Application source code +│ ├── Web/ # Web-specific code +│ ├── Console/ # Console-specific code +│ └── Shared/ # Shared code +├── resources/ # Views, assets, translations +└── vendor/ +``` + +Instead of separate applications, Yii3 encourages: + +1. **Single codebase** with different entry points +2. **Configuration-based separation** between web and console +3. **Route-based access control** for admin vs. public areas +4. **Middleware-based logic** for different application sections + +## Setting up multiple entry points + +### Creating separate entry scripts + +To replicate the frontend/backend separation, create multiple entry points in your `public/` directory: + +#### Frontend entry script + +Create `public/index.php` for your public-facing application: + +```php +run(); +``` + +#### Backend entry script + +Create `public/admin.php` for your administration panel: + +```php +run(); +``` + +### Configuring web server routing + +Configure your web server to route requests appropriately: + +#### Nginx configuration + +```nginx +server { + listen 80; + server_name example.com; + root /path/to/app/public; + index index.php; + + # Frontend application + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + # Backend application (admin panel) + location /admin { + try_files $uri $uri/ /admin.php$is_args$args; + } + + # PHP-FPM configuration + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } +} +``` + +## Organizing configuration + +### Configuration structure + +Create a configuration structure that separates concerns: + +``` +config/ +├── common/ # Shared between all entry points +│ ├── di/ # Dependency injection +│ │ ├── logger.php +│ │ ├── db.php +│ │ └── cache.php +│ ├── params.php # Common parameters +│ └── routes.php # Shared routes +├── web/ # Frontend-specific +│ ├── di/ +│ │ └── application.php +│ ├── params.php +│ └── routes.php +├── admin/ # Backend-specific +│ ├── di/ +│ │ └── application.php +│ ├── params.php +│ └── routes.php +├── console/ # Console commands +│ └── params.php +├── params.php # Root parameters +├── bootstrap.php # Application bootstrap +└── packages/ # Package configurations +``` + +### Configuring the config plugin + +Update `composer.json` to include admin configuration: + +```json +{ + "config-plugin": { + "common": "config/common/*.php", + "params": [ + "config/params.php", + "?config/params-local.php" + ], + "web": [ + "$common", + "config/web/*.php" + ], + "admin": [ + "$common", + "config/admin/*.php" + ], + "console": [ + "$common", + "config/console/*.php" + ], + "events": "config/events.php", + "events-web": [ + "$events", + "config/events-web.php" + ], + "events-admin": [ + "$events", + "config/events-admin.php" + ], + "providers": "config/providers.php", + "providers-web": [ + "$providers", + "config/providers-web.php" + ], + "providers-admin": [ + "$providers", + "config/providers-admin.php" + ], + "routes": "config/routes.php" + } +} +``` + +### Creating admin-specific configuration + +Create `config/admin/di/application.php`: + +```php + [ + '__construct()' => [ + 'dispatcher' => DynamicReference::to([ + 'class' => MiddlewareDispatcher::class, + 'withMiddlewares()' => [ + [ + ErrorCatcher::class, + SessionMiddleware::class, + CsrfTokenMiddleware::class, + Router::class, + ], + ], + ]), + ], + ], +]; +``` + +## Organizing source code + +### Directory structure + +Organize your source code to separate concerns while sharing common code: + +``` +src/ +├── Admin/ # Backend-specific code +│ ├── Controller/ +│ │ ├── DashboardController.php +│ │ └── UserController.php +│ ├── Service/ +│ │ └── UserManagementService.php +│ └── View/ +│ └── layout.php +├── Frontend/ # Frontend-specific code +│ ├── Controller/ +│ │ ├── SiteController.php +│ │ └── PostController.php +│ ├── Service/ +│ │ └── PostService.php +│ └── View/ +│ └── layout.php +├── Console/ # Console commands +│ └── Command/ +│ └── MigrateCommand.php +├── Shared/ # Shared code +│ ├── Entity/ +│ │ ├── User.php +│ │ └── Post.php +│ ├── Repository/ +│ │ ├── UserRepository.php +│ │ └── PostRepository.php +│ ├── Service/ +│ │ └── AuthService.php +│ └── ValueObject/ +└── Api/ # API-specific code (optional) + ├── Controller/ + └── Dto/ +``` + +### Example: Shared entity + +Create shared entities in `src/Shared/Entity/`: + +```php +id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getRole(): string + { + return $this->role; + } + + public function isActive(): bool + { + return $this->status === 1; + } +} +``` + +### Example: Admin controller + +Create admin-specific controllers in `src/Admin/Controller/`: + +```php +userRepository->count(); + + return $this->responseFactory->createResponse([ + 'totalUsers' => $totalUsers, + ]); + } +} +``` + +### Example: Frontend controller + +Create frontend-specific controllers in `src/Frontend/Controller/`: + +```php +responseFactory->createResponse([ + 'title' => 'Welcome to Frontend', + ]); + } +} +``` + +## Setting up routes + +### Separate route configuration + +Create route configurations for each application section: + +#### Frontend routes + +Create `config/web/routes.php`: + +```php +action([SiteController::class, 'index']) + ->name('site/index'), + + Route::get('/post/{id:\d+}') + ->action([PostController::class, 'view']) + ->name('post/view'), + + Route::get('/posts') + ->action([PostController::class, 'index']) + ->name('post/index'), +]; +``` + +#### Backend routes + +Create `config/admin/routes.php`: + +```php +action([DashboardController::class, 'index']) + ->name('admin/dashboard'), + + Group::create('/users') + ->routes( + Route::get('/') + ->action([UserController::class, 'index']) + ->name('admin/user/index'), + + Route::get('/{id:\d+}') + ->action([UserController::class, 'view']) + ->name('admin/user/view'), + + Route::post('/{id:\d+}/edit') + ->action([UserController::class, 'update']) + ->name('admin/user/update'), + ), +]; +``` + +## Implementing access control + +### Creating authentication middleware + +Create admin-specific authentication middleware: + +```php +currentUser->isGuest() && $this->currentUser->getIdentity()->getRole() === 'admin') { + return $handler->handle($request); + } + + return $this->responseFactory + ->createResponse(403) + ->withHeader('Location', '/login'); + } +} +``` + +### Applying middleware to admin routes + +Update `config/admin/di/application.php` to include the authentication middleware: + +```php + [ + '__construct()' => [ + 'dispatcher' => DynamicReference::to([ + 'class' => MiddlewareDispatcher::class, + 'withMiddlewares()' => [ + [ + ErrorCatcher::class, + SessionMiddleware::class, + CsrfTokenMiddleware::class, + AdminAuthMiddleware::class, // Admin authentication + Router::class, + ], + ], + ]), + ], + ], +]; +``` + +## Managing shared resources + +### Shared database connections + +Configure database connections in `config/common/di/db.php` to be shared across all applications: + +```php + [ + 'class' => Connection::class, + '__construct()' => [ + 'driver' => new Driver( + $params['yiisoft/db-mysql']['dsn'], + $params['yiisoft/db-mysql']['username'], + $params['yiisoft/db-mysql']['password'], + ), + ], + ], +]; +``` + +### Shared services + +Create shared services in `src/Shared/Service/`: + +```php +userRepository->findByUsername($username); + + if ($user === null) { + return null; + } + + if (!password_verify($password, $user->getPasswordHash())) { + return null; + } + + return $user; + } +} +``` + +## Environment-specific configuration + +### Using environment variables + +Create `.env` files for different environments: + +#### Development environment + +Create `.env.dev`: + +```env +APP_ENV=dev +DB_HOST=localhost +DB_NAME=app_dev +DB_USER=dev_user +DB_PASSWORD=dev_password +``` + +#### Production environment + +Create `.env.prod`: + +```env +APP_ENV=prod +DB_HOST=production-db.example.com +DB_NAME=app_prod +DB_USER=prod_user +DB_PASSWORD=secure_password +``` + +### Loading environment variables + +Use [vlucas/phpdotenv](https://github.com/vlucas/phpdotenv) to load environment variables: + +```sh +composer require vlucas/phpdotenv +``` + +Update your entry scripts to load the appropriate environment file: + +```php +load(); + +// Define constants from environment +define('YII_ENV', $_ENV['APP_ENV'] ?? 'prod'); +define('YII_DEBUG', YII_ENV !== 'prod'); + +// Continue with application runner... +``` + +### Environment-specific parameters + +Create environment-specific parameter files: + +```php + [ + 'name' => $_ENV['APP_NAME'] ?? 'My Application', + 'charset' => 'UTF-8', + ], + + 'yiisoft/db-mysql' => [ + 'dsn' => sprintf( + 'mysql:host=%s;dbname=%s', + $_ENV['DB_HOST'] ?? 'localhost', + $_ENV['DB_NAME'] ?? 'app' + ), + 'username' => $_ENV['DB_USER'] ?? 'root', + 'password' => $_ENV['DB_PASSWORD'] ?? '', + ], +]; +``` + +## Handling assets and views + +### Separate view paths + +Configure separate view paths for frontend and backend in parameters: + +```php + [ + 'basePath' => '@root/resources/views/frontend', + ], +]; +``` + +```php + [ + 'basePath' => '@root/resources/views/admin', + ], +]; +``` + +### View directory structure + +Organize views by application: + +``` +resources/ +├── views/ +│ ├── frontend/ +│ │ ├── layout/ +│ │ │ ├── main.php +│ │ │ └── guest.php +│ │ ├── site/ +│ │ │ ├── index.php +│ │ │ └── about.php +│ │ └── post/ +│ │ ├── index.php +│ │ └── view.php +│ └── admin/ +│ ├── layout/ +│ │ └── main.php +│ ├── dashboard/ +│ │ └── index.php +│ └── user/ +│ ├── index.php +│ └── edit.php +└── assets/ + ├── frontend/ + │ ├── css/ + │ └── js/ + └── admin/ + ├── css/ + └── js/ +``` + +## API application + +### Creating an API entry point + +For RESTful APIs, create `public/api.php`: + +```php +run(); +``` + +### API configuration + +Create `config/api/di/application.php`: + +```php + [ + '__construct()' => [ + 'dispatcher' => DynamicReference::to([ + 'class' => MiddlewareDispatcher::class, + 'withMiddlewares()' => [ + [ + ErrorCatcher::class, + Router::class, + // Note: No CSRF for API, use token authentication instead + ], + ], + ]), + ], + ], +]; +``` + +### API routes + +Create RESTful routes in `config/api/routes.php`: + +```php +routes( + Group::create('/users') + ->routes( + Route::get('/') + ->action([UserController::class, 'index']) + ->name('api/user/index'), + + Route::get('/{id:\d+}') + ->action([UserController::class, 'view']) + ->name('api/user/view'), + + Route::post('/') + ->action([UserController::class, 'create']) + ->name('api/user/create'), + + Route::put('/{id:\d+}') + ->action([UserController::class, 'update']) + ->name('api/user/update'), + + Route::delete('/{id:\d+}') + ->action([UserController::class, 'delete']) + ->name('api/user/delete'), + ), + + Group::create('/posts') + ->routes( + Route::get('/') + ->action([PostController::class, 'index']) + ->name('api/post/index'), + + Route::get('/{id:\d+}') + ->action([PostController::class, 'view']) + ->name('api/post/view'), + ), + ), +]; +``` + +## Testing considerations + +### Separate test configurations + +Create test configurations for each application section: + +``` +tests/ +├── Admin/ +│ └── Controller/ +│ └── DashboardControllerTest.php +├── Frontend/ +│ └── Controller/ +│ └── SiteControllerTest.php +├── Api/ +│ └── Controller/ +│ └── UserControllerTest.php +└── Shared/ + ├── Entity/ + └── Service/ +``` + +### Example test + +```php +createMock(UserRepository::class); + $userRepository->method('count')->willReturn(42); + + $controller = new DashboardController( + new DataResponseFactory(), + $userRepository + ); + + $response = $controller->index( + $this->createMock(\Psr\Http\Message\ServerRequestInterface::class) + ); + + $this->assertSame(200, $response->getStatusCode()); + } +} +``` + +## Migration from Yii2 + +### Key differences to consider + +When migrating from Yii2 advanced template to Yii3: + +1. **No separate application classes**: Yii3 uses a single application with different entry points +2. **Configuration-driven**: Separation is achieved through configuration rather than directory structure +3. **PSR compliance**: All components follow PSR standards (PSR-7, PSR-11, PSR-15, etc.) +4. **Dependency injection**: Everything is configured through the DI container +5. **No global state**: No `Yii::$app` or similar global accessors + +### Migration steps + +1. **Identify shared code**: Move common models, services, and repositories to `src/Shared/` +2. **Separate application logic**: Organize frontend and backend code in respective directories +3. **Configure entry points**: Create separate entry scripts for each application +4. **Update routing**: Convert Yii2 routes to Yii3 route configuration +5. **Refactor controllers**: Update controllers to use PSR-7 request/response +6. **Update views**: Adapt view rendering to Yii3 view system +7. **Configure middleware**: Set up middleware stacks for each application +8. **Test thoroughly**: Ensure all functionality works in the new structure + +## Best practices + +1. **Keep shared code truly shared**: Only place code in `src/Shared/` if it's genuinely used by multiple applications +2. **Use middleware for separation**: Apply application-specific middleware instead of duplicating logic +3. **Leverage configuration**: Use the config plugin to manage environment and application-specific settings +4. **Follow PSR standards**: Ensure all code follows PSR interfaces for better interoperability +5. **Document entry points**: Clearly document each entry script and its purpose +6. **Use environment variables**: Externalize configuration that varies between environments +7. **Implement proper access control**: Use middleware and RBAC for securing admin areas +8. **Test each application separately**: Write tests that verify each application section works independently + +## Conclusion + +While Yii3 doesn't have a direct equivalent to the Yii2 advanced template, you can achieve the same level of separation and organization through: + +- Multiple entry scripts with different configuration sets +- Organized source code structure separating frontend, backend, API, and shared code +- Configuration-based application setup +- Middleware-based access control and request handling + +This approach provides more flexibility and follows modern PHP practices while maintaining clear separation between different parts of your application. + +## References + +- [Application structure overview](../guide/structure/overview.md) +- [Configuration](../guide/concept/configuration.md) +- [Middleware](../guide/structure/middleware.md) +- [Routing](../guide/runtime/routing.md) +- [Structuring code by use-case with vertical slices](organizing-code/structuring-by-use-case-with-vertical-slices.md) From 2041018e62675d39a7ffe2b568eea518df796b6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:00:33 +0000 Subject: [PATCH 3/5] Fix code review issues: add missing methods and import statements Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- .../replicating-yii2-advanced-app-structure.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cookbook/replicating-yii2-advanced-app-structure.md b/src/cookbook/replicating-yii2-advanced-app-structure.md index 74d2e858..8bc28ae6 100644 --- a/src/cookbook/replicating-yii2-advanced-app-structure.md +++ b/src/cookbook/replicating-yii2-advanced-app-structure.md @@ -373,6 +373,11 @@ final class User return $this->role; } + public function getPasswordHash(): string + { + return $this->passwordHash; + } + public function isActive(): bool { return $this->status === 1; @@ -549,7 +554,9 @@ final class AdminAuthMiddleware implements MiddlewareInterface ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { - if (!$this->currentUser->isGuest() && $this->currentUser->getIdentity()->getRole() === 'admin') { + $identity = $this->currentUser->getIdentity(); + + if (!$this->currentUser->isGuest() && $identity !== null && $identity->getRole() === 'admin') { return $handler->handle($request); } @@ -571,8 +578,12 @@ declare(strict_types=1); use App\Admin\Middleware\AdminAuthMiddleware; use Yiisoft\Definitions\DynamicReference; +use Yiisoft\ErrorHandler\Middleware\ErrorCatcher; use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher; +use Yiisoft\Router\Middleware\Router; +use Yiisoft\Session\SessionMiddleware; use Yiisoft\Yii\Http\Application; +use Yiisoft\Yii\Middleware\CsrfTokenMiddleware; /** @var array $params */ @@ -871,7 +882,9 @@ Create `config/api/di/application.php`: declare(strict_types=1); use Yiisoft\Definitions\DynamicReference; +use Yiisoft\ErrorHandler\Middleware\ErrorCatcher; use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher; +use Yiisoft\Router\Middleware\Router; use Yiisoft\Yii\Http\Application; /** @var array $params */ From 01ead27da82b3edc1bd0289ad24701549ad533de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:02:15 +0000 Subject: [PATCH 4/5] Fix HTTP status code for authentication redirect Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/cookbook/replicating-yii2-advanced-app-structure.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cookbook/replicating-yii2-advanced-app-structure.md b/src/cookbook/replicating-yii2-advanced-app-structure.md index 8bc28ae6..c50477b7 100644 --- a/src/cookbook/replicating-yii2-advanced-app-structure.md +++ b/src/cookbook/replicating-yii2-advanced-app-structure.md @@ -561,7 +561,7 @@ final class AdminAuthMiddleware implements MiddlewareInterface } return $this->responseFactory - ->createResponse(403) + ->createResponse(302) ->withHeader('Location', '/login'); } } From 94c6d81af69658a66326cfb17bd19bfc8b7d831d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 10:39:10 +0000 Subject: [PATCH 5/5] Refocus article on implementing structure rather than migration/comparison Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/.vitepress/config.js | 2 +- src/cookbook/index.md | 2 +- ...replicating-yii2-advanced-app-structure.md | 89 ++++++------------- 3 files changed, 30 insertions(+), 63 deletions(-) diff --git a/src/.vitepress/config.js b/src/.vitepress/config.js index b50caa10..49541f51 100644 --- a/src/.vitepress/config.js +++ b/src/.vitepress/config.js @@ -157,7 +157,7 @@ export default { text: 'Cookbook', items: [ {text: 'Preface', link: '/cookbook/preface'}, - {text: 'Replicating Yii2 Advanced App Structure', link: '/cookbook/replicating-yii2-advanced-app-structure'}, + {text: 'Implementing Advanced App Structure', link: '/cookbook/replicating-yii2-advanced-app-structure'}, {text: 'Making HTTP Requests', link: '/cookbook/making-http-requests'}, {text: 'Disabling CSRF Protection', link: '/cookbook/disabling-csrf-protection'}, {text: 'Sentry Integration', link: '/cookbook/sentry-integration'} diff --git a/src/cookbook/index.md b/src/cookbook/index.md index 1663c154..ae970664 100644 --- a/src/cookbook/index.md +++ b/src/cookbook/index.md @@ -13,7 +13,7 @@ This book conforms to the [Terms of Yii Documentation](https://www.yiiframework. --- - [Preface](preface.md) -- [Replicating Yii2 advanced app structure](replicating-yii2-advanced-app-structure.md) +- [Implementing advanced app structure](replicating-yii2-advanced-app-structure.md) - [Structuring code by use-case with vertical slices](organizing-code/structuring-by-use-case-with-vertical-slices.md) - [Making HTTP requests](making-http-requests.md) - [Disabling CSRF protection](disabling-csrf-protection.md) diff --git a/src/cookbook/replicating-yii2-advanced-app-structure.md b/src/cookbook/replicating-yii2-advanced-app-structure.md index c50477b7..632107cd 100644 --- a/src/cookbook/replicating-yii2-advanced-app-structure.md +++ b/src/cookbook/replicating-yii2-advanced-app-structure.md @@ -1,55 +1,45 @@ -# Replicating Yii2 advanced app structure with Yii3 +# Implementing advanced app structure with Yii3 -In Yii2, the [advanced application template](https://github.com/yiisoft/yii2-app-advanced) provided a structure for applications that require multiple entry points, such as frontend, backend, console, and API applications. This recipe explains how to achieve a similar structure in Yii3. +This recipe shows how to structure a Yii3 application with multiple entry points for frontend, backend, console, and API applications, similar to the advanced application template pattern. -## Understanding the differences +## Application structure -Yii3 takes a fundamentally different approach to application structure compared to Yii2: - -### Yii2 advanced template structure - -The Yii2 advanced template was organized around multiple application directories: - -``` -yii2-app-advanced/ -├── backend/ # Administration panel application -├── frontend/ # Public-facing application -├── console/ # Console commands -├── api/ # API application (optional) -├── common/ # Shared code between applications -│ ├── models/ -│ ├── config/ -│ └── mail/ -├── environments/ # Environment-specific configurations -└── vendor/ -``` - -### Yii3 approach - -Yii3 uses a package-based architecture with a single application that can have multiple entry points: +The structure uses multiple entry points with separate configurations for each application section: ``` yii3-app/ ├── config/ # Configuration files │ ├── common/ # Shared configuration -│ ├── web/ # Web application config +│ ├── web/ # Frontend application config +│ ├── admin/ # Backend application config +│ ├── api/ # API application config │ └── console/ # Console application config ├── public/ # Web root with entry scripts -│ └── index.php +│ ├── index.php # Frontend entry point +│ ├── admin.php # Backend entry point +│ └── api.php # API entry point ├── src/ # Application source code -│ ├── Web/ # Web-specific code +│ ├── Frontend/ # Frontend-specific code +│ ├── Admin/ # Backend-specific code +│ ├── Api/ # API-specific code │ ├── Console/ # Console-specific code -│ └── Shared/ # Shared code +│ └── Shared/ # Shared code between applications ├── resources/ # Views, assets, translations +│ ├── views/ +│ │ ├── frontend/ +│ │ └── admin/ +│ └── assets/ +│ ├── frontend/ +│ └── admin/ └── vendor/ ``` -Instead of separate applications, Yii3 encourages: +This structure provides: -1. **Single codebase** with different entry points -2. **Configuration-based separation** between web and console -3. **Route-based access control** for admin vs. public areas -4. **Middleware-based logic** for different application sections +1. **Multiple entry points** - Separate entry scripts for each application +2. **Configuration-based separation** - Each application has its own config +3. **Shared code organization** - Common code in a dedicated namespace +4. **Middleware-based logic** - Different middleware stacks per application ## Setting up multiple entry points @@ -1018,29 +1008,6 @@ final class DashboardControllerTest extends TestCase } ``` -## Migration from Yii2 - -### Key differences to consider - -When migrating from Yii2 advanced template to Yii3: - -1. **No separate application classes**: Yii3 uses a single application with different entry points -2. **Configuration-driven**: Separation is achieved through configuration rather than directory structure -3. **PSR compliance**: All components follow PSR standards (PSR-7, PSR-11, PSR-15, etc.) -4. **Dependency injection**: Everything is configured through the DI container -5. **No global state**: No `Yii::$app` or similar global accessors - -### Migration steps - -1. **Identify shared code**: Move common models, services, and repositories to `src/Shared/` -2. **Separate application logic**: Organize frontend and backend code in respective directories -3. **Configure entry points**: Create separate entry scripts for each application -4. **Update routing**: Convert Yii2 routes to Yii3 route configuration -5. **Refactor controllers**: Update controllers to use PSR-7 request/response -6. **Update views**: Adapt view rendering to Yii3 view system -7. **Configure middleware**: Set up middleware stacks for each application -8. **Test thoroughly**: Ensure all functionality works in the new structure - ## Best practices 1. **Keep shared code truly shared**: Only place code in `src/Shared/` if it's genuinely used by multiple applications @@ -1052,16 +1019,16 @@ When migrating from Yii2 advanced template to Yii3: 7. **Implement proper access control**: Use middleware and RBAC for securing admin areas 8. **Test each application separately**: Write tests that verify each application section works independently -## Conclusion +## Summary -While Yii3 doesn't have a direct equivalent to the Yii2 advanced template, you can achieve the same level of separation and organization through: +This structure provides clear separation between different application sections while maintaining a single codebase: - Multiple entry scripts with different configuration sets - Organized source code structure separating frontend, backend, API, and shared code - Configuration-based application setup - Middleware-based access control and request handling -This approach provides more flexibility and follows modern PHP practices while maintaining clear separation between different parts of your application. +The pattern allows you to maintain separate concerns for different parts of your application while sharing common code and resources efficiently. ## References