First, thank you for taking the time to contribute to Aphiria! We use GitHub pull requests for all code contributions. To get started on a bug fix or feature, fork Aphiria, and create a branch off of 1.x. Be sure to run composer test locally before opening the pull request to run the unit tests, static analyzer, and linter. Once your bug fix/feature is complete, open a pull request against 1.x.

All pull requests must:


Before you attempt to write a bug fix, first read the documentation to see if you're perhaps using Aphiria incorrectly. If you find a hole in our documentation, feel free to open a pull request to fix it.

Reporting a Bug

To report a bug with either the framework or skeleton app, create a new GitHub issue with a descriptive title, steps to reproduce the bug (eg a failing PHPUnit test), and information about your environment. If you are just looking for general help, use GitHub Discussions.

Fixing a Bug

To fix a bug, create a pull request with the fix and relevant PHPUnit tests that provide 100% code coverage. Before opening a pull request, run composer test to run unit tests, the linter, and the static analyzer.


We always appreciate when you want to add a new feature to Aphiria. For minor, backwards-compatible features, create a pull request. Do not submit pull requests to individual libraries' repositories. For major, possibly backwards-incompatible features, please open an issue first to discuss it prior to opening a pull request.

Aphiria strives to not create any unnecessary library dependencies. This even includes having dependencies on other Aphiria libraries whenever possible. If your change will introduce a new dependency to a library, create an issue and ask about it before implementing it.

Security Vulnerabilities

Aphiria takes security seriously. If you find a security vulnerability, please email us at

Coding Style

Aphiria follows PSR-12 coding standards and uses PSR-4 autoloading. All PHP files should specify declare(strict_types=1);. Additionally, unless a class is specifically meant to be extended, declare them as final to encourage composition over inheritance.


All code is run through PHP-CS-Fixer, a powerful linter. Pull requests that do not pass the linter will automatically be prevented from being merged. You can run the linter locally via composer phpcs-test to check for errors, and composer phpcs-fix to fix any errors.

Static Analysis

Aphiria uses the terrific static analysis tool Psalm. It can detect things like unused code, inefficient code, and incorrect types. We use the highest level of error reporting. You can run Psalm locally via composer psalm.

Occasionally, Psalm might highlight false positives, which can be suppressed with:

/** @psalm-suppress {issue handler name} {brief description of why you're suppressing it} */
// Problematic code here...

You can also suppress false positives in psalm.xml.dist at the directory- and file-levels. Be sure to include an XML comment explaining why the errors should be suppressed:

        <errorLevel type="suppress">
            <!-- We don't care about mixed assignments in tests -->
            <directory name="src/**/tests" />

Use error suppression sparingly - try to fix any legitimate issues that Psalm finds.


Use PHPDoc to document all class properties, methods, and functions. Constructors only need to document the parameters. Method/function PHPDoc must include one blank line between the description and the following tag. Here's an example:

final class User
     * @param string $firstName The user's first name
     * @param string $lastName The user's last name
    public function __construct(
        public readonly string $firstName,
        public readonly string $lastName
    ) {
     * Gets the user's full name
     * @return string The user's full name
    public function getFullName(): string
        return "{$this->firstName} {$this->lastName}";

Naming Conventions

Inspired by Code Complete, Aphiria uses a straightforward approach to naming things.


All variable names:


We should favor using class properties over getXxx() and setXxx() whenever accessing and setting the value is straight forward. If a class property should only be read, it should be declared as readonly rather than using a getXxx() method. For example, here is what not to do:

final class Book
     * @param string $title The book title
    public function __construct(private string $title)
     * Gets the book title
     * @return string The book title
    public function getTitle(): string
        return $this->title;

Instead, declare the title to be readonly:

final class Book
     * @param string $title The book title
    public function __construct(public readonly string $title)

The exception to this rule is when an interface needs to expose accessors to a property, but only because PHP interfaces do not support properties.

We should also favor marking properties as readonly, even when private, if their values should not be set/changed outside of the constructor.


All function/method names:


All class constants' names:


All namespaces:


All class names:

Whenever possible, constructor property promotion should be used for properties that have no custom logic in the constructor.

Abstract Classes

All abstract class names:


All interface names:


All trait names:


All enums:

Financial Support

Aphiria is primarily written by David Young in his spare time. It is the labor of over a thousand hours of meticulously crafting its syntax, designing its architecture, and writing its code. While Aphiria is and always will be free and open source, GitHub sponsorship is always welcome. While you're at it, consider sponsoring some others whose tools you might already be using: