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:
- Have 100% code coverage per PHPUnit and Xdebug 3
- Abide by our naming conventions
- Have no static analysis errors
- Have no linter errors
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:
- Must be lower camel case, eg
$emailAddress
- Must not use Hungarian Notation, eg
$arrUsers
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:
- Must be succinct
- Your method name should describe exactly what it does, nothing more, and nothing less
- If you are having trouble naming a method, that's probably a sign it is doing too much and should be refactored
- Must be lower camel case, eg
compileList()
- Acronyms in function/method names ≤ 2 characters long, capitalize each character, eg
startIO()
- "Id" is an abbreviation (not an acronym) for "Identifier", so it should be capitalized
Id
- Acronyms in function/method names ≤ 2 characters long, capitalize each character, eg
- Must answer a question if returning a boolean variable, eg
hasAccess()
oruserIsValid()
- Always think about how your function/method will be read aloud in an
if
statement.if (userIsValid())
reads better thanif (isUserValid())
.
- Always think about how your function/method will be read aloud in an
Constants
All class constants' names:
- Must be upper snake case, eg
TYPE_SUBSCRIBER
Namespaces
All namespaces:
- Must be Pascal case, eg
Aphiria\FooBar
- For namespace acronyms ≤ 2 characters long, capitalize each character, eg
IO
- For namespace acronyms ≤ 2 characters long, capitalize each character, eg
Classes
All class names:
- Must be succinct
- Your class name should describe exactly what it does, nothing more, and nothing less
- If you are having trouble naming a class, that's probably a sign that it is doing too much and should be refactored
- Must be Pascal case, eg
ListCompiler
- For class name acronyms ≤ 2 characters long, capitalize each character, eg
IO
- Class filenames should simply be the class name with .php appended, eg ListCompiler.php
- For class name acronyms ≤ 2 characters long, capitalize each character, eg
Whenever possible, constructor property promotion should be used for properties that have no custom logic in the constructor.
Abstract Classes
All abstract class names:
- Must be Pascal case, eg
ConnectionPool
- Must not use
Abstract
,Base
, or any other word in the name that implies it is an abstract class
Interfaces
All interface names:
- Must be preceded by an
I
, egIUser
Traits
All trait names:
- Must be Pascal case, eg
ListValidator
- Must be not use
T
,Trait
, or any other word in the name that implies it is a trait
Enums
All enums:
- Must use singular names, eg
StatusCode
instead ofStatusCodes
- Must be Pascal case, eg
StatusCode::NotFound
instead ofStatusCode::NOT_FOUND
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: