This guide documents the established patterns and conventions in the Podsumer codebase. These are not suggestions or improvements, but observations of how the repository currently operates. Contributors should follow these existing patterns to maintain consistency.
Podsumer is a self-hosted podcast aggregator (podcatcher) written in PHP with zero third-party runtime dependencies. The codebase prioritizes simplicity, privacy, and self-containment.
- Zero Runtime Dependencies: The application runs without any third-party PHP packages in production. Only PHPUnit is used as a development dependency.
- Single Namespace: All code lives under
Brickner\Podsumerwith no sub-namespaces. - SQLite Only: The application uses SQLite exclusively for data storage, with support for both in-database and on-disk media storage.
- Privacy First: No telemetry, analytics, or external service calls except to podcast feeds.
- Strict Types: Every PHP file begins with
<?php declare(strict_types = 1); - Type Hints: All method parameters and return types must be explicitly typed
- Property Visibility: Use
protectedfor class properties, notprivate - Method Visibility: Always explicitly declare method visibility (
public,protected) - Comments: Use
#for single-line comments, not// - Imports: Use
usestatements at the top for specific classes/functions from global namespace - Short Tags: Templates use
<?and<?=instead of<?phpand<?php echo
- Classes: PascalCase (e.g.,
Main,State,FSState) - Methods: camelCase (e.g.,
getFeed,addFeedItems) - Properties: snake_case (e.g.,
$state_file_path,$test_mode) - Constants: UPPERCASE with underscores (e.g.,
VERSION,PODSUMER_PATH) - Database Tables: Plural snake_case (e.g.,
feeds,items,file_contents) - Database Columns: snake_case (e.g.,
url_hash,last_update) - Configuration Keys: snake_case (e.g.,
media_dir,state_file)
- Source Code:
src/Brickner/Podsumer/- flat structure, no subdirectories - Tests:
tests/Brickner/Podsumer/- mirrors source structure - Templates:
templates/- uses.html.phpand.xml.phpextensions - Configuration:
conf/- INI format configuration files - SQL:
sql/- Database schema and migrations - Web Root:
www/- Contains onlyindex.phpentry point
- Routes are defined using PHP 8 attributes on global functions in
www/index.php - Route format:
#[Route('/path', 'METHOD', $requiresAuth)] - Route handlers are global functions that accept
array $argsparameter - Route discovery uses reflection to find functions with Route attributes
- All route handlers return
voidand output directly
Example:
#[Route('/feed', 'GET', true)]
function feed(array $args): void
{
global $main;
// Implementation
}- All queries use prepared statements via PDO
- SQL strings are multi-line with specific formatting
- Use
ON CONFLICT DO UPDATE SET id=idfor upsert operations - Foreign keys are always enabled with cascading deletes
- Hash columns (md5) used for deduplication
- PRAGMA settings optimize for performance over durability
- Exceptions are caught, logged, and only re-thrown in test mode
- HTTP errors return appropriate status codes (404, 500)
- Missing resources return 404 without throwing exceptions
- Validation uses
empty()checks with early returns
- Templates use PHP as the templating language
- Variables are extracted into template scope
- All template variables are sanitized with
strip_tags() - Base template wraps content templates
- Static render methods on Template class
- Test classes are declared as
final - Test class names follow
{ClassName}Testpattern - Test methods follow
test{MethodName}pattern - Uses
expectNotToPerformAssertions()for tests without assertions - Creates temporary test database that's cleaned up after tests
- Real external URLs used in tests (e.g., NPR podcast feed)
- Test mode flag passed through application stack
- Default branch is
development - Pull requests target
developmentbranch - GitHub Actions run on push and PR to
development
- Composer validation with
--strictflag - PHPUnit tests with coverage reporting
- Coverage badge generated and pushed to
image-databranch - Composer dependencies cached for performance
- Based on
php:8.2.12-apache-bookwormimage - Application installed in
/opt/podsumer/ - Runs on port 3094
- Uses
www-datauser with 755 permissions - Composer autoload regenerated at build time
- INI format with sections
[section_name] - Comments use
#prefix - Typed value parsing enabled
- Two-level access pattern:
section.key - Runtime configuration changes supported
- Test-specific configuration for test mode
- Tailwind CSS loaded from CDN
- Dark theme using neutral colors
- Amber accent colors for links
- Database size shown in GB
- Footer includes version and contribution links
- HTTP Basic Authentication when configured
- Input sanitization using
filter_var_array() - File paths validated before access
- No direct SQL query building - always use prepared statements
- No more than 3 attempts to fix linter errors per file
- Methods should be focused and relatively short
- Early returns preferred over nested conditionals
- Guard clauses at method start
- Explicit null checks rather than truthy/falsy checks
- Media Storage: All media files are stored on disk in the configured media_dir
- Performance: Database operations use transactions and optimized PRAGMA settings
- Compatibility: Requires SQLite 3.6.19+ with foreign key support
- Memory: No memory limit set (
memory_limit = -1)
- Never create documentation files unless explicitly requested
- Never create README files unless explicitly requested
- Never create unnecessary helper or utility files
- Always prefer editing existing files over creating new ones
- Run tests:
composer test - Generate coverage:
composer coverage - View coverage locally:
composer coverage-dev - Ensure no regression in test coverage
Follow the CODE-OF-CONDUCT.md: "Be kind. Don't be mean." Apply the Golden Rule in all interactions.
All contributions must be compatible with the MIT License. Copyright is held by Joshua Brickner.