Style Guide

Coding Style: PSR-12

General Overview

As stated in the PHP-Fig PSR-2 Coding Style Guide the PSR-2 Standard is deprecated as of 2019-08-10. Hence we should use PSR-12 which extends, expands and replaces PSR-2, the coding style guide and requires adherence to PSR-1, the basic coding standard.

Furthermore it is advised to stick to the PSR4 autoloading standard.

I will try to give a brief overview over these standards in this document .

To illustrate how code should be formatted, a short example is given below:

PSR-12 Code Example
<?php

declare(strict_types=1);

namespace Vendor\Package;

use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
use Vendor\Package\SomeNamespace\ClassD as D;

use function Vendor\Package\{functionA, functionB, functionC};

use const Vendor\Package\{ConstantA, ConstantB, ConstantC};

class Foo extends Bar implements FooInterface
{
    public function sampleFunction(int $a, int $b = null): array
    {
        if ($a === $b) {
            bar();
        } elseif ($a > $b) {
            $foo->bar($arg1);
        } else {
            BazClass::bar($arg2, $arg3);
        }
    }

    final public static function bar()
    {
        // method body
    }
}

Files and Lines

Files
  • MUST use the Unix LF (linefeed) line ending only.
  • MUST end with a non-blank line, terminated with a single LF.
  • MUST have an opening <?php tag and the closing ?> tag MUST be omitted from files containing only PHP.
  • MUST use only UTF-8 without BOM for PHP code
  • The header of a PHP file may consist of a number of different blocks. If present, each of the blocks below MUST be separated by a single blank line
  • Each block MUST be in the order listed below, although blocks that are not relevant may be omitted. → see PSR-12 Code Example
 PHP File Order
  • PHP File Order
    • <?php tag
    • File-level docblock.
    • One or more declare statements.
    • The namespace declaration of the file.
    • One or more class-based use import statements.
    • One or more function-based use import statements.
    • One or more constant-based use import statements.
    • The remainder of the code in the file.



Lines
  • There MUST NOT be a hard limit on line length. The soft limit on line length MUST be 120 characters.
  • Lines SHOULD NOT be longer than 80 characters; lines longer than that SHOULD be split into multiple subsequent lines of no more than 80 characters each.
  • There MUST NOT be trailing whitespace at the end of lines.
  • Blank lines MAY be added to improve readability and to indicate related blocks of code except where explicitly forbidden.
  • There MUST NOT be more than one statement per line.
  • Code MUST use an indent of 4 spaces for each indent level, and MUST NOT use tabs for indenting.

Classes and Functions

Classes, Interfaces and Traits
  • When instantiating a new class, parentheses MUST always be present even when there are no arguments passed to the constructor.
Class instantiation
$tree = new Tree();
  • The extends and implements keywords MUST be declared on the same line as the class name.
  • The opening brace for the class MUST go on its own line; the closing brace for the class MUST go on the next line after the body.
  • Lists of implements and, in the case of interfaces, extends MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one interface per line.
Example Class
<?php

namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class ClassName extends ParentClass implements
    \ArrayAccess,
    \Countable,
    \Serializable
{
    // constants, properties, methods
}
  • The use keyword used inside the classes to implement traits MUST be declared on the next line after the opening brace. Each individual trait that is imported into a class MUST be included one-per-line and each inclusion MUST have its own use import statement.
  • When the class has nothing after the use import statement, the class closing brace MUST be on the next line after the use import statement. Otherwise, it MUST have a blank line after the use import statement.
Example Class Traits
<?php

namespace Vendor\Package;

use Vendor\Package\FirstTrait;
use Vendor\Package\SecondTrait;
use Vendor\Package\ThirdTrait;

class ClassName
{
    use FirstTrait;
    use SecondTrait;
    use ThirdTrait;
}
Functions and Methods
  • Visibility MUST be declared on all methods.  (public, protected, private)
  • Method names MUST NOT be prefixed with a single underscore to indicate protected or private visibility. That is, an underscore prefix explicitly has no meaning.
  • Method and function names MUST NOT be declared with space after the method name. In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma.
  • The opening brace MUST go on its own line, and the closing brace MUST go on the next line following the body.
  • There MUST NOT be a space after the opening parenthesis, and there MUST NOT be a space before the closing parenthesis.
Example Method
<?php

namespace Vendor\Package;

class ClassName
{
    public function fooBarBaz($arg1, &$arg2, $arg3 = [])
    {
        // method body
    }
}
  • Method and function arguments with default values MUST go at the end of the argument list.
  • Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.
  • When the argument list is split across multiple lines, the closing parenthesis and opening brace MUST be placed together on their own line with one space between them.
Example Method
<?php

declare(strict_types=1);

namespace Vendor\Package;

class ReturnTypeVariations
{
	protected static $foo;

    public function functionName(int $arg1, ...$arg2): ?string
    {
        return 'foo';
    }

    final public function anotherFunction(
        ?string $foo,
        string $bar,
        &...$baz
    ): string {
        return 'foo';
    }
}

  • When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis, there MUST NOT be a space after the opening parenthesis, and there MUST NOT be a space before the closing parenthesis. In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma.
Example Method and Function call
bar();
$foo->bar($arg1);
Foo::bar($arg2, $arg3);

$foo->bar(
    $longArgument,
    $longerArgument,
    $muchLongerArgument
);

Control Structures

General
  • There MUST be one space after the control structure keyword
  • There MUST NOT be a space after the opening parenthesis
  • There MUST NOT be a space before the closing parenthesis
  • There MUST be one space between the closing parenthesis and the opening brace
  • The structure body MUST be indented once
  • The body MUST be on the next line after the opening brace
  • The closing brace MUST be on the next line after the body
  • The body of each structure MUST be enclosed by braces. This standardizes how the structures look and reduces the likelihood of introducing errors as new lines get added to the body.
  • All control keywords SHOULD look like single words (elsif)
Examples
If/Else/Elsif
if ($expr1) {
    // if body
} elseif ($expr2) {
    // elseif body
} else {
    // else body;
}

if (
    $expr1
    && $expr2
) {
    // if body
} elseif (
    $expr3
    && $expr4
) {
    // elseif body
}

// Ternary Operators
$variable = $foo ? 'foo' : 'bar';

$variable = $foo ?: 'bar';
Switch
switch ($expr) {
    case 0:
        echo 'First case, with a break';
        break;
    case 1:
        echo 'Second case, which falls through';
        // no break
    case 2:
    case 3:
    case 4:
        echo 'Third case, return instead of break';
        return;
    default:
        echo 'Default case';
        break;
}

switch (
    $expr1
    && $expr2
) {
    // structure body
}
While Loops
while ($expr) {
    // structure body
}

while (
    $expr1
    && $expr2
) {
    // structure body
}

do {
    // structure body;
} while ($expr);

do {
    // structure body;
} while (
    $expr1
    && $expr2
);
Foreach Loops
foreach ($iterable as $key => $value) {
    // foreach body
}
Try/Catch
try {
    // try body
} catch (FirstThrowableType $e) {
    // catch body
} catch (OtherThrowableType | AnotherThrowableType $e) {
    // catch body
} finally {
    // finally body
}

PSR-4 Auto-Loading

In Short: The Namespace should correspond to the file Path outgoing from the base directory. Class names should be 'PascalCase'!

For detailed information look into the PSR-4 Spec.

 Table with examples

The table below shows examples for the corresponding file path for a given fully qualified class name, namespace prefix, and base directory to adhere to the PSR-4 autoloading standard.

Fully Qualified Class Name

Namespace Prefix

Base Directory

Resulting File Path

\Acme\Log\Writer\File_Writer

Acme\Log\Writer

./acme-log-writer/lib/

./acme-log-writer/lib/File_Writer.php

\Aura\Web\Response\Status

Aura\Web

/path/to/aura-web/src/

/path/to/aura-web/src/Response/Status.php

\Symfony\Core\Request

Symfony\Core

./vendor/Symfony/Core/

./vendor/Symfony/Core/Request.php

\Zend\Acl

Zend

/usr/includes/Zend/

/usr/includes/Zend/Acl.php

Naming Conventions

Classes

TypeNaming ConventionFolderExampleAdditional NotesArtisan Command
Command

Pascal Case

[CommandSingular]Command.php

app/Console/Commands/

modules/[moduleName]/Console/

SettlementRunCommand.phpCommand should be callable with [module]:[command] or nms:[command]

make:command

module:make-command

Controller

Pascal Case

[ModelSingular]Controller

app/Http/Controllers/

modules/[moduleName]/Http/Controllers/

UserController.php

SlaController.php

Several Controllers can be grouped if they belong together. (i.e. Authentication)

make:controller

module:make-controller

Custom Collections

Pascal Case

[ModelSingular]Collection

app/Collections/

modules/[moduleName]/Collections/

AbilityCollection.php *Need to Extend EloquentCollection and Model-Method "newCollection()" should return instance of Custom Collectionnone
Event

Pascal Case - Past tense of Action that occured, no Suffix

app/Events/

modules/[moduleName]/Events/

ModemRestarted.php *

UserRegistered.php *


make:event

module:make-event

Exception

Pascal Case

[ExceptionName]Exception

app/Exceptions

modules/[moduleName]/Exceptions

PostTooLargeException.php

HttpResponseException.php

Extends the Original Exception Class and helps to have finer grained Errors
Factory

Pascal Case

[ModelSingular]Factory

database/factories/

modules/[moduleName]/Database/Factories

UserFactory.php *

ModemFactory.php *

Like Migrations, Factories have no Namespace

Used to generate dummy Data (Single Model) and for Tests

make:factory

module:make-factory

Job

Pascal Case

[SingularNoun]Job

app/Jobs

modules/[moduleName]/Jobs

RedisJob.php

ModemRefreshJob.php *

Processes that need to be Queued

make:job

module:make-job

ListenerPascal Case - Present Tense that describes the Action that is taken

app/Listeners

modules/[moduleName]/Listeners

LogPayment.php *

RestartModem.php *

Can be bound to one or More Events and is a Subtask that needs to happen when the event is triggered

make:listener

module:make-listener

MailPascal Case - Named after the action you want to inform about

app/mails

modules/[moduleName]/mails

NewCommentOnTicket.php *

ContractChanged.php *

How to use Mailable Classes

make:mail

module:make-mail

MiddlewarePascal Case - Named after the action you want to do

app/Http/Middleware

modules/[moduleName]/Http/Middleware

EncryptCookies.php

SetLanguage.php

Can be enabled or configured using the Kernel.php in app/Http

make:middleware

module:make-middleware

Migration

Snake Case with all lower case

[YYYY_MM_DD_HHiiss_action_table]

database/migrations

modules/[moduleName]/Database/Migrations

2018_08_24_134622_update_users_table.php

2018_09_07_000100_create_sla_table.php

Actions can be: "bugfix","change", "create","update","delete",

If possible only change one Table per Migration file

Class name of Migration should be the action in Pascal Case and has no Namespace

make:migration

module:make-migration

add "extendsBaseMigration"

ModelPascal Case - Singular

app/

modules/[moduleName]/Entities/

User.php

Modem.php

Eloquent Model, to easily work with DB

make:model

module:make-model

NotificationPascal Case - like mail, named after the action you want to inform about

app/Notifications

modules/[moduleName]/Notifications

NewCommentOnTicket.php *

ContractChanged.php *

Notification Doc

make:notification

module:make-notification

Observer

Pascal Case

[SingularModel]Observer

app/Observers

modules/[moduleName]/Observers

UserObserver.php *

ModemObserver.php *

We have them in the Model File right now → they should however be ported to their own files.

make:

module:make-

Policy

Pascal Case

[SingularModel]Policy

app/Policies

modules/[moduleName]/Policies

UserPolicy.php *

ModemPolicy *

Custom Request Policy to change Access permissions → We use Bouncer and therefore no real usage for us - but here to have a complete list

make:policy

module:make-policy

Provider

Pascal Case

[Entity]ServiceProvider

app/Providers

modules/[moduleName]/Providers

AppServiceProvider.php

EventServiceProvider.php


make:provider

module:make-provider

FormRequest

Pascal Case

[SingularModel]Request

app/Http/Requests

modules/[moduleName]/Http/Requests

UserRequest.php *Custom Form Requests to cleanup Controllers and keep the request related logic here

make:request

module:make-request

Validation rulePascal Case - name of the [thing] you want to validate

app/Rules

modules/[moduleName]/Rules

SerialNumber.php *

IpAdress.php *


make:rule

module:make-rule

Seeder

Pascal Case

[SingularModel]Seeder

database/seeds

modules/[moduleName]/Database/Seeders

CostCenterSeeder.php *

ProductSeeder.php *

Either to prefill the Database with data or

generate Dummy Data with multiple Models → should call factory

make:seeder

module:make-seeder

Test

Pascal Case

Unit: [SingularModel]Test

Feature: [Action]Test

app/Tests/[Unit/Feature]

modules/[moduleName]/Tests/[Unit/Feature]

BaseModelTest.php *

ModemTest.php *

RefreshModemTest.php *


make:test

module:make-test

Interface

Pascal Case

[Name]Contract

app/Contracts

modules/[moduleName]/Contracts

CdrContract.php *

ConfigFileContract.php *

Used to have an Agreement(API) between several classes, that thei have to adhere to
Trait

Pascal Case - verb form or some kind of active form (has/can)

app/Traits

modules/[moduleName]/Traits

HasRolesAndAbilities.php

Notifiable.php

Used to have related Code that is used in more than one Class in one Place.
Utility / PHP ClassPascal Case

app/Utility

modules/[moduleName]/Utility

NmsPrimeHelpers.php

SettlementRunZipper.php

Plain PHP Classes that are no Eloquent Models or have some Utility Logic

*These are Examples for Filenames, but they do not Exist in the System at the time of writing this page

Database

EntityTypeNaming ConventionExamplesNMSPrime complies to this
TablesStandardPluralmodems, usersno
PivotSingular Snake Case in Alphabetic orderrole_usermostly
ColumnsStandard

Snake Case Singular

name, streetno
JSON/ArraySnake Case - PluralserviceNumbersno
boolean

CamelCase

is[Singular]


no
Primary Keyid
yes
Foreign Keys[ModelSingular]_idconfigfile_idyes
Polymorphicadjective, usually ending on "able" - similar to trait. (i. e. likable)ticketable → ticket.ticketable_id and ticket.ticketable_typeyes
timestamps

Snake Case and should end with "on", "at", (or for dates) "from", "to"


sometimes

Functions, Methods and Variables

EntityTypeNaming ConventionExamplesComment
Artisan command options
kebab-case

FunctionsClasslessCamel Case - inside Helpers
checkLocale helpers.php
/**
 * This determines if the given locale is supported by NMS Prime. It returns a
 * two letters language ISO 639-1 code of a supported language. The default
 * is set to English, when no other configuration in 'app/config' is set.
 *
 * @param string|null $locale
 * @return string
 */
function checkLocale($locale = null): string
{
    return in_array($locale, config('app.supported_locales')) ?
            $locale :
            config('app.locale', config('app.fallback_locale', 'en'));
}

VariablesConstantsAll Capital Snake Case
Constant MibFile.php
    /**
     * @Const MibFile Upload Path relativ to storage directory
     */
    private const REL_MIB_UPLOAD_PATH = 'app/data/hfcsnmp/mibs/';
Try to avoid constants, rather use the configuration file system that Laravel provides.
Properties[public/protected/private] [static] $CamelCase
Properties Role.php
    /**
     * The table associated with the model.
     *
     * @var string
     */
    public $table = 'roles';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'title',
        'description',
        'rank',
    ];

Laravel specific Eloquent properties

protected $connection

protected $table

protected $primaryKey

public $incrementing

protected $with

protected $withCount

protected $guarded

protected $fillable

protected $casts

protected $dates

Laravel Properties Model.php
    /**
     * The connection name for the model.
     *
     * @var string
     */
    protected $connection;

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table;

    /**
     * The primary key for the model.
     *
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * The "type" of the auto-incrementing ID.
     *
     * @var string
     */
    protected $keyType = 'int';

    /**
     * Indicates if the IDs are auto-incrementing.
     *
     * @var bool
     */
    public $incrementing = true;

    /**
     * The relations to eager load on every query.
     *
     * @var array
     */
    protected $with = [];

    /**
     * The relationship counts that should be eager loaded on every query.
     *
     * @var array
     */
    protected $withCount = [];
  • every model should have a fillable or guarded property to protect against/allow mass assignments
  • with property casts (or dates), it is possible to cast properties of models to certain types on retreival


Standard$CamelCase
Expressive Variables AbilityController.php
    /**
     * Updates the Abilities that are explicitly bound to a model with the CRUD
     * actions manage (allow everything on that model), view, create, update
     * and delete. It is bound to the Route "modelAbility.update" and is
     * called via AJAX Requests.
     *
     * @param Illuminate\Http\Request $request
     * @return json
     * @author Christian Schramm
     */
    protected function updateModelAbility(Request $request)
    {
        $requestData = collect($request->all())->forget('_token');

        $module = $requestData->pull('module');
        $allowAll = $requestData->pull('allowAll');
        $role = Role::find($requestData->pull('roleId'));

        $modelAbilities = self::getModelAbilities($role)[$module]
            ->keys()
            ->mapWithKeys(function ($model) use ($requestData) {
                if (! $requestData->has($model)) {
                    $requestData[$model] = [];
                }

                return [$model => $requestData[$model]];
            })
            ->merge($requestData);

        $this->registerModelAbilities($role, $modelAbilities, $allowAll);

        return self::getModelAbilities($role)->toJson();
    }
Try to use expressive variable names - ret and var are NOT expressive
Methods



standard[final/abstract] [public/protected/private] [static] CamelCase()
method updateModelAbility AbilityController.php
    /**
     * Updates the Abilities that are explicitly bound to a model with the CRUD
     * actions manage (allow everything on that model), view, create, update
     * and delete. It is bound to the Route "modelAbility.update" and is
     * called via AJAX Requests.
     *
     * @param Illuminate\Http\Request $request
     * @return json
     * @author Christian Schramm
     */
    protected function updateModelAbility(Request $request)
    {
        $requestData = collect($request->all())->forget('_token');

        $module = $requestData->pull('module');
        $allowAll = $requestData->pull('allowAll');
        $role = Role::find($requestData->pull('roleId'));

        $modelAbilities = self::getModelAbilities($role)[$module]
            ->keys()
            ->mapWithKeys(function ($model) use ($requestData) {
                if (! $requestData->has($model)) {
                    $requestData[$model] = [];
                }

                return [$model => $requestData[$model]];
            })
            ->merge($requestData);

        $this->registerModelAbilities($role, $modelAbilities, $allowAll);

        return self::getModelAbilities($role)->toJson();
    }

Relationship

hasMany/belongsToMany/morphTo: public [ModelPlural]()

hasOne/belongsTo: public [ModelSingular]()

Relationships Ticket.php
    /**
     * Relations
     */
    public function comments()
    {
        return $this->hasMany(Comment::class)->orderBy('id', 'desc');
    }

    /**
     * user who created the ticket
     */
    public function user()
    {
        return $this->belongsTo('App\User', 'user_id');
    }

Laravel Magic Methods

set[name]Attribute

get[name]Attribute

increment[Attribute]

decrement[Attrubute]

fresh()

Laravel Doc

NMS Prime Specific

String concatenation: TBD


PHP CodeSniffer Profile


TODO: Will work on a PHP Codesniffer and fixer configuration as precommit hook to apply most of the mentioned Styles automatically

Frontend

For Theming a mixture of Bootstrap and ColorAdmin is used. Due to this, a lot of functionality.

Color Standards

code

Color

Meaning
infoblue

Info

Label: Default

dangerredError or sth is critically wrong
warningyellowGives a hint that there is a threshhold exceeded or sth is close to a critical state
activegreyActually used for inactive/outdated contracts, items - everything that is old

Be aware that colorization is not yet implemented as written above in a lot of models/cases as of Oct 2019