Amoureux de Behat

Je viens de passer une semaine à coder des tests behat pour un projet Drupal !

J’en suis tombé amoureux. C’est vraiment l’outil qui manquait pour rédiger des tests : les simpleTest et compagnie, trop complexe à écrire. Cette fois, c’est aussi simple qu’écrire une phrase de base en anglais, et ça marche !

La première fois que j’en ai entendu parler, j’ai négligé Behat parce que je trouvais ça « trop magique pour être vrai ». Et bien non ! Ca fonctionne, et même bien. Et la courbe d’apprentissage est carrément raisonnable. Votre premier test peut s’écrire rapidement, et ensuite vous rentrez dans les subtilités au fur et à mesure des besoins. Le seul bémol peut-être : la doc n’est pas encore très mature.

Combiné à gitlab-ci pour automatiser les tests de non régression lors d’un merge request : que demande le peuple ?

Perl 6 est enfin sorti !!

Ça y est ! Perl 6 est sorti ! Incroyable mais vrai. Et, sur le papier du moins, l’attente en valait la peine.

Je n’ai jamais codé en perl, mais, si j’ai l’occasion je compte bien m’y essayer, essentiellement pour deux raisons :

  • méta-programmation : on peut définir Lua ou Python comme sous langages de « perl6 » !
  • programmation réactive : une fonction est déclenchée automatiquement quand une variable est modifiée

Bien sûr, il y a tout le reste, programmation object/fonctionnelle.

Cela fait des années que je surveille les les langages avec méta-programmation. Les derniers qui m’ont séduits sont Dao et Nim, ainsi que Red.

Red est une reprise de Rebol en open-source. Intéressante mais pas encore mature. Dao serait sympa, mais la communauté est plus que limitée, il ne semble pas prendre son envol. Nim en revanche fait le buzz mais semble un peu complexe, quoi que puissant.

Perl 6 se rajoute donc à la liste. Toute la question est de savoir s’il est facile à apprendre, et … à lire : avoir un langage très puissant c’est intéressant, mais si seul l’expert qui a codé peut relire son code tellement c’est ésotérique ça ne sert pas à grand chose. C’est pour cela d’ailleurs que c++ reste marginal par rapport au C : bien que plus puissant, pour comprendre le code des autres, il faut avoir investit beaucoup de temps. Quand un gars fait des héritages multiples de classes abstraites génériques (templates) on se prends vite la tête 😉

A voir donc si Perl 6 réussi à faire sa place : quand est-ce qu’on recode Drupal en perl ? lol

Drupal 8 Outils du développeur

Comme en témoigne mon petit module hello world, l’API et l’architecture de Drupal 8 sont autrement plus complexe que celle de Drupal7.

Tout programmeur est amené a se poser la question : si je me trompe, comment vais-je debugger mon code ?

Drupal 7 se base sur certains principes fondamentaux simples et universel : les hooks (qui sont des sortes de callbacks améliorées, reproduisant en PHP une forme d’AOP), les tableaux associatifs (et rarement des objets). La plupart des cas sont réglés par un bon vieux « dsm » (du module « devel » qui reste toujours présent dans Drupal 8) qui permet d’afficher le contenu de n’importe quelle variable, façon « krumo » :

Si cette méthode reste valable en Drupal 8, elle peut se révéler trop rudimentaire. Quand on se trompe de nom de classe à hériter ou d’annotation, un dsm ne nous sauvera pas. Il va nous falloir d’autres outils. Je vous en présentes ici les plus utiles.

Composer mananger

Pour l’installer il y a plusieurs étapes :

drush en -y composer_manager
drush composer-manager-init
cd core
composer drupal-update

Permet de lister les packages installés et leurs versions.

Pour rentrer dans les entrailles de la bête :

vim core/vendor/composer/autoload_classmap.php

 

 

Quelques articles sur les problèmes de dépendances (quand 2 composants dépendent de 2 versions différentes d’une même librairie):

Je mentionne le Composer extension pour drush (pas trop compris l’intérêt) : https://drupal.org/project/composer

Petit exemple pour rajouter une dépendance à son module : mymodule/composer.json [attention il faut rajouter composer.json & le champ name est important pour être reconnu par composer manager]

{
  "name": "drupal/mymodule",
  "description": "An automaticcaly generated module",
  "license": "GPL-2.0+",
  "require": {
    "acquia/acquia-sdk-php-cloud-api": ">=0.9.0,<0.10.0",
    "monolog/monolog": "1.10.*"
  }
}

Plugin

Permet de lister les plugins au sens Drupal (eq. hooks regroupés en objets) – ne fonctionne plus à l’heure actuelle en beta12.

Le Web Profiler

Indispensable. C’est un outil qui vient de symfony2 et qui a été adapté à Drupal8.

Pour installer le web-profiler, actuellement (en béta7) il faut suivre plusieurs étapes. En effet, certains modules ne s’installent pas correctement si les dépendances « internes » (packages s2) ne sont pas résolues :

  • Installer drush 7.x
  • Installer le Composer Manager
  • Rafraîchir les dépendances :
    • drush composer-manager-init
    • /core composer drupal-update
  • Installer le WebProfiler

Drupal8 debug1

Le console logger

  • Installer le module « Consoller logger »
  • Lancer la console : drush runserver 2> /dev/null
  • Écrire dans la console : file_put_contents("php://stdout", sprintf("test"));

Debug Twig

  • Activer le debug Twig : sites/default/services.yml
  • {{ dump(text) }}
  • \Doctrine\Common\Util\Debug::dump($votreCollection);

La console Drupal

Semblable à la console symfony2, mais pour Drupal. Permet de générer des modules à la volé, d’avoir un canevas.

Pour l’installer :

curl -LSs http://drupalconsole.com/installer | php

exemple d’utilisation :

./console.phar generate:module

Generator UI

Similaire a la console, mais dédié à la génération de modules. Dispose de plus de templates que la console, mais pourrait être fusionné avec à terme :

Drupal8 - generator UI

Le module upgrader

Permet d’upgrader automatiquement un module drupal 7 en 8. Pour l’installer

drush dl drupalmoduleupgrader

cd drupalmoduleupgrader

composer update

drush en -y drupalmoduleupgrader

 

Transformer un drupal7 mono-site en multi-site avec des sous-répertoires

Voici la manière de transformer un drupal monosite en drupal multisite, sous la forme :

http://www.monsite.fr qui devient http://www.monsite.fr/site1 et http://www.monsite.fr/site2
(et non pas http://site1.monsite.fr et http://site2.monsite.fr – autre cas qui n’est pas traité ici).

1/ La conf d’apache
après le « DocumentRoot » du serveur :
Alias /site1 /var/www/drupalroot
Alias /site2 /var/www/drupalroot

bien entendu, il faut mettre le bon répertoire, celui qui pointe sur la racine de Drupal

2/ le fichier .htaccess dans /var/www/drupalroot
# site1 site
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} ^/site1/(.*)$
RewriteRule ^(.*)$ /site1/index.php [L,QSA]

# site2 site
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} ^/site2/(.*)$
RewriteRule ^(.*)$ /site2/index.php [L,QSA]

# default site
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^ index.php [L]

3/ le répertoire « sites »
à la racine de drupal faire:

cd sites
mv default localhost.site1
mkdir localhost.site2
ln -s localhost.site1 site1
ln -s localhost.site2 site2
cd site2
mkdir files
cp ../site1/settings.php
cp ../site1/default.settings.php
chmod 777 files settings.php

cd .  #la racine de Drupal
ln -s . site2
ln -s . site1

4/ le fichier sites.php
ici c’est un peu étrange, mais c’est « Drupal » :  http://www.monsite.fr/site2 et  http://www.monsite.fr/site1 doivent s’écrire  www.monsite.fr.site1 et  www.monsite.fr.site2
On enlève le http, et le / devient un .

<?php
$sites = array(
"www.monsite.fr.site1" => "site1",
"www.monsite.fr.site2" => "site2",
);

changer le base_url dans sites/site1/settings.php
$base_url = ‘https://www.monsite.fr/site1‘;

A ce stade l’ancien site doit fonctionner sous la nouvelle URL, mais le nouveau n’est pas encore prêt

5/ création de la nouvelle base
créer une nouvelle base de donnée vierge : site2_dev

6/ fichiers sites/site2/settings.php

$base_url = ‘https://www.monsite.fr/site2‘;
$databases = array (
'default' =>
array (
'default' =>
array (
'database' => 'site2_dev',

(Attention, dans le cas présent, on est en multibase, d’ou les 2 ‘default’ qui s’enchainent)

7/ installer drupal pour créer la base
https://www.monsite.fr/site2/install.php
suivre les étapes « bêtement ».

Sinon on peut tenter un drush :

drush si ftv_site1_back --notify -y --db-url=mysql://$username$:$password$@localhost/site2_dev --account-mail=admin@local.host --account-name=admin --account-pass=$passwordadmindrupal$

et voila ! normalement à ce stade, vous avez un copie du site fonctionnellement, sans données dedans.

Drupal duel : Drupal 7&8 side by side

Voici, mis cote à cote 2 modules hello-world en Drupal 7 et Drupal 8 (beta9) qui mettent en œuvre les concepts principaux qu’on retrouve dans la plupart des modules :

  • menus et chemins d’accès
  • création de block
  • création d’un formulaire
  • theming
  • accès aux « variables »

Comme vous pouvez le constater, les changements sont nombreux, mais au final :

  • c’est la même chose écrit différement (pour simplifier : on passe du fonctionnel à de l’objet)
  • si on omet les « namespaces », ce n’est pas foncièrement plus verbeux, mais le code est éclaté en 7 fichiers en Drupal8 au lieu de 3
Drupal7 Drupal8

hello.info

hello.info.yml

name = Hello world
description = Minimalist Hello World in Drupal 7
package = Example modules
core = 7.x
files[] = hello.module
name: Hello World
type: module
description: Minimalist Hello World in Drupal 8
package: Example modules
core: 8.x

hello.module

function hello_menu() {

hello.links.menu.yml

$items['helloworld'] = array(
  'title' => 'Hello world',
hello.main:
  title: Hello world
  route_name: hello.world
$items['admin/config/content/hello'] = array(
  'title' => 'Hello config',
hello.form:
  title: Hello config
  route_name: hello.form

hello.routing.yml

$items['helloworld'] = array(
  'page callback' => '_page_hello_world',
  'access callback' => TRUE,
hello.world:
  path: 'helloworld'
  defaults:
    _controller: '\Drupal\hello\Controller\HelloRouteController::index'
  requirements:
    _access: 'TRUE'
$items['admin/config/content/hello'] = array(
  'page callback' => 'drupal_get_form',
  'page arguments' => array('hello_config_form'),
  'access arguments' => array('access hello content')
hello.form:
  path: 'admin/config/content/hello'
  defaults:
    _form: '\Drupal\hello\Form\HelloForm'
  requirements:
    _permission: 'access hello content'
function hello_theme() {
  return array(
    'hello_text' => array(
    'template' => 'hello-text',
    'variables' => array('text' => NULL)),
  );
}
function hello_theme() {
  return array(
    'hello_text' => array(
    'template' => 'hello-text',
    'variables' => array('text' => NULL)),
  );
}

src/Controller/HelloRouteController.php

function _page_hello_world() {
  return array( '#markup' => '<p>Hello world page text</p>' );
}
namespace Drupal\hello\Controller;
use Drupal\Core\Controller\ControllerBase;
class HelloRouteController extends ControllerBase {
  public function index() {
    return array('#markup' => '<p>Hello world page text</p>');
  }
}

src/Plugin/Block/HelloBlock.php

function hello_block_info() {
  $blocks['hello'] = array(
    'info' => t('Hello world block title'),
  );
  return $blocks;
}
namespace Drupal\hello\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\block\Annotation\Block;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Session\AccountInterface;
/**
* Provides a 'Hello' block.
* @Block(
* id = "hello_block",
* admin_label = @Translation("Hello world block title")
* )
*/
function hello_block_view($delta = '') {
  switch ($delta) {
    case 'hello':
      $block['subject'] = t('Hello world');
      $block['content'] = theme('hello_text', array('text' => 
        variable_get('hello_value', 'hello')));
      break;
  }
  return $block;
}
class HelloBlock extends BlockBase {
  public function build() {
    return array('#theme' => 'hello_text', '#text' =>
      \Drupal::config('hello.settings')->get('hello_value'));
  }
}

src/Form/HelloForm.php

function hello_config_form() {
namespace Drupal\hello\Form;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class HelloForm extends FormBase {
  public function getFormID() {
    return 'hello_form';
  }
  public function buildForm(array $form, 
    FormStateInterface $form_state) {
  $form['hello_config'] = array(
    '#type' => 'textfield',
    '#title' => t('Who are you ?'),
    '#size' => 10,
    '#description' => t('Text for the hello world block.'),
    '#default_value' => 
       variable_get('hello_value', 'anonymous'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}
  $form['hello_config'] = array(
    '#type' => 'textfield',
    '#title' => t('Who are you ?'),
    '#size' => 10,
    '#description' => t('Text for the hello world block.'),
    '#default_value' => 
       \Drupal::config('hello.settings')->get('hello_value'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}
function hello_config_form_submit($form, $form_state) {
  variable_set('hello_value', 
    $form_state['values']['hello_config']);
}
public function submitForm(array &$form, 
  FormStateInterface $form_state) {
  $form_values = $form_state->getValues();
  \Drupal::service('config.factory')->getEditable('hello.settings')
->set('hello_value', $form_values['hello_config'])
->save();
}

drupal7-hello-world/hello-text.tpl.php

Hi from template : <?php print $variables['text']; ?>
Hi from template : {{ text }}

 

Wolfram Language : affaire à suivre

Après Mathematica, Wolfram Alpha (le moteur de recherche qui comprends les questions), après « A New Kind of Science » et son approche empirique et systématique des automates cellulaires, Wolfram Langage impressionne !!!

  • Syntaxe simple, à la Rebol/Lisp/4th/Logo.
  • Connection a Wolfram Alpha, et donc à un Big Data universel
  • Cloud, déployable, parallélisable (sur une ferme de RPi par exemple)

Cela semble être le GRAAL des programmeurs, toutes les meilleurs idées regroupées en un seul langage. Seul défaut, il n’est pas open-source.

En quelques lignes on peut calculer le plus court trajet pour parcourir les capitales Européennes, demander l’heure de couché du soleil locale, générer des graphes interactifs, etc…

Pourquoi et comment se former à Drupal ?

Pourquoi ?

J’ai la chance d’avoir la double casquette de chef de projet et formateur Drupal.

J’ai pu constater « de visu » les dégâts que peuvent faire de mauvaises pratiques sur un développement Drupal.

Par exemple, j’ai du intervenir en tant que consultant sur un projet ou l’on me demandait comment rajouter une fonctionnalité au site. Tout naturellement, j’ai conseillé d’installer un module (la manière de choisir ou pas un module est un autre sujet) qui répondait suffisamment bien à la demande. Mais là, je me suis rendu compte que le reste du site était développé de manière « non drupaliene » et que le module ne serait pas compatible avec le site.

Il ne faut pas sous-estimer la réticence des développeurs à apprendre un nouveau framework :

  • d’abord c’est un effort considérable, un investissement, qui n’est pas toujours perçu comme rentable (à juste titre parfois);
  • ensuite, il y a les goûts et les couleurs. Les choix techniques d’un framework ne sont pas toujours parfait, ni bien justifiés;
  • connaître une API c’est une chose, mais connaître les limites d’un framework, savoir contourner et anticiper les problèmes, ça demande énormément d’expérience, voire de la passion. C’est parfois plus simple de développer les choses soi même;
  • parfois aussi, Drupal n’est pas le meilleur choix, et le développeur en connaît un plus efficace (tout du moins partiellement, mais ça peut être suffisant pour ne pas avoir envie de s’investir)

Donc, les développeurs se forment parfois à contre-coeur, ou mal, ou n’ont pas le temps de le faire correctement. En tant que formateur, il faut en tenir compte. Les conséquences de l’absence de formation sont parfois (souvent) bien plus importantes que les 3 jours qu’on « perds » à les suivre.

Le ROI d’une formation sur un produit comme Drupal est très rapide.

Comment ?

Le rôle d’un formateur est multiple, mais en premier lieu il s’agit de :

  • s’assurer que tous les concepts de bases sont assimilés
  • donner un maximum d’autonomie aux stagiaires

Il faut donc une pédagogie adaptée à ce contexte (ce sera l’objet d’un prochain article – notez qu’il risque d’y avoir des bouleversement pour Drupal 8 qui sera assez différent à enseigner). Il faut être capable d’expliquer les forces et faiblesses de Drupal, et l’intérêt de s’investir dedans. Mais il faut surtout être capable de transmettre, non pas un maximum d’informations, mais un maximum de savoir-faire !

C’est pareil dans tous les domaines. Dans le bâtiment, ce qui à de la valeur, ce n’est pas de montrer comment jeter du crépît sur un mûr et passer la taloche dessus. Ce qui à de la valeur, c’est le petit geste qu’on à mis des années à affiner et qui fait qu’on se fatigue moins, qu’on est plus précis, et au final, qu’on travaille 2X plus vite qu’un débutant. Ce n’est pas la proportion théorique de sable / ciment / eau qu’il faut pour faire du béton, mais le coup d’oeil dans la bétonnière pour savoir s’il est suffisamment gras ou pas.

Tous les développeurs savent lire et peuvent de débrouiller avec une documentation, ou un « Pro Drupal Developpment« . Le formateur doit être capable d’aller à l’essentiel, de faire le tri dans la masse d’informations pour ne retenir que les plus pertinentes. Un formateur doit donc proposer une plus-value réelle.

Pour cela, il n’y a pas 10 000 solutions : il doit s’adapter à ses stagiaires. Mes formations ne sont jamais les même, car j’élabore le programme en amont, mais aussi pendant la formation, pour coller au mieux au besoin réel. Avec des programmeurs expérimentés on va pouvoir aller dans sujets plus ardus que les API de base, ou bien, je leur tends des pièges ou leur proposes défis afin de toujours les maintenir en tension, et intéressés, de sorte qu’ils en retirent un maximum de choses (quelque soit leur niveau initial).

Une formation n’est pas un bourrage de crâne d’une liste, la plus longue possible, d’API à connaître par coeur. C’est tout l’inverse. Il s’agit d’expliquer comment Drupal est construit autour de concepts imbriqués les uns les autres et de leurs variantes. Comme ça, quand le développeur est face à une situation qu’il ne connaît pas, il sait s’orienter et trouver des solutions par lui même.

D’une certaine manière, on peut dire qu’une formation ne sert pas à savoir écrire du code, mais à le lire.

Compte rendu : Drink & Drupal Toulouse décembre 2013

Hier soir, j’ai fait une présentation de Drupal Commons 3.5 lors du Drink & Drupal à la cantine à Toulouse.

Je mettrais les slides en ligne bientôt.

DrinknDrupal1

Ambiance très sympa.

Nous en avons profité pour faire un petit test rapide de « talky.io« , une plateforme dont le code est en partie Open Source de webconférence basée sur WebRTC.

C’était assez fun de passer en webcam alors qu’on était tous dans la même pièce. Ceci dit, tout le monde a été d’accord sur l’intérêt d’intégrer cette technologie (ou une autre telle que Google Hangout – avec l’avantage que la session se retrouve archivée pour le public) lors des prochaines présentations, de sorte que d’autres puissent être de la partie, même s’ils sont loin ou indisponibles.

Nous en avons profité pour faire un point de fin d’année et voir comment organiser l’année qui arrive. Il a été retenu notamment de prévoir les conférences plus en amont, sur un calendrier glissant de 3 mois, et d’être plus présent sur drupalfr (notamment d’y poster nos présentations).

Méthodologie d’audit d’un site Drupal

Éléments à fournir pour l’audit du code :

  • fichiers sources du site

  • base de données du site

  • documentation fonctionnelle et technique

Démarche suivie :

Afin d’obtenir une vue d’ensemble du site, les étapes suivantes ont été observées :

  1. Recherche de la présence d’erreurs critiques remontées par les différents logs.

  2. Étude des modules coeur et contributeurs activés, recherche de hacks dans leurs fichiers.

  3. Examen du code des modules développés spécifiquement

  4. Examen du theme du site

Détails des contrôles effectués lors de ces étapes :

  • contrôle du rapport général de Drupal

  • contrôle du watchdog de Drupal

  • différentiel entre les fichiers du coeur de Drupal et le coeur du site à versions égales (annexe diff-modules-core)

  • différentiel entre les fichiers des modules contributeurs de Drupal et ceux du site à versions égales (annexe diff-modules-contrib)

  • utilisation du module drupal « hacked » pour recouper les informations avec les résultats des différentiels (voir annexe rapport-hacked)

  • examen des hacks détectés par les différentiels et de leur impact sur le site

  • vérification des modules activés et non-activés (voir stats)

  • vérification manuelle du code custom, combiné avec une vérification avec le module « coder »

  • examen du theme du site (voir annexe templates-pages)

En cas de mauvaises pratiques http://drupal.org/best-practices/do-not-hack-core il faut en évaluer les implications.

Notez qu’en cas de modification du cœur de Drupal ou d’un module cela va entraîner des difficultés lors de mises à jour de sécurités (qui vont écraser ces moficiations). Il est donc important de les repérer en amont.

Il faut éventuellement prévoir une campagne de revue du code et de conversion en hook_xxx_alter qui sont prévus pour éviter ce genre de situation. De manière pro-active il est conseillé de faire suivre à ses développeurs une formation, ou une micro-formation à distance, pour s’assurer qu’ils connaissent les bonnes pratiques, ou bien de faire valider les compétences du prestataire en charge du développement par un expert pour éviter ce genre de déboires à l’avenir.

Les problèmes détectés pendant l’audit sera classifiés de la manière suivante :

  • Problème : Description du problème.
  • Importance : critique, haute, moyenne
  • Type : sécurité, maintenabilité, évolutivité
  • Conséquences : description des conséquences selon le problème rencontré, les impacts pour les développeurs, les utilisateurs, les chefs de projets, et les clients
  • Solution : quelles actions mettre en œuvre pour corriger, les plus simplement possible le problème
  • Source : référence vers la documentation correspondante, le cas échéant

Evolution de la complexité cyclomatique de Drupal 4 à 8

Voici les données brutes, l’analyse viendra plus tard.

Je les aies obtenues avec la commande suivante (en paramètre je passe le n° de version de Drupal :

phploc "drupal$1" --log-csv="~/drupal-analisys/d$1loc3.csv" --names="*.php,*.inc,*.module,*.yml"
Drupal 4 Drupal 5 Drupal 6 Drupal 7 Drupal 8
Directories 7 37 40 88 1 474
Files 73 83 179 382 6 025
Lines of Code (LOC) 39 397 46 223 71 710 207 763 812 817
Cyclomatic Complexity / Lines of Code 0,45 0,45 0,46 0,42 0,22
Comment Lines of Code (CLOC) 9 261 11 631 22 255 70 845 278 700
Non-Comment Lines of Code (NCLOC) 30 136 34 592 49 455 136 918 534 117
Logical Lines of Code (LLOC) 10 808 12 112 17 086 33 566 174 481
Namespaces 0 0 0 3 1 110
Interfaces 0 0 0 16 498
Traits 0 0 0 0 10
Classes 0 0 1 110 5 223
Abstract Classes 0 0 0 10 340
Concrete Classes 0 0 1 100 4 883
Average Class Length (LLOC) 0,00 0,00 57,00 40,04 26,33
Methods 0 0 4 1 042 28 246
Non-Static Methods 0 0 4 993 25 711
Static Methods 0 0 0 49 2 535
Public Methods 0 0 4 797 22 438
Non-Public Methods 0 0 0 245 5 808
Average Method Length (LLOC) 0,00 0,00 14,25 4,23 4,87
Cyclomatic Complexity / Number of Methods 0,00 0,00 5,25 2,64 2,06
Functions 1 337 1 468 2 024 4 211 3 957
Named Functions 1 337 1 468 2 024 4 211 3 568
Anonymous Functions 0 0 0 0 389
Constants 91 110 165 267 837
Global Constants 91 110 165 258 71
Class Constants 0 0 0 9 766
Attribute Accesses 2 617 2 905 3 849 7 361 50 386
Non-Static Attribute Accesses 2 617 2 905 3 849 7 294 49 357
Static Attribute Accesses 0 0 0 67 1 029
Method Calls 1 0 1 6 795 103 543
Non-Static Method Calls 1 0 1 6 636 95 186
Static Method Calls 0 0 0 159 8 357
Global Accesses 1 067 1 183 1 483 2 846 1 481
Global Variable Accesses 263 222 252 355 447
Super-Global Variable Accesses 230 229 277 426 294
Global Constant Accesses 574 732 954 2 065 740
Test Classes 0 0 0 0 0
Test Methods 0 0 0 0 0