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
andimplements
(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:
final
- Access modifier
static
- (
function
) - 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 thecase
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
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:
"php"
- All extensions, if there are any:
"ext-..."
- 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 thepackage.json
file.name
andversion
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 ause
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(...)
- Classes in the global scope mustnβt be imported:
- 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.&
,<
and>
). - 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.
- Exception: tracking scripts. Those should be loaded asynchronously just before the closing
- 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()
andhsl()
. - 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-
orhas-
. 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:
@extend
- Local variable definitions
@include
without a body- Definitions of this element
@include
with a body.- 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:
content
- Core properties (
display
,padding
) - Positioning (
position
,left
, β¦) - 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 abase
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
ormolecule
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 thebody
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
- Primary Key:
-
Naming schema:
- tables: plural
- field names: singular
- Fields of type boolean should always have a prefix like
is_
orhas_
. This shouldnβt be translated into the mapping in doctrine. Example: property name in doctrine$active
β field name in MySQLis_active
.
- Donβt use redundant field names like
product_name
in the tableproducts
. Simply usename
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 textsVARCHAR(1000)
long textsTEXT
texts with a unlimited length of characters.
Twig
- Variables:
camelCase
- everything else (functions, filters, test, block names, β¦):
snake_case
Symfony Projects
- The use of Symfony Flex is encouraged.
- The use of Becklyn Assets Bundle is mandatory.
Conventions
- YAML files have the
.yaml
file extension. - The files are named
services.yaml
androutes.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 theAction
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
andautoconfigure
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 underdevDependencies
. - Set
"private": true
. - Donβt use
name
andversion
. - 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.