Style

General

  • The source code of all projects should look like a single person wrote it.
  • This style guide will be enforced strictly.
  • There will be no prematurely built-in performance optimization. The source code should primarily be readable and understandable.
  • All lists have to contain trailing commas if possible.
  • All code should obey the rules of our linting (CI and kaba).
  • If possible always use types explicitly. This especially goes for void and all types in TypeScript.

Files

  • All files are saved as UTF-8 without BOM.
  • Superfluous whitespace at the end of a line has to be removed. The only exceptions are file formats where whitespace makes a functional difference (e.g. Markdown).
  • All files end in an empty new line (technically they end with the new line character).

Indentation

  • All files are indented with four spaces – no tabs.
  • The maximum (non-strict) limit of characters in one line is 120.
  • A line broken up into multiple lines are indented twice (= 8 blank spaces).

    <?php
    function iAmAVeryLongFunctionName (LongClassName $longClassName,
        VeryLongClassName $veryLongClassName)
    {
        // ...
    }

Parentheses

  • Opening and closing braces belong on a new line.

    do
    {
        // ...
    }
    while (/* ... */);
    
    if (/* ... */)
    {
        // ...
    }
    elseif (/* ... */)
    {
        // ...
    }
    else
    {
        // ...
    }
  • Only one statement per line. (The only exceptions are language constructs like for-loops).
  • Comments belong into their own line, with the only exception being argument comments on calls with multiple arguments.
  • Function declarations: a single whitespace belongs between the function name and the parentheses.

    function test ($arg)
    {
        // ...
    }
  • Function call: no whitespace between the function name and the parentheses.

    test(5);

Calls with Config Parameters

Function calls, which use arrays or js-objects, can be written in a special way. Arguments are being listed normally and the array argument is being indented. This way of listing the arguments is only permitted if the array/object is the first or last parameter and if it’s the only array/object in the list of arguments.

PHP:

<?php

$this->render("@MyBundle/template.html.twig", [
    "var1" => "a",
    "var2" => "b",
]);

$this->someMethod([
    "var1" => "a",
    "var2" => "b",
], $a, $b);

JavaScript:

$.ajax({
    a: 1,
    b: 2
});

$("a").animate({
    height: 20,
    width: 20
}, 200, onComplete);

This rule is only optional and these examples may be written in a longer form too. This rule becomes mandatory after the arguments contain multiple arrays/objects.

someFunction(
    {
        a: 1,
        b: 2
    },
    {
        c: 3,
        d: 4
    }
);

$("a").animate(
    {
        height: 20,
        width: 20
    },
    200,
    onComplete
);

Versioning

Own projects use Semantic Versioning.

For packages installed via package managers (like Composer, npm, etc…) that use SemVer, use the short form of the version number: ^2.4 (to get all versions compatible with 2.4).

URIs

  • All URIs only use lowercase characters and words divided by a hyphen.
  • This also includes static directly accessible assets like images, css- and js-files.
  • The only exception to this are files automatically generated by an external tool.

Universal Naming Conventions

  • Classes, Interfaces, Traits and Namespaces: StudlyCaps
  • Methods, Properties and Variables: camelCase
  • Constants: ALL_UPPER_CASE
  • Keywords are written in lowercase: true, false, null

Example:

<?php
$storeSomething = MyClass::VARIABLE_ID;

$object = new \Becklyn\Test\MyClass();
$object->doSomething();

Classes, Interfaces, Traits

  • extends and implements (in this order) have to be written within the same line as the class name.

    class Test extends A implements B, C
    {
        // ...

Methods

  • Access modifiers have to be applied everywhere possible if the programming language allows them.

    <?php
    namespace Vendor\Package;
    
    class ClassName
    {
        public function foo ($arg1, &$arg2, $arg3 = [])
        {
            // method body
        }
    }
  • Long argument lists are allowed to be split into multiple lines. Each argument as well as the closing parentheses have to be written on their own line.

    <?php
    class ClassName
    {
        public function aVeryLongMethodName (
            ClassTypeHint $arg1,
            &$arg2,
            array $arg3 = []
        )
        {
            // ...
        }
    }

Properties

  • Access modifiers have to be applied to every property if the programming language allows them.

    <?php
    class Test
    {
        public $test;
    
        protected static $abc;
    }

Abstract, Final und Static

The following order has to be kept:

  1. final
  2. Access modifier
  3. static
  4. (function)
  5. Name

Method- and Function Calls

  • No whitespace between function name and parentheses.

    <?php
    test(5);
    $fibonacci->calc(10);
  • Long argument lists are allowed to be split into multiple lines. Each argument as well as the closing parentheses have to be written on their own line. Comments after each argument are allowed.

    <?php
    imagecopyresampled(
        $newImage, // $dst_image,
        $resource, // $src_image
        $destX,    // $dst_x
        $destY,    // $dst_y
        $srcX,     // $src_x
        $srcY,     // $src_y
        $destW,    // $dst_w
        $destH,    // $dst_h
        $srcW,     // $src_w
        $srcH      // $src_h
    );

Other Language Constructs

Switch / Case

  • case is indented, as well as their body:

    <?php
    
    switch ($a)
    {
        case "a":
            $b = 1;
            break;
    
        case "b":
            $b = 2;
            break;
    
        default:
            $b = 0;
            break;
    }
  • A default case should to be defined. This can be a fallthrough of another case:

    <?php
    switch ($type)
    {
        case 1:
            // ...
            break;
    
        case 2:
            // ...
            break;
    
        case 3:
        default:
            // ...
            break;
    }
  • If all case-bodies are simple, they can be defined in the same line as the case they belong to and are indented into one column:

    <?php
    switch ($type)
    {
        case 1:     return "a";
        case 11:    return "b";
        case 111:   return "c";
        case 1111:
        case 1112:  return "d";
        case 11111:
        default:    return "e";
    }

Ternary Operator (?:)

  • The ternary operator is being split into three lines, where the last two line are being indented once:

    $variable = condition
        ? foo()
        : bar();
  • The literal should be put into the last line if only one of the return values is a literal:

    $variable = condition
        ? luckyNumberLukas()
        : 7;
  • Alternatively the whole ternary can be written in one line, provided the ternary itself is a simple one (e.g. only returns literals):

    $variable = condition ? 3 : 4;

Code Comments

  • PHP: all code must be commented with PHPDoc.
  • JS: all code must be commented with JSDoc.
  • SCSS: reusable mixins must be commented with SassDoc.

Package Manager

All php projects must use composer. All JS projects must use npm. Own, reusable components should be published over composer or npm.

The lock files of package managers should be committed into the project repository. The lock files of reusable libraries must not be committed.

PHP (Composer)

  • Own components have to be published on either Private Packagist or on Github.
  • The following properties are being set in the composer.json:
{
    "type": "project",
    "license": "proprietary",
    "require": {
        "php": "^7.2"
    },
    "config": {
        "preferred-install": {
            "*": "dist"
        },
        "sort-packages": true
    }
}
  • The php version has to be altered correspondingly to the version the package has to run on.
  • All extraordinary packages have to be listed as dependency like "ext-sodium": "*".
  • The order of the dependencies have to follow the following example:

    1. "php"
    2. All extensions, if there are any: "ext-..."
    3. All other dependencies, ordered alphabetically.
  • Composer uses the scheme of the previous example automatically if the following property has been set: "sort-packages": true.

JavaScript (NPM)

  • Own Components have to be published on NPM. Ideally under the namespace @becklyn/….
  • For projects (as opposed to libraries) the "private": true flag has to be set in the package.json file. name and version are omitted.

PHP

  • The shorthand for arrays (["abc" => 123]) has to be used instead of the long version (array("abc" => 123))

Strict Types

The β€œstrict types”-mode has to be used. The declare-statement is written after the opening <?php-tag on the same line:

<?​php declare(strict_types=1);

class TestClass
{
    // ...
}

Namespaces

  • Source code must use namespaces. No names are defined in the global scope.
  • No leading \ before the class name of a use statement.

    <?php
    use Test\Example;
  • All names have to be imported.
  • Exceptions:

    • Classes in the global scope mustn’t be imported: \Exception
    • Functions in the global scope have to be called with their FQN like: \array_map(...)
  • Namespace scheme for general projects: \Becklyn\$Component\...

Annotations

If annotations are used they are handled like class constructors, which means the parentheses belong to the constructor call:

<?php

/**
 * @Test()
 */
public function test ()
{
}

Autoloader

PSR-4 is used as autoloader. The tests used in the autoloader in composer have to be split:

{
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "App\\Tests\\": "tests/"
        }
    }
}

JavaScript

  • All statements have to be followed by a semicolon.
  • Promises should be used instead of callbacks.
  • Use kaba for building projects.
  • We use ES 2015 and all stage 3 syntax constructs.

    • Always use arrow functions.
    • When in doubt one can always look up if it’s supported in kaba-babel-preset.
  • The code style has to pass the kaba linting.
  • The code should be written in TypeScript.
  • Private methods mustn’t be prefixed with a _.
  • Private methods not written in TypeScript must have JSDoc annotated with @private.

    class MyClass
    {
        _privateFunction ()
        {
            // BAD
        }
    
        /**
         * @private
         */
        privateFunction ()
        {
            // GOOD
        }
    }

HTML

  • All HTML tags have to be indented.
  • No inline styles.
  • All HTML tags und and attributes must be written with lowercase characters.
  • The id attribute has to be unique.
  • HTML5 Doctype has to be used: <!doctype html>
  • HTML should be used semantically. (Example: avoid clickable <span> tags and use a styled <button type="button"> tag instead).
  • Don’t use encoded elements. Use € instead of &eur; exceptions being HTML entities i.e. &amp;, &lt; and &gt;).
  • Use double quotes (") on HTML attributes.
  • HTML 5 tags can and should be used.

    • <main></main> should only be used once in the entire document.
  • The closing slash for self closing tags has to be avoided.

    <br> instead of <br />
    
    <img src="..."> instead of <img src="..." />
    
    <meta> instead of <meta />
  • The content of the head tag should start with the following html tags in the order shown below:

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>...</title>
  • The lang attribute has to be set on the <html> tag.
  • JavaScripts should always be loaded with the defer attribute set and just before the closing <body> tag.

    • Exception: tracking scripts. Those should be loaded asynchronously just before the closing <head> tag.
  • If available, external scripts and resources have to be integrated via https.

SCSS

  • The maximum nesting depth of 3 selectors (excluding: pseudo classes) shouldn’t be exceeded.
  • Global variables have to be avoided if they aren’t containing global values.
  • Style definitions with !important are not allowed. The only exception to this are style definitions overwriting externally integrated styles which already use !important.
  • Selectors relying on the id attribute (#test) are not allowed.
  • * selectors are not allowed. The exception to this are global resets and generic sibling selectors (> * + *).
  • Unqualified attribute selectors should be avoided (a class should be used instead).

    /* GOOD */
    input[type="text"],
    a[href^="http://example.org/"],
    .test[data-tooltip] {
        /* ... */
    }
    
    /* BAD */
    [type="text"],
    [href^="http://example.org/"],
    [data-tooltip] {
        /* ... */
    }
  • If possible 0 should be written without a unit.
  • Leading 0 before a decimal point should be avoided as well.
  • A semicolon has to be written even after the last style declaration of a block.

Formatting

  • Only one selector per line; multiple selectors should be split and written into multiple lines.

    /* GOOD */
    a,
    input[type="text"],
    .test-element {
        color: red;
    }
    
    /* BAD */
    a, input[type="text"], .test-element {
        color: red;
    }
  • One declaration per line.

    /* GOOD */
    a {
        color: red;
        background-color: blue;
    }
    
    /* BAD */
    a {
        color: red; background-color: blue;
    }
  • All values have to be written with lowercase characters.
  • Colors are either being defined by their shortest hex code and with lowercase characters, or via the color functions rgba() and hsl().
  • Double quotes should always be used.
  • Attribute selectors like input[type="text"] have to be written with double quotes.
  • Opening braces belong on the same line as their selector.
  • There has to be a single space between selectors and opening braces.
  • The content of a block has to be indented once.
  • Closing braces have to be aligned with the start of their selectors.
  • All names including but not limited to class names, variable names, mixin names and placeholder names have to be written in kebap-case

    .page-tree {
        .page-tree-title {
            // ...
        }
    }
  • Modifier classes should be prefixed with is- or has-. The prefix may be modified to satisfy the English grammar.

    .widget {
        &:hover,
        &.is-hover {
            // ...
        }
    
        &.is-selected {
            // ...
        }
    
        // modified prefix
        &.has-children {
            // ...
        }
    }

Order

The order within an element is as follows:

  1. @extend
  2. Local variable definitions
  3. @include without a body
  4. Definitions of this element
  5. @include with a body.
  6. Modifiers for this element (e.g pseudo classes) and nested child definitions sorted into an understandable order.

Example:

.some-selector {
    @extend .placeholder;

    background-color: red;
}

.some-other-selector {
    $my-var: 123;
    $another-var: 234;

    @include example-mixin;
    @include example-mixin-with-parameters($my-var, $another-var);

    border: 1px solid red;
    color: red;
    background-color: white;

    @include example-mixin-with-a-body {
        display: none;
    }

    @include example-mixin-with-a-body-and-parameters($my-var, $another-var) {
        display: none;
    }

    a {
        // ...
    }

    &:hover {
        // ...
    }

    &.is-selected {
        // ...

        a {
            // ...
        }
    }
}

Style definitions may be grouped to improve readability:

.selector {
    /* Positioning */
    position: absolute;
    z-index: 10;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;

    /* Display & Box Model */
    display: inline-block;
    overflow: hidden;
    box-sizing: border-box;
    width: 100px;
    height: 100px;
    padding: 10px;
    border: 10px solid #333;
    margin: 10px;

    /* Other */
    background: #000;
    color: #fff;
    font-family: sans-serif;
    font-size: 16px;
    text-align: right;
}

Long comma separated values may be split up into multiple lines. If this is done every line may only contain one value indented once beginning with in the first line after the property name.

.selector {
    background-image:
        linear-gradient(#fff, #ccc),
        linear-gradient(#f3c, #4ec);
    box-shadow:
        1px 1px 1px #000,
        2px 2px 1px 1px #ccc inset;
}

The definitions for the element should be grouped and roughly in the following order:

  1. content
  2. Core properties (display, padding)
  3. Positioning (position, left, …)
  4. Coloring (background, text, border)

Directory Structure

All files excluding the entry files have to be prefixed with an underscore _.

The directory structure looks like this (logically grouped and not ordered alphabetically):

.
└── scss
    β”œβ”€β”€ base/                   <-- base data (fonts, global reset, %placeholder definitions, etc..)
    |   β”œβ”€β”€ _content.scss       <-- .content class
    |   β”œβ”€β”€ _fonts.scss         <-- font definitions
    |   └── _global.scss        <-- global definitions
    |
    β”œβ”€β”€ helper/                 <-- helper files. Mustn't have side effects
    |   β”œβ”€β”€ _grid.scss          <-- (optional) if a grid layout exists
    |   β”œβ”€β”€ _mixins.scss
    |   └── _variables.scss
    |
    β”œβ”€β”€ atom/                   <-- common atom/molecule/organism structure
    β”œβ”€β”€ molecule/
    β”œβ”€β”€ organism/
    β”œβ”€β”€ template/               <-- page templates
    |   └── __default.scss      <-- default template - is always active and has no selector
    |
    β”œβ”€β”€ vendor/                 <-- external libraries
    └── app.scss                <-- entry file

Atomic Design

Components should be simple, modular and reusable hence why those components should be broken into the smallest reusable pieces (atoms).

All Components

A component is only allowed to change/affect the positioning of other containing components. It’s not allowed to change its own position using properties like position pared with top, left, right, bottom or negative margin. An exception to this rule is centering itself using margin (e.g. margin-left: auto; margin-right: auto; or transform: translate(-50%);).

Naming

Sub components should keep use the name of the parent element as prefix (maybe with a plural -> singular change (like test-entries -> test-entry or even test-list -> list-entry). To avoid having overly long names, the prefix can be shortened to a β€œunique enough” prefix.

Nesting

A component shouldn’t have more than 3 levels of nesting within the components class. Notable exceptions here are navigation components, which can turn off the corresponding stylelint rule.

.list {
    .list-entry {
        .entry-action {
            .action-icon {
                //some style
            }
        }
    }
}

Base

content
  • Is a marker class for running text and rich text content.
  • Only sets internal margins (e.g. for li + li).
  • Doesn’t set a background or anything that would make it visually hard to re-use this class inside other components.
fonts
  • Contains all @font-face definitions
global
  • Sets the global font-size
icon
  • Contains all styling related to icons used within the project. To reduce component nesting and due to the nature of modern design, icon has been promoted to a base component in order to prevent having to promote every component to its next higher component type.
placeholder
  • Typically used for grouped properties that will be re-used a lot throughout the project as an effort to reduce the compiled CSS’s total file size.
  • Reuseable placeholders must be implemented through a corresponding mixin.
// base/__placeholders.scss
%content-container {
    width: 100%;
    max-width: $content-width;
    margin: 0 auto;
}

// helper/_mixins.scss
@mixin content-container {
    @extend %content-container;
}
  • Only the mixin to the corresponding placeholder may be used within the app.
  • Typically used, if there is a lot of content in the placeholder (like fully responsive components, that also define the break points, etc..)
  • must only be imported once as a direct import in the scss’s entry file

Hierarchy

  • by default any component is an atom
  • if a component uses another atom or molecule it gets promoted to one level above the components it uses.

Templates

  • All template files have to be prefixed with template- in their class name.
  • This means that the _page.scss file has a template class of .template-page { ... } as the root class.
  • The __default.scss shouldn’t set styling globally, but instead use a marker class on the body to apply the global styles. The project name may be used for it’s name.
  • Container for the page (like Material Design)

SCSS Entry File

Based on this structure and the design principle the entry file would look something like this:

// Reset
@import "~samos/reset";

// vendor
@import "vendor/abc";

// Base
@import "base/fonts";
@import "base/global";
@import "base/content";

// Atoms
@import "atom/...";

// Molecules
@import "molecule/...";

// Organisms
@import "organism/...";

// Templates
@import "template/_default";
@import "template/...";
  • Vendors should always (if possible) be included via NPM as a dependency. These dependencies can be imported with @import "~package/file";.
  • The imports have to be ordered like in the example above and are sorted within those blocks alphabetically. The only exceptions are reset and base, which are sorted in the exact order as shown above.

SQL

  • All identifiers: snake_case
  • Keywords and functions: CAPS
  • Required fields in tables:

    • Primary Key: id
    • All tables have to have a Primary Key
  • Naming schema:

    • tables: plural
    • field names: singular
    • Fields of type boolean should always have a prefix like is_ or has_. This shouldn’t be translated into the mapping in doctrine. Example: property name in doctrine $active β†’ field name in MySQL is_active.
  • Don’t use redundant field names like product_name in the table products. Simply use name instead.

Data Types

The correct data type should always be used. Common mistakes include but are not limited to:

  • Zip codes should always be stored as a string of characters instead of a number. This has to do with some countries having letters or leading zeros in their zip code.
  • House numbers should always be stored as a string of characters because they can include symbols e.g. / or -.
  • Numbers with a fixed number of digits after the comma should be stored as integers. Normally this is just a transformation to the lowest possible unit, for money it’s β€œ5€” = β€œ500ct”. This measure is necessary to prevent rounding problems on mathematical operations.

Display Width

As far as the ORM does not specify it differently the display width should always be set to the maximum (unsigned) amount of digits possible for the specific numeric type:

  • TINYINT(3) (range of values: [-128, 127] / [0, 255])
  • SMALLINT(5) (range of values: [-32.768, 32.767] / [0, 65.535])
  • MEDIUMINT(8) (range of values: [-8.388.608, 8.388.607] / [0, 16.777.215])
  • INT(10) (range of values: [-2.147.483.648, 2.147.483.647] / [0, 4.294.967.295])
  • BIGINT(20) (range of values: [-9.223.372.036.854.775.808, 9.223.372.036.854.775.807] / [0, 18.446.744.073.709.551.615])

String Types

Binary types should only be used to save encrypted data. Other binary types don’t usually belong in the database.

The following values are just reference values. If in a certain case a certain amount of characters makes sense, you should use that exact amount.

  • CHAR if there is a fixed amount of characters (Example: ISO codes of countries)
  • VARBINARY/BINARY/BLOB if encrypted data is being stored.
  • VARCHAR(50) for external, long IDs (i.e.: Facebook-IDs, Twitter-IDs, etc.)
  • VARCHAR(254) short texts
  • VARCHAR(1000) long texts
  • TEXT texts with a unlimited length of characters.

Twig

  • Variables: camelCase
  • everything else (functions, filters, test, block names, …): snake_case

Symfony Projects

Conventions

  • YAML files have the .yaml file extension.
  • The files are named services.yaml and routes.yaml if they are needed.

Controller

  • Controller have the Controller suffix (Example: ExampleController).
  • All public methods of a controller are considered actions, but don’t have the Action suffix.

Config

  • All config files in config/packages contain exactly one top-level entry.
  • The file is named after the top-level entry.

Dependency Injection

  • The full service container must never be injected. Inject a service locator instead.
  • The usage of autowire and autoconfigure is allowed.
  • bind in _defaults should be avoided, set these values explicitly.
  • Use auto loading of classes (via resource), but set appropriate exceptions (like /Entities/, /Resources/, etc..).

Project Directory Structure

The directory structure is based on the Symfony 4 directory structure:

.
β”œβ”€β”€ assets/         <-- assets that needs to be processed (SCSS, TypeScript, JavaScript, ...)
β”œβ”€β”€ bin/
β”œβ”€β”€ build/
|   β”œβ”€β”€ css/        <-- compiled from SCSS
|   β”œβ”€β”€ font/       <-- fonts
|   β”œβ”€β”€ img/        <-- images
|   └── js          <-- compiled from TS/JS
β”œβ”€β”€ config/
β”œβ”€β”€ node_modules/   <-- NPM vendors
β”œβ”€β”€ public/         <-- web directory root
β”œβ”€β”€ src/            <-- components
β”œβ”€β”€ storage/        <-- project specific storage (everything outside of the main framework)
β”œβ”€β”€ templates/      <-- templates
β”œβ”€β”€ tests/          <-- all test cases
β”œβ”€β”€ var/
|   β”œβ”€β”€ cache/      <-- (Symfony Core: cache)
|   β”œβ”€β”€ logs/       <-- (Symfony Core: logs)
|   └── sessions/   <-- (Symfony Core: sessions)
β”œβ”€β”€ vendor/         <-- Composer vendors
└── ...             <-- default files

An entry in the AssetsBundle is made for the namespace @app which corresponds to the β€œbuild” directory.

  • Avoid grouping components by their function (\Service\ oder \Helper\). Group by component instead (\File\ oder \Path\).
  • Exceptions:

    • Entity
    • Listener
    • Model
    • Twig
  • The template files as well as the directories they are located in are written in kebab-case (e.g. admin\my-entity\my-action.html.twig).
  • The use of SensioFrameworkExtraBundle with the @Security() annotation is allowed.
  • The use of the @Template() annotation is prohibited.

Build

kaba is being used for the build process.

package.json

  • All build dependencies (especially kaba) are to be listed under devDependencies.
  • Set "private": true.
  • Don’t use name and version.
  • Version number should comply to semver (^).

Vendors

Vendors should always be included via NPM / Composer and/or the build. The vendors shouldn’t be loaded explicitly as own files.