Contributing

Basics

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:

Developing in the Framework

If you have PHP installed locally with the intl extension, you're already set. If you prefer to develop in Docker, Aphiria comes with a Docker Compose file to get you up and running quickly. Simply run docker compose build, then configure your IDE to map your checked out Aphiria code to the /aphiria directory within the php service created by Docker Compose.

Bugs

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.

Features

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 bugs@aphiria.com.

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.

Linter

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:

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

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

PHPDoc

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
{
    /** @var string The user's full name */
    public string $fullName {
        get => "$this->firstName $this->lastName";
    }

    /**
     * @param string $firstName The user's first name
     * @param string $lastName The user's last name
     * @param list<string> $roles The user's roles
     */
    public function __construct(
        public readonly string $firstName,
        public readonly string $lastName,
        public private(set) array $roles = []
    ) {
    }

    /**
     * Adds a role to the user
     *
     * @param string $role The role to add
     */
    public function addRole(string $role): void
    {
        $this->roles[] = $role;
    }
}

Naming Conventions

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

Variables

All variable names:

Properties

We should favor using class properties over getXxx() and setXxx() whenever accessing and setting the value. If get- or set-logic is complicated, use property hooks over getter- or setter-methods. 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)
    {
    }
}

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

Functions/Methods

All function/method names:

Constants

All class constants' names:

Namespaces

All namespaces:

Classes

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:

Interfaces

All interface names:

Traits

All trait names:

Enums

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: