No description
  • PHP 75.1%
  • CSS 18.1%
  • JavaScript 5.1%
  • Hack 1.7%
Find a file
2026-07-05 22:14:02 +02:00
assets add new image, new CSS class and apply it to poems hero container 2026-07-05 22:14:02 +02:00
cache chore: documentation, simplification of concerns, initialization of system directories 2026-07-05 15:17:40 +02:00
lib add new image, new CSS class and apply it to poems hero container 2026-07-05 22:14:02 +02:00
logs chore: documentation, simplification of concerns, initialization of system directories 2026-07-05 15:17:40 +02:00
templates add missing links to main footer (put privacy page only on footer - dropdown menu is becoming too big) 2026-07-05 15:35:14 +02:00
.gitignore chore: documentation, simplification of concerns, initialization of system directories 2026-07-05 15:17:40 +02:00
.htaccess init 2026-04-05 05:10:52 +02:00
composer.json refactor: implement PSR-7 request handling and improve error handling 2026-07-03 20:28:12 +02:00
composer.lock refactor: implement PSR-7 request handling and improve error handling 2026-07-03 20:28:12 +02:00
favicon.ico Add files via upload 2026-04-04 01:33:53 +02:00
index.php chore: documentation, simplification of concerns, initialization of system directories 2026-07-05 15:17:40 +02:00
php_index.php feat: introduce MVC routing architecture and poetry module 2026-07-03 04:01:38 +02:00
README.md chore: documentation, simplification of concerns, initialization of system directories 2026-07-05 15:17:40 +02:00
robots.txt Add files via upload 2026-04-04 01:33:53 +02:00

ArcaneSunset

A personal poetry showcase web application, built as a lightweight PHP MVC application without any framework. Uses Composer autoloading, custom routing, PSR-7 HTTP messages, and PHP-DI for dependency injection.

Overview

Attribute Value
Author ArcaneSunset contact@arcanesunset.com
Language PHP (strict typing)
Namespace ArcaneSunset\lib/
Coding Style PSR-12 (enforced via Composer scripts)
Entry Point /index.php

Dependencies

{
  "composer/composer": "^2.9",
  "nesbot/carbon": "^3.11",
  "php-di/php-di": "^7.1",
  "vlucas/phpdotenv": "^5.6",
  "psr/http-message": "^2.0",
  "guzzlehttp/psr7": "^2.12"
}

Directory Structure

/var/www/html/
├── index.php                          # Entry point (bootstraps DI, routing, rendering)
├── .htaccess                          # URL rewriting + AI bot blocking rules
├── .env                               # Environment variables (APP_DEBUG)
├── composer.json / composer.lock      # Dependencies and autoload config
├── robots.txt                         # SEO crawling directives
├── php_index.php                      # Legacy/alternate entry point
├── favicon.ico                        # Site favicon
│
├── templates/                          # Layout and partials
│   ├── main.php                        # Master layout (header/footer, renders page view)
│   └── components/
│       ├── headers/main.php            # Header partial
│       └── footers/main.php            # Footer partial
│
├── lib/.htaccess                       # Denies direct access to lib files
│
├── lib/                               # Application code (ArcaneSunset\ namespace)
│   ├── Container.php                   # DI container bootstrap (php-di, file caching in prod)
│   │
│   ├── Controllers/                    # Controllers
│   │   ├── AbstractController.php      # Abstract base: $viewTemplate, $viewData, assign(), handle()
│   │   ├── BaseController.php          # Concrete base: CRUD actions, setting context variables
│   │   ├── HomeController.php
│   │   ├── AboutController.php
│   │   └── PoemsController.php
│   │
│   ├── Models/                         # Domain models and repositories
│   │   ├── Page.php                    # Page metadata from pages.json (title, desc, content path)
│   │   ├── BaseRepository.php          # XML data fetching base class (SimpleXML → JSON decode)
│   │   ├── DummyDataProvider.php       # Fallback dummy XML data generation
│   │   └── Poetry/
│   │       ├── Poem.php                # Poem domain model (uses Carbon for date formatting)
│   │       └── PoetryRepository.php    # Extends BaseRepository, sorted by ID desc
│   │
│   ├── Traits/                         # Reusable trait compositions
│   │   └── SeasonTrait.php             # Season calculation via Carbon month/day logic
│   │
│   ├── Services/                       # Cross-cutting services
│   │   └── Router.php                  # Custom router: static route definitions with {id} placeholders
│   │
│   ├── Exceptions/                     # Custom exceptions and handler
│   │   ├── ExceptionHandler.php        # Centralized error handling (debug mode, logging, HTTP codes)
│   │   ├── NotFoundException.php       # 404
│   │   ├── InternalServerError.php     # 500
│   │   └── ValidationException.php     # 400
│   │
│   ├── config/                         # Configuration files
│   │   ├── config.php                  # App config (debug, display_errors from env)
│   │   └── di.php                      # PHP-DI definitions (controllers, services, factories)
│   │
│   ├── data/                           # Data files
│   │   ├── pages.json                  # Page metadata (title, desc, content path, optional script path)
│   │   └── poetry/poems.xml            # Poem data in XML format
│   │
│   └── views/                          # Page-specific view templates
│       ├── index.php
│       ├── about.php
│       ├── poems.php
│       ├── poems-list.php              # Alternate listing view for poems
│       └── scripts/poems.php           # Page-specific inline JS
│
├── assets/                             # Static assets (CSS, JS, fonts, images)
│   ├── css/main.css                    # Main stylesheet
│   ├── js/header.js                    # Header interactivity
│   ├── js/poem-modal.js               # Poem modal dialog logic
│   └── fonts/ ... / img/ ...          # Fonts and image assets
│
├── cache/                              # DI definition cache (production only)
└── logs/                               # Error log files

Architecture

Request Lifecycle

  1. .htaccess blocks known AI bot user agents with [F] flag, then rewrites all non-file/non-directory requests to /index.php?path=$1
  2. index.php bootstraps: loads Composer autoload, loads .env via Dotenv, builds DI container
  3. URL path segments are extracted directly in index.php using parse_url() + manual splitting
  4. Segments are validated with regex (^[a-zA-Z0-9\/_-]+$) — invalid chars throw ValidationException
  5. Router::resolve() matches segments against hardcoded static route definitions, returns controller class and params
  6. Controller is instantiated via DI container factory definitions ($container->make())
  7. Route params are injected via $controller->setRouteParams()
  8. Controllers receive a PSR-7 ServerRequestInterface via constructor injection (created by GuzzleHttp in di.php)
  9. $controller->handle() executes the action and populates view data
  10. Page model is instantiated with route segments to load page metadata from pages.json
  11. If route has json flag, returns JSON response; otherwise renders layout template
  12. templates/main.php uses extract($viewData) to make controller data available as local variables in views
  13. Page-specific view is included via $page->getContentPath()
  14. Page-specific scripts are included via $page->getScriptPath() (if defined)

Controller Hierarchy

  • AbstractController: Abstract base class. Defines $viewTemplate, $viewData, $routeParams, $repository. Accepts ServerRequestInterface in constructor. Provides assign(), getViewTemplate(), getViewData(), setRouteParams(), getRouteParam(), setRepository(). Abstract handle() method. Uses PHP docblock @property and @method annotations for IDE introspection.
  • BaseController: Extends AbstractController. Uses Context Traits. Adds HTTP method detection, CRUD-like actions (getIndexAction, getViewAction, getUpdateAction), JSON path detection. Has public $id, $method properties and context parameters. Uses $repository property for data access.
  • Concrete controllers (Home, About, Poems): Extend BaseController. Set $viewTemplate to view path. PoemsController overrides constructor and sets $id = 'poem'.

Model / Repository Pattern

  • Page: Loads page metadata from lib/data/pages.json. Constructor accepts $segments array (not string ID). Provides title, description, content path, script path. Validates file existence. Throws NotFoundException/InternalServerError on failures.
  • BaseRepository: Generic XML data loader. Parses XML via SimpleXML → JSON decode → instantiates model objects. Provides getData(), getById(), update(), sorting helpers (sortById, sortBy). Delegates dummy data writing to DummyDataProvider.
  • DummyDataProvider: Separate class for generating fallback XML data files. Has poem-specific (writeDummyPoems) and generic (writeDummyDefault) methods. Both write to hardcoded path /lib/data/poetry/poems.xml.
  • Domain models (e.g., Poem): Plain PHP objects with public properties. May include formatting/parsing methods (HTML rendering, date formatting via Carbon).
  • Domain repositories (e.g., PoetryRepository): Extend BaseRepository. Configure model class and data path in constructor. Override dummy data writing for domain-specific fallback content.

Routing

Routes are hardcoded in the Router constructor:

'/'           => HomeController::class,
'/about'      => AboutController::class,
'/poems'      => PoemsController::class,
'/poems/{id}' => PoemsController::class,

The {id} placeholder syntax is supported — segments matching placeholders are extracted as route params. The json flag in params controls JSON vs HTML output.

Dependency Injection

  • Container bootstrapped in lib/Container.php using php-di/php-di
  • Definitions loaded from lib/config/di.php
  • Controllers use factory definitions (not autowired) to avoid DI guessing repository properties
  • Controllers receive ServerRequestInterface via factory closure (created by GuzzleHttp \GuzzleHttp\Psr7\ServerRequest::fromGlobals())
  • ExceptionHandler and Router are autowired with constructor/property overrides
  • Page uses a factory accepting optional $id array parameter (route segments)
  • PoemsController factory explicitly instantiates PoetryRepository and calls setRepository()
  • Definition caching enabled in production (cache/ directory), disabled when APP_DEBUG=true

Exception Handling

  • Global exception handler set via set_exception_handler() in index.php
  • ExceptionHandler logs errors to logs/error.log, sets HTTP status code, renders debug or generic error page based on APP_DEBUG
  • Custom exceptions: NotFoundException (404), InternalServerError (500), ValidationException (400)

View Rendering

  • Master layout: templates/main.php (includes header/footer partials, injects context variables into JS, loads jQuery)
  • Page-specific views live in lib/views/ and are included via $page->getContentPath()
  • Controller view data is extracted into local scope via extract($viewData) for direct variable access in views
  • Views use PHP short tags (<?php, <?=, <?=) with declare(strict_types=1)

Data Storage

  • pages.json: JSON file defining page metadata (title, description, content path, optional script path). Supports {id} placeholder patterns in keys (e.g., "poems.{id}").
  • poems.xml: XML file containing poem data. Parsed by BaseRepository via SimpleXML.
  • Repositories write dummy/fallback data if the data file is missing (via DummyDataProvider delegation).

Coding Standards

  • PSR-12 coding style enforced via composer run cs-check / composer run cs-fix
  • All PHP files start with <?php and declare(strict_types=1);
  • Namespace follows PSR-4: ArcaneSunset\\lib/
  • Type hints used throughout (strict typing, return types, property types)
  • Extensive DocBlock annotations including @package, @author, @since, @property, and @method for IDE introspection

Environment Configuration

  • .env file loaded via vlucas/phpdotenv in index.php (immutable mode)
  • APP_DEBUG controls: debug error display, DI caching behavior, exception handler debug mode

Composer Scripts

Script Command
composer run cs-check phpcs --standard=PSR12 . --ignore=vendor
composer run cs-fix phpcbf --standard=PSR12 . --ignore=vendor

AI Bot Protection

The .htaccess file blocks known AI/data-scraping bot user agents with an [F] (Forbidden) flag. The robots.txt file reinforces this by disallowing crawling for the same set of bots. Blocked user agents include: CCBot, ChatGPT, GPTBot, anthropic-ai, ClaudeBot, Google-CloudVertexBot, PerplexityBot, and many others.