Skip to main content

Overview

Mindsets:

  • Leave things better than you found it.
    • For example by keeping the dependencies updated.
      • For PHP: composer outdated
      • For JS and CSS: yarn upgrade
    • Also, by adding missing type information in legacy code.
  • Keep it simple, always favor simplicity over complexity. Try not to over engineer.
  • Strive to use descriptive names, with an affirmative meaning for variables, clases, methods, etc.
    • Prefer longer and descriptive names over short ones
  • While it may sound controversial, default to not using comments.
    • If you think that something needs a comment, first see if the code is not yet clear enough and needs improvement.
    • If the code is already clear and you feel the comment would add value, then make sure the comment explains WHY and not WHAT.

General

  • Prefer single quotes ' over double quotes "
  • Use 4 spaces instead of tabs for all files
    • Except for files like YML that require 2 spaces
  • Default to strict comparisons ===
  • Use camelCase for:
    • Variables
    • Functions and methods
    • Macros
  • Use CapitalizedCamelCase for:
    • Classes and their file names
  • Use snake_case for:
    • Config vars: config('reader.coniglio_url')
    • Query parameters: /library/filter?latest_issues=latest_issues
  • Use kebab-case for:
    • Localizations: trans('dashboard.plan-stats.issues-stats')
  • Keep indentation to the minimum.
    • Makes code easier to follow and reason about.
    • Early returns could help with this.
  • Prefer more functional loops such as map, each and reduce, over basic ones like for and foeach.
  • If two things work together and there's no clear reason why they can not share the same name, then it's probably a good idea to use the same name.
  • If you're just returning a boolean, then opt for a ternary and leave the boolean implicit:
# Instead of this
if ($bla === $ble) {
return false;
} else {
return true;
}
# Or this
return $bla === $ble ? true : false;
# Do this
return $bla === $ble;
  • Avoid nesting ternary operations at all costs, it makes code hard to understand and can lead to unexpected bugs.
  • When adding code from an external source by copy/paste (like from Stack Overflow), remember to always add a comment that explains what the code does, why we need it and where you took it from.

When creating new projects

  • Use the generic .editorconfig. Your text editor or IDE might need a plugin for it.
  • Use the generic .gitignore. We maintain this file up to date with the patterns we most usually need.

Things to take into account:

  • Take into account that this is a multilanguage platform, all string must be localizable.

CI / CD

  • For new projects, always include the base GitLab CI template
  • Ensure the CI passes after every push and, specially, before deploying.

PHP

JavaScript

  • Prefer yarn over npm.
  • Prefer let and const over var. Ref 1,
  • Prefer import over require, update legacy references if you encounter any.
  • Always add the file extension when importing a local file: import cacheResources from './cacheResources.js';.
  • Use map, reduce and filter wherever possible, and try to avoid using for, for...in, for...of loops.
  • Use the entire word error when trackign errors client side:
try {
// ...
} catch (error) {
logger.error('Error doing...', { error });
// It's the same as logger.error('Error doing...', { error: error });
}

CSS

Vue.js

  • Prefer single file components
  • The <style> element must be SCSS and scoped, like this: <style lang="scss" scoped>
    • Clear event listeners on component destroy with $off, source
  • Always use kebab-case for event names, source
  • Always use :key in v-for loops, source
  • Use PascalCase or kebab-case for single file components, source
  • Use prefix for Base Component names, source
  • Use PascalCase for component names in JS, source
  • Prop names should always use camelCase during declaration, but kebab-case in templates, source
  • Use Component options order from style guide, source
  • Never use v-if on the same element as v-for, source
  • For components file name we use CapitalizedCamelCase, the same for all uses in other Vue components.
// Instead of this
components: {
'export-button': require('../../reusable-components/ExportButton.vue').default,
},
// Do this
import ExportButton from '../../reusable-components/ExportButton.vue';
components: {
'ExportButton',
},
  • When working with dynamic CSS clases and variables, always attempt to use the exact same word, or combination of words, for the class name and the variable name.
// Instead of this
<div class="DownloadButton" :class="{ downloaded, 'download-disabled': disabledButton }" v-if="canDownload">
// Do this
<div class="DownloadButton" :class="{ downloaded, 'disabled': disabled }" v-if="canDownload">
// Which can also be written as
<div class="DownloadButton" :class="{ downloaded, disabled }" v-if="canDownload">

Laravel

  • Migrations move forward, except strictly required there's no need to use the down() method in migrations.
  • Always put the tenant_id column in the first place.
  • Always use Laravel constructs to access data normally inside PHP global variables like $_POST, $_GET, $_SERVER, $_REQUEST and $_COOKIES.
  • Always validate request's data before using or storing it, we cannot trust what happens outside the app. It prevent us from 2 main scenarios:
    • Our clients (web frontend, mobile app) misbehaving and submiting broken data.
    • Malicious actors trying to hack in or just cause damage.
  • When creating a new controller, default to also creating a form request to encapsulate authorization and validation.
    • The same goes for refactoring old code, try to migrate the validation and authorization to a form request. Adhering to our principle of leaving things better than you found them.
  • Never use $request->all() apart from logging it or validation, we never knwo what does the request contain. There's safer alternatives like:
    • $request->only('...', '....').
    • $request->validated().
    • $validated = $request->validate(['...']) and then use $validated.
  • If you add something to a column in tenants_meta (or any other column with a JSON data type), make sure to also add a migration that makes that change in all rows.
  • If the Controller only has 1 public method (like an ExportUsersCsvHandler case), the we preferably use an __invoke type of method and name the Controller as a Handler.
  • If possible, use whereBetween instead Eloquent's whereYear, whereMonth or whereDay methods. The performance difference is outrageous because whereBetween can use the index of a date column, the others can't.
# Instead of this:
->whereYear('publication_date', 2019)
# Do this:
->whereBetween('publication_date', [
new Carbon('first day of jan 2019'),
new Carbon('last day of dec 2019'),
])
  • Use Collections wherever possible and try to avoid using for and foeach loops
  • Prefer global helper functions over Facades. For example logger() over Log::info() and cache()->get() over Cache::get() , but 👇
  • Prefer container injection over everything else.
# Instead of this:
public function index()
{
request()
...
}
# Do this:
public function index(Request $request)
{
$request
...;
}
  • When in need of a Carbon object, prefer the global now() helper instead of Carbon::now().
  • In farfalla, if possible use the tenant() global helper to access tenant data. Eg: tenant('id') instead of app('CurrentTenant')->get()->id
  • There's no need to explicitly define the : Response return type in Controller methods. In a Controller where we only use the default rest methods, it's assumed that we're returning a response.
  • If you're returnig a redirect, do it like this:
# Instead of this
return redirect()
# Do this
return redirect()->to('https://...')
# Or if you're returning a route
return redirect()->to(route('login'))
  • If you're returnig a redirect with an intended url:
# Instead of this to put the url
Session::put('url.intended', $request->fullUrl());
# Do this
redirect()->setIntendedUrl($request->fullUrl());
# And get it as this
return redirect($request->session()->get('url.intended'));
  • If you need to make a request to an external service, try to always use Zttp. If that's not enough, use Guzzle. Manually managing curl is the last resource. This is because we favor readability and practicality.
  • Using DB::raw or similar raw database queries is always a last resource and must be justifiable, either by performance, practicality or just because there's simple no other alternative. This is not only because we favor readability and practicality, but also because using raw DB queries can get complicated and lead to security issues that are simply not possible when using the ORM.
  • Name cookies in hyphen-case.
  • Use $request->boolean('is_admin'); to retrieve boolean properties from the request. The boolean('...') method returns true for 1, "1", true, "true", "on", and "yes". All other values will return false:
  • Use the entire word exception when trackign exceptions server side:
try {
# ...
} catch (Exception $exception) {
logger()->error('Exception doing...', [
'exception' => $exception,
]);
}

Testing

To keep in mind when testing

  • Test should be easy to follow and quick to understan, just like regular code. Try to avoid generating complex scenarios if it's not absolutely necesary.
  • Preferably, use dynamic random data. For example by using Faker.
  • All tests will run on tenant 1 by default:
    • farfalla.test in local env.
    • stagingapp.publica.la in staging.
    • app.publica.la in production.
  • We are using the same MySQL database as the app uses for regular requests, we create a transaction and rollback for every test.
  • It wil use the default .env file, it could also override variables in phpunit.xml.
  • Use /** @test */ to indicate tests from PHPUnit.
  • Use camel_case test name, like reuses_user_if_emails_match_and_uuid_is_null and do not use the test prefrix.

X

Graph View