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;