diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3385e6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +composer.lock +vendor/ +.phpstan.cache/ +.phpunit.cache/ + +*.DS_Store +*.idea/ diff --git a/README.md b/README.md index 7808726..a414162 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,604 @@ -# skeleton-project -Project Template +# Cubex Framework Skeleton Project +A barebones showcase of the **Cubex Framework** - a modern PHP framework for building scalable web applications with clear separation between API, Frontend, and CLI layers. -##Generating Content +## What is Cubex? -Create your database and schema using test.sql +Cubex is a lightweight, modular PHP framework that emphasizes: +- **Subdomain-based routing** - Different subdomains route to different applications +- **Application separation** - API, Frontend, and CLI are completely separate concerns +- **Generator-based routing** - Modern PHP generators for efficient route handling +- **Context-driven architecture** - Request context flows through your entire application -To generate database content, run the following command +## Project Architecture - ./cubex GenerateContent - -You can optionally specify the number of content pages to generate, by specifying ``--quantity=N`` or ``-aN`` +```mermaid +graph TB + A[Incoming Request] --> B{DefaultApplication} + B -->|api.domain.com| C[ApiApplication] + B -->|www.domain.com| D[FrontendApplication] + B -->|CLI Command| E[CliApplication] - ./cubex GenerateContent -a4 + C --> F[API Controllers] + D --> G[Frontend Controllers] + E --> H[Console Commands] + + F --> I[(Database)] + G --> I + H --> I + + style C fill:#e1f5ff + style D fill:#fff4e1 + style E fill:#f0e1ff +``` + +The framework uses a **root application** (`DefaultApplication`) that routes requests to specialized sub-applications based on the subdomain or execution context. + +## Getting Started + +### Prerequisites +- PHP 8.0 or higher +- Composer +- SQLite (default) or MySQL/PostgreSQL + +### Installation + +```bash +# Install dependencies +composer install + +# Create the database +./cubex db:create + +# Generate sample content +./cubex db:generate --quantity=10 +``` + +## Project Structure + +``` +src/ +├── Api/ # API Layer +│ ├── ApiApplication.php # API app entry point +│ └── V1/ # Version 1 endpoints +│ ├── ApiVersionOne.php +│ └── Controllers/ +│ └── CatsController.php +├── Frontend/ # Frontend Layer +│ ├── FrontendApplication.php +│ ├── Homepage/ +│ │ └── HomepageController.php +│ ├── Content/ +│ │ ├── ContentController.php +│ │ ├── ContentModel.php +│ │ └── ContentView.php +│ └── Layout/ +│ ├── LayoutController.php +│ └── HtmlWrap.php +├── Cli/ # CLI Layer +│ ├── CliApplication.php +│ ├── CreateTable.php +│ └── GenerateContent.php +├── Storage/ # Data Models +│ └── ContentPage.php +└── DefaultApplication.php # Root router +``` + +--- + +# API Layer + +The API layer provides RESTful endpoints, typically accessed via the `api` subdomain. + +## How API Routing Works + +```mermaid +graph LR + A[api.domain.com/v1/cats] --> B[ApiApplication] + B --> C[ApiVersionOne] + C --> D[CatsController] + D --> E[getCats method] + E --> F[JSON Response] + + style A fill:#e1f5ff + style F fill:#c8e6c9 +``` + +### Example: Creating an API Endpoint + +**1. Define your data structure** (`src/Api/V1/Definitions/Cat.php`): +```php +class Cat +{ + public function __construct( + public string $name, + public string $breed + ) {} +} +``` + +**2. Create a controller** (`src/Api/V1/Controllers/CatsController.php`): +```php +class CatsController extends AbstractVersionOneApiController +{ + protected function _generateRoutes(): string + { + return 'cats'; // Routes to /v1/cats + } + + public function getCats(): array + { + return [ + 'items' => [ + new Cat("Molly", "Russian Blue"), + new Cat("Polly", "Scottish Fold"), + ], + ]; + } +} +``` + +**3. Register it** in `ApiVersionOne.php`: +```php +protected function _generateRoutes(): Generator +{ + yield self::_route('cats', CatsController::class); +} +``` + +### API Request Examples + +```bash +# Get cats list +curl http://api.localhost/v1/cats + +# Response: +{ + "items": [ + {"name": "Molly", "breed": "Russian Blue"}, + {"name": "Polly", "breed": "Scottish Fold"} + ] +} +``` + +### Key API Concepts + +- **Versioning**: APIs are versioned (`/v1/`, `/v2/`) for backward compatibility +- **Auto-serialization**: Return arrays or objects - Cubex handles JSON encoding +- **Method mapping**: `getCats()` automatically responds to GET requests +- **Type safety**: Use PHP 8 typed properties and return types + +--- + +# Frontend Layer + +The frontend layer serves HTML pages and handles user interfaces. + +## Frontend Request Flow + +```mermaid +sequenceDiagram + participant User + participant FrontendApp + participant Controller + participant Model + participant View + participant Database + + User->>FrontendApp: GET /content + FrontendApp->>Controller: Route to ContentController + Controller->>Model: Create ContentModel + Controller->>Database: Fetch ContentPage + Database-->>Controller: Return data + Controller->>Model: Populate model + Model->>View: Render ContentView + View-->>User: Return HTML +``` + +### Example: Creating a Frontend Page + +**1. Create a Model** (`src/Frontend/Content/ContentModel.php`): +```php +class ContentModel extends ViewModel +{ + public ?ContentPage $page = null; +} +``` + +**2. Create a Controller** (`src/Frontend/Content/ContentController.php`): +```php +class ContentController extends LayoutController +{ + protected function _generateRoutes(): string + { + return 'content'; // Routes to /content + } + + public function processContent(): ContentModel + { + $model = ContentModel::withContext($this); + + // Fetch latest page from database + $model->page = ContentPage::collection() + ->orderBy(['id' => 'desc']) + ->first(); + + return $model->setView(ContentView::class); + } +} +``` + +**3. Create a View** (`src/Frontend/Content/ContentView.php`): +```php +class ContentView extends View +{ + protected function _getContentForRender(): string + { + $page = $this->_model->page; + return Div::create( + H1::create($page->title), + P::create($page->content) + ); + } +} +``` + +**4. Register the route** in `FrontendApplication.php`: +```php +protected function _generateRoutes(): Generator +{ + yield self::_route("/content", ContentController::class); +} +``` + +### Frontend Routing Patterns + +```mermaid +graph TD + A[FrontendApplication] --> B[/_r/*] + A --> C[/content] + A --> D[/] + + B --> E[Static Assets
CSS, JS, Images] + C --> F[ContentController] + D --> G[HomepageController] + + F --> H[ContentView] + G --> I[Homepage Template] + + style B fill:#ffe1e1 + style E fill:#ffe1e1 + style C fill:#e1ffe1 + style D fill:#e1e1ff +``` + +### Key Frontend Concepts + +- **Layout inheritance**: Controllers extend `LayoutController` for consistent page structure +- **Asset management**: Static resources served via Dispatch at `/_r/` +- **Model-View separation**: Business logic in Models, presentation in Views +- **Context awareness**: Every component has access to request context + +--- + +# CLI Layer + +The CLI layer provides command-line tools for maintenance and data management. + +## CLI Command Flow + +```mermaid +graph LR + A[./cubex command] --> B[CliApplication] + B --> C[Console Command] + C --> D[Execute Logic] + D --> E[Database Operations] + E --> F[Output Results] + + style A fill:#f0e1ff + style F fill:#c8e6c9 +``` + +### Example: Database Seeding Command + +The project includes a content generator command: + +```php +// src/Cli/GenerateContent.php +class GenerateContent extends ConsoleCommand +{ + public int $quantity = 1; + + protected function configure(): void + { + $this->setName('db:generate'); + $this->setDescription('Seed the Content Page Table with content'); + + $this->addOption( + 'quantity', + 'a', + InputOption::VALUE_OPTIONAL, + 'How many seed items to generate', + 1 + ); + } + + protected function _execute(InputInterface $input, OutputInterface $output): int + { + for($i = 0; $i < $this->quantity; $i++) + { + $content = new ContentPage(); + $content->title = "Content Page " . Strings::randomString(10); + $content->content = "Random content..."; + $content->save(); + } + return 0; + } +} +``` + +### CLI Usage Examples + +```bash +# Create database table +./cubex db:create + +# Generate 1 content page (default) +./cubex db:generate + +# Generate 5 content pages +./cubex db:generate --quantity=5 +./cubex db:generate -a5 + +# List available commands +./cubex list + +# Php localhost server +./cubex serve +``` + +### Key CLI Concepts + +- **Symfony Console**: Built on Symfony Console component +- **Auto-registration**: Commands registered in `CliApplication::launch()` +- **Short flags**: Use `@short` annotation for abbreviated options +- **Database access**: Full DAL access in CLI context + +--- + +# Connection Management + +Cubex uses a **DalResolver** pattern for database connections. + +## Connection Configuration Flow + +```mermaid +graph TD + A[SkeletonApplication] --> B[Load Connection Config] + B --> C[conf/defaults/connections.ini] + B --> D[conf/local/connections.ini] + + C --> E[DalResolver] + D --> E + + E --> F{Connection Type?} + F -->|SQLite| G[Local File Database] + F -->|MySQL| H[Remote Database] + F -->|PostgreSQL| I[Remote Database] + + G --> J[Application Ready] + H --> J + I --> J + + style C fill:#e1f5ff + style D fill:#fff4e1 + style E fill:#f0e1ff +``` + +### Configuration Files + +**Default configuration** (`conf/defaults/connections.ini`): +```ini +[cubex_skeleton] +construct_class = \Packaged\Dal\Ql\PdoConnection +; Other connection settings... +``` + +**Local override** (`conf/local/connections.ini`): +```ini +; Override defaults for local development +[cubex_skeleton] +host = localhost +username = root +password = secret +``` + +### Database Setup + +1. **SQLite** (Default - no setup needed): + ```bash + ./cubex db:create + ``` + Creates `database/database.sqlite` + +2. **MySQL**: + ```bash + # Update conf/local/connections.ini + ./cubex db:create + ``` + +--- + +# Routing System + +Cubex uses **Generator-based routing** for memory efficiency and flexibility. + +## Subdomain Routing + +```mermaid +graph TB + A[Request: api.example.com/v1/cats] --> B{DefaultApplication
Subdomain Check} + + B -->|api| C[ApiApplication] + B -->|www or empty| D[FrontendApplication] + + C --> E[V1 Routes] + E --> F[CatsController] + + D --> G[Frontend Routes] + G --> H[ContentController] + + style B fill:#ffe1e1 + style C fill:#e1f5ff + style D fill:#fff4e1 +``` + +### How Routing Works + +**In `DefaultApplication.php`**: +```php +protected function _generateRoutes() +{ + // API subdomain routes to ApiApplication + yield self::_route( + '/', + new LazyHandler(function (Context $c) { + return ApiApplication::withContext($c, $c->getCubex()); + }) + )->add(RequestCondition::i()->subDomain('api')); + + // Frontend routes + yield self::_route('/', $frontendHandler) + ->add(RequestCondition::i()->subDomain('www')); +} +``` + +### Route Generators + +Each application defines routes using PHP generators: + +```php +protected function _generateRoutes(): Generator +{ + // Static route + yield self::_route('/home', HomepageController::class); + + // Parameterized route + yield self::_route('/user/{id}', UserController::class); + + // Conditional route + yield self::_route('/admin', AdminController::class) + ->add(AuthCondition::i()); +} +``` + +**Why generators?** They're evaluated lazily, so only matched routes consume memory. + +--- + +# Development Workflow + +## Local Development + +```bash +# Start PHP built-in server +php -S localhost:8000 -t public/ + +# With subdomain support (requires /etc/hosts setup) +# Add to /etc/hosts: +# 127.0.0.1 localhost api.localhost www.localhost + +# Access points: +# Frontend: http://localhost:8000 +# API: http://api.localhost:8000/v1/cats +``` + +## Testing + +```bash +# Run PHPUnit tests +composer test + +# Run PHPStan static analysis +composer analyse +``` + +## Project Structure Best Practices + +1. **Keep applications separate**: API, Frontend, and CLI should not share controllers +2. **Use Models for business logic**: Keep controllers thin +3. **Version your APIs**: Always use `/v1/`, `/v2/` prefixes +4. **Leverage context**: Pass context through `withContext()` for testability + +--- + +# Quick Reference + +## Common Operations + +| Task | Command | +|----------------------|------------------------------------| +| Install dependencies | `composer install` | +| Create database | `./cubex db:create` | +| Seed data | `./cubex db:generate -a10` | +| Run tests | `composer test` | +| Static analysis | `composer analyse` | +| Start dev server | `php -S localhost:8000 -t public/` | + +## Key Directories + +| Directory | Purpose | +|-----------------|--------------------------| +| `src/Api/` | REST API endpoints | +| `src/Frontend/` | Web pages and UI | +| `src/Cli/` | Command-line tools | +| `src/Storage/` | Data models and entities | +| `conf/` | Configuration files | +| `public/` | Web root (index.php) | +| `tests/` | PHPUnit tests | + +## Key Concepts Summary + +```mermaid +mindmap + root((Cubex
Framework)) + Applications + API Layer + Frontend Layer + CLI Layer + Routing + Subdomain-based + Generator pattern + Lazy loading + Architecture + Context-driven + Model-View separation + DalResolver pattern + Features + Auto-serialization + Asset management + Database abstraction +``` + +--- + +# Next Steps + +1. **Explore the API**: Add more endpoints in `src/Api/V1/Controllers/` +2. **Build frontend pages**: Create controllers in `src/Frontend/` +3. **Add CLI commands**: Extend `src/Cli/` for maintenance tasks +4. **Configure database**: Update connection settings in `conf/local/` +5. **Write tests**: Add unit tests in `tests/` + +## Resources + +- [Cubex Framework Documentation](https://cubex.io) +- [Packaged DAL](https://github.com/packaged/dal) - Database abstraction +- [Packaged Glimpse](https://github.com/packaged/glimpse) - HTML generation +- [Symfony Console](https://symfony.com/doc/current/components/console.html) - CLI component + +--- + +## License + +MIT diff --git a/composer.json b/composer.json index 8947770..29f9e8f 100644 --- a/composer.json +++ b/composer.json @@ -9,30 +9,28 @@ }, "prefer-stable": true, "require": { - "cubex/framework": "^4.0", - "cubex/mv": "^0.2", + "php": "^8", "filp/whoops": "^2.3", - "packaged/dal": "^1.8", - "packaged/dispatch": "~2.6", - "packaged/glimpse": "^2.8" + "packaged/dal": "^1.18", + "packaged/dispatch": "^2.21", + "packaged/glimpse": "^2.8", + "cubex/framework": "^4.23.0", + "cubex/api-foundation": "^0.8.0", + "cubex/api-transport": "^0.3.0" }, "require-dev": { - "phpunit/phpunit": "9.5.*" + "phpunit/phpunit": "^11.5.50", + "phpstan/phpstan": "^2.1" }, "autoload": { "psr-4": { - "Project\\": "src/" + "Project\\": "src/", + "Project\\Transport\\": "transport/" } }, "autoload-dev": { "psr-4": { "ProjectTests\\": "tests/" } - }, - "repositories": [ - { - "type": "vcs", - "url": "git@github.com:cubex/framework.git" - } - ] + } } diff --git a/conf/defaults.ini b/conf/defaults.ini index 91d30a2..dfe4d07 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -5,4 +5,5 @@ redirect_https = false [console] commands[generate] = GenerateContent +commands[create] = CreateTable patterns[] = Project.Cli.%s diff --git a/conf/local/connections.ini b/conf/local/connections.ini index 6ffa8c3..38ea498 100644 --- a/conf/local/connections.ini +++ b/conf/local/connections.ini @@ -1,6 +1,15 @@ +; If you want to use Mysql/Postgres swap the Pdo Connection with this commented block +;[cubex_skeleton] +;construct_class = \Packaged\Dal\Ql\MySQLiConnection +;hostname = 127.0.0.1 +;database = cubex_skeleton +;username = root +;password = password +;port = 3306 + +; Sqlite support for local development [cubex_skeleton] -construct_class = \Packaged\Dal\Ql\MySQLiConnection -hostname = 127.0.0.1 -database = cubex_skeleton -username = root -password = +construct_class = \Packaged\Dal\Ql\PdoConnection +hostname = 127.0.0.1 +database = cubex_skeleton +username = root diff --git a/cubex b/cubex index 59afea3..3b4affe 100755 --- a/cubex +++ b/cubex @@ -1,4 +1,12 @@ #!/usr/bin/env php cli()); +$cubex = Cubex::withCustomContext(SkeletonContext::class, __DIR__, $loader); +$application = new DefaultApplication($cubex); +exit($cubex->cli()); + diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..5311f9a --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,19 @@ +parameters: + level: 6 + tmpDir: '.phpstan.cache' + paths: + - src + ignoreErrors: + - '#^Parameter \#1 \$callback of function set_error_handler expects \(callable\(int, string, string, int\)\: bool\)\|null, [a-zA-Z0-9\\_\\\\:\\(\\)\, ]+ given\.$#' + - '#^Yield can be used only with these return types\: Generator, Iterator, Traversable, iterable\.$#' + - '#^Method [a-zA-Z0-9\\_\\\\:\\(\\)]+ return type has no value type specified in iterable type array\.$#' + - '#^Property [a-zA-Z0-9\\$\\_\\\\:\\(\\)]+code has no type specified\.$#' + - '#^Property [a-zA-Z0-9\\$\\_\\\\:\\(\\)]+message has no type specified\.$#' + - '#^Property [a-zA-Z0-9\\$\\_\\\\:\\(\\)]+::\$_tableName has no type specified\.$#' + - '#^Property [a-zA-Z0-9\\$\\_\\\\:\\(\\)]+ type has no value type specified in iterable type array\.$#' + - '#^Method [a-zA-Z0-9\\$\\_\\\\:\\(\\)]+ has parameter [a-zA-Z0-9\\$\\_\\\\:\\(\\)]+ with no value type specified in iterable type array\.$#' + - '#^Method [a-zA-Z0-9\\$\\_\\\\:\\(\\)]+ has parameter [a-zA-Z0-9\\$\\_\\\\:\\(\\)]+ with no value type specified in iterable type Packaged\\Dal\\Ql\\QlDaoCollection\.$#' + - '#^Parameter \#2 \$default of static method Packaged\\Helpers\\ValueAs\:\:obj\(\) expects null, stdClass given.$#' + - + identifier: missingType.generics + reportUnmatchedIgnoredErrors: false diff --git a/phpunit.xml b/phpunit.xml index 615517d..95fbc42 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,25 +1,28 @@ - + + + - - tests + + ./tests/Frontend - - + + src - - + transport + + + diff --git a/src/Api/ApiApplication.php b/src/Api/ApiApplication.php index 6019e19..e4f98d5 100644 --- a/src/Api/ApiApplication.php +++ b/src/Api/ApiApplication.php @@ -1,12 +1,13 @@ _configureConnections(); yield self::_route('v1', ApiVersionOne::class); diff --git a/src/Api/V1/AbstractVersionOneApiController.php b/src/Api/V1/AbstractVersionOneApiController.php index 8fc01ac..17ee28f 100644 --- a/src/Api/V1/AbstractVersionOneApiController.php +++ b/src/Api/V1/AbstractVersionOneApiController.php @@ -4,10 +4,11 @@ use Cubex\Controller\Controller; use Packaged\Context\Context; use Packaged\Http\Responses\JsonResponse; +use Symfony\Component\HttpFoundation\Response; abstract class AbstractVersionOneApiController extends Controller { - protected function _prepareResponse(Context $c, $result, $buffer = null) + protected function _prepareResponse(Context $c, $result, $buffer = null): Response { if(is_array($result) || is_scalar($result)) { diff --git a/src/Api/V1/ApiVersionOne.php b/src/Api/V1/ApiVersionOne.php index fdfcb6e..09a78c0 100644 --- a/src/Api/V1/ApiVersionOne.php +++ b/src/Api/V1/ApiVersionOne.php @@ -1,17 +1,18 @@ [ diff --git a/src/Api/V1/Definitions/Cat.php b/src/Api/V1/Definitions/Cat.php index 167681b..ddd79f6 100644 --- a/src/Api/V1/Definitions/Cat.php +++ b/src/Api/V1/Definitions/Cat.php @@ -3,10 +3,10 @@ class Cat { - public $name; - public $breed; + public string $name; + public string $breed; - public function __construct($name, $breed) + public function __construct(string $name, string $breed) { $this->name = $name; $this->breed = $breed; diff --git a/src/Cli/CliApplication.php b/src/Cli/CliApplication.php index 41af534..5cccd59 100644 --- a/src/Cli/CliApplication.php +++ b/src/Cli/CliApplication.php @@ -2,15 +2,38 @@ namespace Project\Cli; use Cubex\Context\Context; +use Cubex\CubexAware; +use Packaged\Context\ContextAware; use Project\SkeletonApplication; class CliApplication extends SkeletonApplication { - public static function launch(Context $ctx) + public static function launch(Context $ctx): self { - $app = new static($ctx->getCubex()); + $cubex = $ctx->getCubex(); + $app = new self($cubex); $app->setContext($ctx); $app->_configureConnections(); + + $cubex->onAfterResolve( + function($instance) use ($cubex) + { + if ($instance instanceof CubexAware) + { + $instance->setCubex($cubex); + } + + if ($instance instanceof ContextAware) + { + $instance->setContext($cubex->getContext()); + } + + return $instance; + } + ); + + $cubex->getConsole()->add($cubex->resolve(CreateTable::class)); + $cubex->getConsole()->add($cubex->resolve(GenerateContent::class)); return $app; } diff --git a/src/Cli/CreateTable.php b/src/Cli/CreateTable.php new file mode 100644 index 0000000..17a2443 --- /dev/null +++ b/src/Cli/CreateTable.php @@ -0,0 +1,32 @@ +setName('db:create'); + $this->setDescription('Run the test.sql script which creates the required table for testing the skeleton'); + } + + protected function _execute(InputInterface $input, OutputInterface $output): int + { + $connection = new ContentPage(); + $connection->getDataStore() + ->getConnection() + ->runQuery( + file_get_contents( + Path::system($this->getContext()->getProjectRoot(), 'test.sql') + ) + ); + + return self::SUCCESS; + } +} diff --git a/src/Cli/GenerateContent.php b/src/Cli/GenerateContent.php index e159608..eb48af9 100644 --- a/src/Cli/GenerateContent.php +++ b/src/Cli/GenerateContent.php @@ -5,6 +5,7 @@ use Packaged\Helpers\Strings; use Project\Storage\ContentPage; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class GenerateContent extends ConsoleCommand @@ -12,9 +13,23 @@ class GenerateContent extends ConsoleCommand /** * @short a */ - public $quantity = 1; + public int $quantity = 1; - protected function executeCommand(InputInterface $input, OutputInterface $output) + protected function configure(): void + { + $this->setName('db:generate'); + $this->setDescription('Seed the Content Page Table with content'); + + $this->addOption( + 'quantity', + 'a', + InputOption::VALUE_OPTIONAL, + 'How many seed items to generate', + 1 + ); + } + + protected function _execute(InputInterface $input, OutputInterface $output): int { for($i = 0; $i < $this->quantity; $i++) { @@ -25,9 +40,10 @@ protected function executeCommand(InputInterface $input, OutputInterface $output $content->content = implode(' ', array_slice($words, 0, 50)); $content->save(); } + return 0; } - protected function _words() + protected function _words(): array { return [ "knock", diff --git a/src/Context/SkeletonContext.php b/src/Context/SkeletonContext.php index 23d0da8..0d9b9e2 100644 --- a/src/Context/SkeletonContext.php +++ b/src/Context/SkeletonContext.php @@ -8,7 +8,7 @@ class SkeletonContext extends Context { - protected function _construct() + protected function _construct(): void { parent::_construct(); //Setup Database connections for Console Commands diff --git a/src/DefaultApplication.php b/src/DefaultApplication.php index ba9a436..5dc0320 100644 --- a/src/DefaultApplication.php +++ b/src/DefaultApplication.php @@ -46,7 +46,7 @@ function (\Packaged\Context\Context $c) use ($publicFile) { $proxies = $this->getContext()->config()->getItem('serve', 'trusted_proxies'); if($proxies !== null) { - Request::setTrustedProxies(ValueAs::arr($proxies), Request::HEADER_X_FORWARDED_ALL); + Request::setTrustedProxies(ValueAs::arr($proxies), Request::HEADER_X_FORWARDED_TRAEFIK); } //Run any generic setup here @@ -84,7 +84,7 @@ function ($errno, $errstr, $errfile, $errline) { ); } - protected function _setupApplication() + protected function _setupApplication(): void { } } diff --git a/src/Frontend/Content/ContentController.php b/src/Frontend/Content/ContentController.php index c89aa69..3b361f8 100644 --- a/src/Frontend/Content/ContentController.php +++ b/src/Frontend/Content/ContentController.php @@ -7,20 +7,21 @@ class ContentController extends LayoutController { - protected function _generateRoutes() + protected function _generateRoutes(): string { return 'content'; } - public function processContent() + public function processContent(): ContentModel { //Create a view model $model = ContentModel::withContext($this); - /** @var ContentView $page */ - $model->page = ContentPage::collection()->orderBy(CustomSelectExpression::create('RAND()'))->first(); + /** @var ContentPage $page */ + $page = ContentPage::collection()->orderBy(['id' => 'desc'])->first(); + $model->page = $page; //Return the model with a preferred view for improved testability - return $model->setDefaultView(ContentView::class); + return $model->setView(ContentView::class); } } diff --git a/src/Frontend/Content/ContentModel.php b/src/Frontend/Content/ContentModel.php index d071deb..53c0dd2 100644 --- a/src/Frontend/Content/ContentModel.php +++ b/src/Frontend/Content/ContentModel.php @@ -1,10 +1,11 @@ _model->page) + /** @var ContentModel $_model */ + $_model = $this->_model; + if(!$_model->page) { - return Paragraph::create('No page content found'); + return Div::create('No page content found'); } return Div::create( - HeadingOne::create($this->_model->page->title), - Paragraph::create($this->_model->page->content) + HeadingOne::create($_model->page->title), + Paragraph::create($_model->page->content) ); } } diff --git a/src/Frontend/FrontendApplication.php b/src/Frontend/FrontendApplication.php index 6491e0b..ae52da7 100644 --- a/src/Frontend/FrontendApplication.php +++ b/src/Frontend/FrontendApplication.php @@ -2,6 +2,8 @@ namespace Project\Frontend; use Cubex\Events\Handle\ResponsePreSendHeadersEvent; +use Generator; +use Packaged\Context\Conditions\ExpectEnvironment; use Packaged\Context\Context; use Packaged\Dispatch\Dispatch; use Packaged\Http\Response; @@ -14,7 +16,7 @@ class FrontendApplication extends SkeletonApplication { const DISPATCH_PATH = '/_r'; - protected function _initialize() + protected function _initialize(): void { parent::_initialize(); @@ -26,14 +28,14 @@ protected function _initialize() Dispatch::bind($dispatch); } - protected function _setupApplication() + protected function _setupApplication(): void { //Send debug headers locally $this->getCubex()->listen( ResponsePreSendHeadersEvent::class, function (ResponsePreSendHeadersEvent $e) { $r = $e->getResponse(); - if($r instanceof Response && $e->getContext()->isEnv(\Cubex\Context\Context::ENV_LOCAL)) + if($r instanceof Response && $e->getContext()->matches(ExpectEnvironment::local())) { $r->enableDebugHeaders(); } @@ -44,7 +46,7 @@ function (ResponsePreSendHeadersEvent $e) { $this->_configureConnections(); } - protected function _generateRoutes() + protected function _generateRoutes(): Generator { yield self::_route( self::DISPATCH_PATH, diff --git a/src/Frontend/Homepage/HomepageController.php b/src/Frontend/Homepage/HomepageController.php index 466a635..5c2d3d0 100644 --- a/src/Frontend/Homepage/HomepageController.php +++ b/src/Frontend/Homepage/HomepageController.php @@ -5,12 +5,12 @@ class HomepageController extends LayoutController { - protected function _generateRoutes() + protected function _generateRoutes(): string { return 'homepage'; } - public function processHomepage() + public function processHomepage(): string { return "This is a basic homepage"; } diff --git a/src/Frontend/Layout/HtmlWrap.php b/src/Frontend/Layout/HtmlWrap.php index e9010db..91c5d54 100644 --- a/src/Frontend/Layout/HtmlWrap.php +++ b/src/Frontend/Layout/HtmlWrap.php @@ -9,15 +9,15 @@ class HtmlWrap extends Element implements ContextAware { use ContextAwareTrait; - protected $_content = []; + protected mixed $_content = []; - public function setContent($content) + public function setContent(mixed $content): self { $this->_content = $content; return $this; } - public function getContent() + public function getContent(): mixed { return $this->_content; } diff --git a/src/Frontend/Layout/Layout.php b/src/Frontend/Layout/Layout.php index aea2cd2..0e0f967 100644 --- a/src/Frontend/Layout/Layout.php +++ b/src/Frontend/Layout/Layout.php @@ -15,14 +15,14 @@ class Layout extends Element implements DispatchableComponent, ContextAware, Wit use ContextAwareTrait; use WithContextTrait; - protected $_content; + protected mixed $_content = null; public function __construct() { ResourceManager::componentClass(self::class)->requireCss('css/layout.css'); } - public function setContent($content) + public function setContent(mixed $content): self { if($content instanceof ISafeHtmlProducer) { @@ -35,7 +35,7 @@ public function setContent($content) return $this; } - public function getContent() + public function getContent(): mixed { return $this->_content; } diff --git a/src/Frontend/Layout/LayoutController.php b/src/Frontend/Layout/LayoutController.php index 2122b13..886b7cd 100644 --- a/src/Frontend/Layout/LayoutController.php +++ b/src/Frontend/Layout/LayoutController.php @@ -2,8 +2,8 @@ namespace Project\Frontend\Layout; use Cubex\Controller\Controller; -use Cubex\Mv\View; -use Cubex\Mv\ViewModel; +use Cubex\ViewModel\View; +use Cubex\ViewModel\ViewModel; use Packaged\Context\Context; use Packaged\Ui\Renderable; diff --git a/src/SkeletonApplication.php b/src/SkeletonApplication.php index 67bdd76..fc53ef0 100644 --- a/src/SkeletonApplication.php +++ b/src/SkeletonApplication.php @@ -11,19 +11,24 @@ abstract class SkeletonApplication extends Application { use WithContextTrait; - //Setup our database connections - protected function _configureConnections() + protected function _configureConnections(): void { $ctx = $this->getContext(); $confDir = Path::system($ctx->getProjectRoot(), 'conf'); - $thisonnectionConfig = new IniConfigProvider(); - $thisonnectionConfig->loadFiles( + $connectionConfig = new IniConfigProvider(); + $connectionConfig->loadFiles( [ $confDir . DIRECTORY_SEPARATOR . 'defaults' . DIRECTORY_SEPARATOR . 'connections.ini', $confDir . DIRECTORY_SEPARATOR . $ctx->getEnvironment() . DIRECTORY_SEPARATOR . 'connections.ini', ] ); + + if ($connectionConfig->getItem('cubex_skeleton', 'construct_class') == '\Packaged\Dal\Ql\PdoConnection') + { + $connectionConfig->addItem('cubex_skeleton', 'dsn', 'sqlite://' . ltrim(Path::system($ctx->getProjectRoot(), 'database', 'database.sqlite'), '/')); + } + $datastoreConfig = new IniConfigProvider(); $datastoreConfig->loadFiles( [ @@ -31,7 +36,8 @@ protected function _configureConnections() $confDir . DIRECTORY_SEPARATOR . $ctx->getEnvironment() . DIRECTORY_SEPARATOR . 'datastores.ini', ] ); - $resolver = new DalResolver($thisonnectionConfig, $datastoreConfig); + + $resolver = new DalResolver($connectionConfig, $datastoreConfig); $this->getCubex()->share(DalResolver::class, $resolver); $resolver->boot(); } diff --git a/src/Storage/ContentPage.php b/src/Storage/ContentPage.php index a64e311..ccd84ee 100644 --- a/src/Storage/ContentPage.php +++ b/src/Storage/ContentPage.php @@ -7,7 +7,7 @@ class ContentPage extends QlDao { protected $_dataStoreName = 'cubex_skeleton'; - public $id; - public $title; - public $content; + public ?int $id = null; + public ?string $title = null; + public ?string $content = null; } diff --git a/test.sql b/test.sql index 58504d3..0157bde 100644 --- a/test.sql +++ b/test.sql @@ -1,8 +1,5 @@ -CREATE DATABASE `cubex_skeleton`; - -CREATE TABLE `cubex_skeleton`.`content_pages` ( - `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `title` varchar(50) COLLATE utf8_unicode_ci NOT NULL DEFAULT '', - `content` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +CREATE TABLE `content_pages` ( + `id` integer not null primary key autoincrement, + `title` varchar not null default '', + `content` text not null +); diff --git a/tests/Frontend/Content/ContentViewTest.php b/tests/Frontend/Content/ContentViewTest.php index 6d06109..9a8c2f3 100644 --- a/tests/Frontend/Content/ContentViewTest.php +++ b/tests/Frontend/Content/ContentViewTest.php @@ -11,7 +11,6 @@ class ContentViewTest extends TestCase public function testNoContent() { $model = new ContentModel(); - /** @var \Cubex\Mv\View $view */ $view = $model->createView(ContentView::class); self::assertStringContainsString('No page content found', $view->render()); } @@ -23,7 +22,6 @@ public function testContentRendered() $model->page->title = 'Page Title Y'; $model->page->content = 'Page Content X'; - /** @var \Cubex\Mv\View $view */ $view = $model->createView(ContentView::class); self::assertInstanceOf(ContentView::class, $view); $rendered = $view->render(); diff --git a/tests/TestApplication.php b/tests/TestApplication.php index 3af23d6..e0b1595 100644 --- a/tests/TestApplication.php +++ b/tests/TestApplication.php @@ -5,7 +5,7 @@ class TestApplication extends SkeletonApplication { - public function configureConnections() + public function configureConnections(): static { $this->_configureConnections(); return $this;