Skip to content

Instantly share code, notes, and snippets.

@nikic
Created October 13, 2012 11:22
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikic/3884203 to your computer and use it in GitHub Desktop.
Save nikic/3884203 to your computer and use it in GitHub Desktop.
Analysis of getter/setter usage in Symfony and Zend Framework

In order to get a bit of "hard data" on what accessors will actually be used for once they are introduced I wrote a small script that scans through a codebase, finds all getter and setter methods and checks them for various characteristics. (The analyze.php file in this Gist.)

Here are the results of running it on a Symfony (Standard) skeleton.

absoluteTotal        => 18516 (486.6%)
total                =>  3805 (100.0%)
skipped              =>   124 (  3.3%)

get                  =>  2767 ( 72.7%, 100.0% of get)
getInterface         =>   274 (  7.2%,   9.9% of get)
trivialGet           =>   928 ( 24.4%,  33.5% of get)
simpleGet            =>  1172 ( 30.8%,  42.4% of get)
getWithCheck         =>   107 (  2.8%,   3.9% of get)

set                  =>   914 ( 24.0%, 100.0% of set)
setInterface         =>    92 (  2.4%,  10.1% of set)
trivialSet           =>   354 (  9.3%,  38.7% of set)
trivialSetChaining   =>    77 (  2.0%,   8.4% of set)
simpleSet            =>    49 (  1.3%,   5.4% of set)
simpleSetChaining    =>    16 (  0.4%,   1.8% of set)
setWithTypeHint      =>   289 (  7.6%,  31.6% of set)

unclassified         =>   612 ( 16.1%)

The same for Zend Framework:

absoluteTotal        =>  9876 (242.1%)
total                =>  4080 (100.0%)
skipped              =>    45 (  1.1%)

get                  =>  2480 ( 60.8%, 100.0% of get)
getInterface         =>   264 (  6.5%,  10.6% of get)
trivialGet           =>  1070 ( 26.2%,  43.1% of get)
simpleGet            =>   465 ( 11.4%,  18.8% of get)
getWithCheck         =>   266 (  6.5%,  10.7% of get)

set                  =>  1555 ( 38.1%, 100.0% of set)
setInterface         =>   138 (  3.4%,   8.9% of set)
trivialSet           =>   120 (  2.9%,   7.7% of set)
trivialSetChaining   =>   397 (  9.7%,  25.5% of set)
simpleSet            =>    14 (  0.3%,   0.9% of set)
simpleSetChaining    =>   189 (  4.6%,  12.2% of set)
setWithTypeHint      =>   342 (  8.4%,  22.0% of set)

unclassified         =>  1112 ( 27.3%)

An overview on what these stats mean and how I interpret them:

absoluteTotal, total:
    "absoluteTotal" is the total number of methods, "total" is the number of accessors,
    i.e. no-param methods starting with "get" or single-param methods starting with "set".
    As you can see getseter make up a large part of the methods. For Symfony 20% for
    ZF even 40%.
    => Getter and setter are a *very* common pattern, so it makes sense to do work in
    this direction.

get, set:
    "get" is the number of getters, "set" the number of setters. You can see that people
    use a good bit more getters than setters. For Symfony there are about 3x more getters,
    for ZF it's 1.6x as many.
    => It is common to have read-only data. This may either mean that there will be many
    accessors with asymmetric visibility or that many properties will only define a get
    accessor.
    
getInterface, setInterface:
    Number of getters/setters defined in interfaces or as abstract methods. This is about
    10% for both projects.
    => Accessor and property support in interfaces is necessary.
    
trivialGet, trivialSet, trivialSetChaining:
    "Trivial" means that the accessors act as a direct proxy to another property. The trivial
    set chaining variant also proxies to another property and additionally returns $this.
    For Symfony about 35% of all accessors are trivial. ZF also has 40% trivial getters, but
    seems to be rather fond of chaining, so 25% of the setters are trivially chaining.
    => A good chunk of all getters/setters don't really do anything. Once we get accessors they
    can be basically replaced by public properties. Accessors give the possibility to add more
    behavior lateron.
    
simpleGet, simpleSet, simpleSetChaining:
    A simple getter contains only a return statement, a simple setter only one assignment. For
    Symfony 40% of the getters are simple (this is not counting the trivial ones), for ZF it's
    20%.
    => Many getter are very simple and I think that especially the simple ones will be the ones
    converted to property accessors.
    
    This category is rather vague. Here are the three main types of simple getters I've found:
    1. Getters returning a constant value. This is more common than I would have thought. Good
       example of such a case are getName() methods that commonly return a constant string. Others
       return arrays or constants.
       => There seems to be need for read-only, constant properties.
    2. Proxies to other getters ($this->foo->getBar()).
    3. Getters taking some property and doing some small postprocessing, like replacing a string
       or similar.
       
getWithCheck:
    Getters which contain an if condition (containing a single statement), followed by a return.
    This is a common pattern for lazy loading. Amounts to 10% in ZF and 4% in Symfony.
    
setWithTypeHint:
    Setters where the argument is type hinted. 30% of the Symfony setters have one and 20% of
    the ZF ones.
    => There should be a simple way to typehint properties.
