Skip to content

Latest commit

 

History

History
283 lines (204 loc) · 12.6 KB

contributing.md

File metadata and controls

283 lines (204 loc) · 12.6 KB

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 [email protected].

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
  • Must answer a question if returning a boolean variable, eg hasAccess() or userIsValid()
    • Always think about how your function/method will be read aloud in an if statement. if (userIsValid()) reads better than if (isUserValid()).

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

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

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, eg IUser

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 of StatusCodes
  • Must be Pascal case, eg StatusCode::NotFound instead of StatusCode::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: