Naming

Names follow fixed conventions so a name tells you what a thing is and whether it can be null.

Classes are CamelCase. A module's runtime lives on the <Module> class, and its cross-module configuration lives on a <Module>Config class. A data object is <Thing>Data and its database class is <Thing>Database. Anything that can be null is named with an optional prefix, so a name without optional is always set and needs no null check.

class Member {}          // module runtime
class MemberConfig {}    // cross-module configuration
class MemberData {}      // data object
class MemberDatabase {}  // database class

function rename(MemberData $member, ?FolderData $optionalFolder): void {
    // $member is always set: use it directly
    $name = $member->name;

    // $optionalFolder can be null: check before use
    if ($optionalFolder !== null) {
        $name = $optionalFolder->name . '/' . $name;
    }
}

Names carry the module prefix

Every class and every file name starts with the name of the module it is defined in. In a Shop module you write ShopProduct, ShopProductData, ShopProductDatabase and ShopPage. The prefix does real work: you can tell at a glance which module a class belongs to, and names never collide across modules, so two modules can each have their own product or page class without treading on each other.

class ShopProduct {}          // service class
class ShopProductData {}      // data object
class ShopProductDatabase {}  // database class
class ShopPage {}             // page/UI class

Suffixes name the kind of thing

Fixed suffixes tell you what role a class plays. A database class ends in Database, a data object ends in Data, and a union object ends in Union. So ShopOrderDatabase talks to the store, ShopOrderData holds one order's fields, and a ShopPaymentUnion represents a value that is one of several shapes. See Objects and types for what a Data class is allowed to contain.

User-interface function groups end in Ui. A class that builds or renders markup has a name ending in Ui, such as StatusUi, ChartUi or MemberAdminPageUi, so you always know where to look when you want UI. See Module structure and separation for why UI and database work are grouped this way.

Order names by importance

A name reads left to right from the most important, most general part down to the more specific. The module comes first, then the thing, then its role or variant. You write ShopProduct, then ShopProductData and ShopProductDatabase; you write a table member, then member_auth and member_folder. The general part is always on the left.

The payoff shows up the moment things are sorted. Because the shared part leads, everything in the same category or subcategory lands together in an alphabetical list. All of a module's classes cluster under its prefix, the ShopProduct trio sits as three consecutive lines, and every member table sorts as one block:

member
member_auth
member_code
member_feature
member_folder
member_password

Scanning becomes reading a grouped list rather than hunting scattered names, and a directory listing or table dump groups itself with no extra effort. The same ordering rule applies to database tables; see Database layer conventions for the table naming details.

The optional prefix

Anything that can be null is named with an optional prefix. A function that may return null starts its name with optional; a variable that may hold null starts with optional; a data-object field that may be null starts with optional. The payoff is that a name without optional is guaranteed to be set, so you never have to null-check it or wonder whether you should. The name is the contract.

Type such values as TYPE|null, never ?TYPE, so the two possibilities read left to right and match the wording of the name. See Database layer conventions for how nullability lines up with the schema.

// May return null: name and return type both say so.
function optionalCurrentMember(): MemberData|null {
    return Member::FromSession();
}

$optionalMember = optionalCurrentMember();

// $product has no optional prefix, so it is always set: use it directly.
$product = ShopProduct::Load($id);
$name = $product->name;

Next: Writing clear code.