<?php
error_reporting(E_ALL);
ini_set('memory_limit', -1);
//$dir = '../Symfony';
$dir = '../ZendFramework-2.0.2';
require_once './lib/bootstrap.php';
$prettyPrinter = new PHPParser_PrettyPrinter_Zend;
class AccessorAccumulatingVisitor extends PHPParser_NodeVisitorAbstract {
public $accessorMethods = array();
public $absoluteTotal = 0;
public function enterNode(PHPParser_Node $node) {
if (!$node instanceof PHPParser_Node_Stmt_ClassMethod) return;
++$this->absoluteTotal;
if (0 === strpos($node->name, 'get')) {
if (0 !== count($node->params)) return;
} elseif (0 === strpos($node->name, 'set')) {
if (1 !== count($node->params)) return;
} else {
return;
}
// we don't care for doc comments
$node->setAttribute('comments', array());
$this->accessorMethods[] = $node;
echo $node->name, "\n";
}
}
$parser = new PHPParser_Parser(new PHPParser_Lexer());
$accumulator = new AccessorAccumulatingVisitor;
$traverser = new PHPParser_NodeTraverser;
$traverser->addVisitor($accumulator);
foreach (new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir),
RecursiveIteratorIterator::LEAVES_ONLY)
as $file
) {
if (!preg_match('/\.php$/', $file)) continue;
try {
$traverser->traverse($parser->parse(file_get_contents($file)));
} catch (PHPParser_Error $e) {}
}
$accessorMethods = $accumulator->accessorMethods;
file_put_contents('accessors', $prettyPrinter->prettyPrint($accessorMethods));
echo "\n";
//file_put_contents('accessors.serialized', serialize($accessorMethods));
//$accessorMethods = unserialize(file_get_contents('accessors.serialized'));
$total = count($accessorMethods);
$counts = array(
'absoluteTotal' => $accumulator->absoluteTotal,
'total' => $total,
'skipped' => 0,
'---',
'get' => 0,
'getInterface' => 0,
'trivialGet' => 0,
'simpleGet' => 0,
'getWithCheck' => 0,
'---',
'set' => 0,
'setInterface' => 0,
'trivialSet' => 0,
'trivialSetChaining' => 0,
'simpleSet' => 0,
'simpleSetChaining' => 0,
'setWithTypeHint' => 0,
'---',
'unclassified' => 0,
);
$skip = array_fill_keys(array('getIterator', 'set', 'get'), true);
$unclassifiedAccessorMethods = array();
foreach ($accessorMethods as $method) {
$name = $method->name;
$stmts = $method->stmts;
if (isset($skip[$name]) // Skip some special method names
|| (is_array($stmts) && empty($stmts)) // Skip empty methods
|| (isset($stmts[0]) && $stmts[0] instanceof PHPParser_Node_Stmt_Throw) // Skip methods that just throw
) {
++$counts['skipped'];
continue;
}
if ($name[0] === 'g') {
++$counts['get'];
if ($stmts === null) {
++$counts['getInterface'];
continue;
}
if ($stmts[0] instanceof PHPParser_Node_Stmt_Return) {
if ($stmts[0]->expr instanceof PHPParser_Node_Expr_PropertyFetch
|| $stmts[0]->expr instanceof PHPParser_Node_Expr_StaticPropertyFetch
) {
++$counts['trivialGet'];
} else {
++$counts['simpleGet'];
}
continue;
}
if (isset($stmts[1])
&& $stmts[0] instanceof PHPParser_Node_Stmt_If
&& count($stmts[0]->stmts) == 1
&& $stmts[1] instanceof PHPParser_Node_Stmt_Return
) {
++$counts['getWithCheck'];
continue;
}
} elseif ($name[0] === 's') {
++$counts['set'];
if ($stmts === null) {
++$counts['setInterface'];
continue;
}
if (null !== $method->params[0]->type) {
++$counts['setWithTypeHint'];
}
if ($stmts[0] instanceof PHPParser_Node_Expr_Assign
&& ($stmts[0]->var instanceof PHPParser_Node_Expr_PropertyFetch
|| $stmts[0]->var instanceof PHPParser_Node_Expr_StaticPropertyFetch)
) {
$isTrivial = $stmts[0]->expr instanceof PHPParser_Node_Expr_Variable;
if (1 === count($stmts)) {
if ($isTrivial) {
++$counts['trivialSet'];
} else {
++$counts['simpleSet'];
}
continue;
}
if (2 === count($stmts)
&& $stmts[1] instanceof PHPParser_Node_Stmt_Return
&& $stmts[1]->expr instanceof PHPParser_Node_Expr_Variable
&& $stmts[1]->expr->name === 'this'
) {
if ($isTrivial) {
++$counts['trivialSetChaining'];
} else {
++$counts['simpleSetChaining'];
}
continue;
}
}
}
++$counts['unclassified'];
$unclassifiedAccessorMethods[] = $method;
}
$getCount = $counts['get'];
$setCount = $counts['set'];
foreach ($counts as $name => $count) {
if ('---' === $count) {
echo "\n";
continue;
}
if (false !== stripos($name, 'get')) {
$additionalPercentage = sprintf(', %5.1f%% of get', $count / $getCount * 100);
} elseif (false !== stripos($name, 'set')) {
$additionalPercentage = sprintf(', %5.1f%% of set', $count / $setCount * 100);
} else {
$additionalPercentage = '';
}
echo sprintf("%-20s => %5d (%5.1f%%%s)\n", $name, $count, $count / $total * 100, $additionalPercentage);
}
file_put_contents('accessors.unclassified', $prettyPrinter->prettyPrint($unclassifiedAccessorMethods));
@thbley
Copy link

thbley commented Jan 23, 2013

@nikic: Thanks for sharing! Could you add version numbers to the report?
In the code I see ZF 2.0.2, but what version of Symfony was tested? 2.0|1|2 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment