Using phploc for a quick code quality estimation - Part 2

Posted on by Matthias Noback

In part 1 of this series we discussed the size and complexity metrics calculated by phploc. We continue with a discussion about dependencies and structure.

Structure

This section gives us an idea of how many things there are of a certain type. These can be useful indicators too. For example, if the number of Namespaces is low, there may be a lack of grouping, which is bad for discoverability. It'll be hard to find out where a certain piece of logic can be found in the code. Too many namespaces, relative to the number of Classes/Interfaces/Traits, is not a good sign either. I would expect every namespace to have a couple of classes that naturally belong together.

The number of Traits is expected to be low compared to the number of Classes. Traits are rarely a good solution in my opinion. The number of Interfaces is expected to be higher, I'd say roughly about 1:5 compared the number of Classes. Not every class needs an interface, but a lack of interfaces usually shows a lack of abstraction too, in the sense that code will be directly coupled to libraries, frameworks, databases, remote services, etc.

Structure
  Namespaces                                         3
  Interfaces                                         1
  Traits                                             0
  Classes                                            9

Classes are further divided into Abstract Classes and Concrete Classes. In general, the number of abstract classes is expected to be low. If it's high, compared to the number of concrete classes, this is often a sign that inheritance is (wrongly) used for code reuse.

    Abstract Classes                                 0 (0.00%)
    Concrete Classes                                 9 (100.00%)

It would be useful to have an indication of the use of the final keyword. We could then easily verify the rule that all classes should either be abstract (meant to be extended) or final (meant not to be extended).

What's also missing in the statistics is a section about the actual use of inheritance. It would be interesting to have the following numbers:

    Minimum class hierarchy depth                    1
    Average class hierarchy depth                    1.2
    Maximum class hierarchy depth                    6

Whenever I find the time for that, I'll try to add these extra metrics to phploc itself.

The number of Methods isn't particularly interesting, but it would be nice to have some extra metrics here as well:

    Minimum number of class methods                  1
    Average number of class methods                  2.5
    Maximum number of class methods                  10

The Scope of Methods is relevant. As explained in part 1 I would want the number of static methods to be very low. The number of Public Methods or Non-Public Methods isn't that insightful in my opinion. It would be good to know the number of protected methods, since those would be better off as private methods. However, in service classes I think private methods are often better off as public methods of collaborator objects. So, we can't really conclude much based on these numbers.

  Methods                                          130
    Scope
      Non-Static Methods                           130 (100.00%)
      Static Methods                                 0 (0.00%)
    Visibility
      Public Methods                               103 (79.23%)
      Non-Public Methods                            27 (20.77%)

As mentioned in part 1, the use of Named Functions should be limited. Anonymous Functions aren't a big problem, because they are used for providing callable arguments, or to work with higher-order functions.

We discussed the use of global constants above. In this section we can compare the use of global constants to how many have been defined.

  Functions                                          0
    Named Functions                                  0 (0.00%)
    Anonymous Functions                              0 (0.00%)
  Constants                                          0
    Global Constants                                 0 (0.00%)
    Class Constants                                  0 (0.00%)

What could be added here is a subdivision of Public Class Constants and Private Class Constants (again, it's on my to do list). Public constants often lead to undesired coupling, so they should be defined as private, whenever possible.

Conclusion

We've reached the end of the phploc output. I hope there were some useful suggestions here, and I hope you'll take a look at phploc yourself and run it on your project.

One of my clients asked me whether you could use phploc output as a way to monitor the quality improvements made to a project over a longer period of time. I think you can. If you'd like to work on some kind an automated solution, check out phploc's JSON or XML output formatter. And please share your experience with us.

If you use phploc metrics as quality indicators in other ways than I described in this post, please share them in the comments as well.

PHP quality assurance object design
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Ravage

Thank you, Matthias, for demonstrating the use of this in n my opinion mostly overlooked tool.

> One of my clients asked me whether you could use phploc output as a way
to monitor the quality improvements made to a project over a longer
period of time. I think you can. If you'd like to work on some kind an
automated solution, check out phploc's JSON or XML output formatter. And
please share your experience with us.

Yes, you can, if you use a continuous integration tool. We use Jenkins for that.

First, during the Jenkins build, we produce a CSV output for phploc:

vendor/bin/phploc --no-interaction --no-ansi --progress --names *.php,*.ctp --count-tests --log-csv build/logs/phploc.csv src tests

At the end of the Jenkins build we "plot" the various values as points in a diagram over time which gets attached to the Jenkins build as an image (example: https://imgur.com/a/YPAVaje).

To do that, one needs the Plot plugin (https://wiki.jenkins.io/dis....

plot([
title: 'A1 - Lines of Code (LoC)',
yaxis: 'Lines of Code',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-lines_of_code.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Lines of Code (LOC),Comment Lines of Code (CLOC),Non-Comment Lines of Code (NCLOC)',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'A2 - Logical Lines of Code (LLoC)',
yaxis: 'Logical Lines of Code',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-logical_lines_of_code.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Logical Lines of Code (LLOC),Classes Length (LLOC),Functions Length (LLOC),LLOC outside functions or classes',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'B1 - Structural Containers',
yaxis: 'Containers',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-structural_containers.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Directories,Files,Namespaces',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'B2 - Structure Objects',
yaxis: 'Structure Objects',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-structure_objects.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Traits,Interfaces,Classes,Methods,Functions',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'C1 - Types of Classes',
yaxis: 'Classes',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-types_of_classes.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Classes,Abstract Classes,Concrete Classes',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'C2 - Types of Methods',
yaxis: 'Methods',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-types_of_methods.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Methods,Non-Static Methods,Static Methods',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'C3 - Method Visibility',
yaxis: 'Methods',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-method_visibility.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Methods,Public Methods,Non-Public Methods',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'C4 - Types of Constants',
yaxis: 'Constants',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-types_of_constants.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Constants,Global Constants,Class Constants',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'C5 - Types of Functions',
yaxis: 'Functions',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-types_of_functions.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Functions,Named Functions,Anonymous Functions',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]],
])
plot([
title: 'D1 - Attribute Access',
yaxis: 'Attribute Accesses',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-attribute_access.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Attribute Accesses,Non-Static Attribute Accesses,Static Attribute Accesses',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'D2 - Method Calls',
yaxis: 'Method Calls',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-method_calls.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Method Calls,Non-Static Method Calls,Static Method Calls',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'D3 - Global Access',
yaxis: 'Global Accesses',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-global_access.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Global Accesses,Global Variable Accesse,Super-Global Variable Accesses,Global Constant Accesses',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'E1 - Tests',
yaxis: 'Count',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-tests.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Test Classes,Test Methods',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'F1 - Average Length',
yaxis: 'Lines of Code',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-ec52cf31-0d78-4536-91dc-5arac251e789.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Average Function Length (LLOC)',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])
plot([
title: 'G1 - Cyclomatic Complexity',
yaxis: 'Average Cyclomatic Complexity',
group: 'phploc',
keepRecords: true,
style: 'line',
csvFileName: 'plot-ec52cf31-0d78-4536-91dg-5arac251e789.csv',
csvSeries: [[
displayTableFlag: false,
exclusionValues: 'Cyclomatic Complexity / Lines of Code,Cyclomatic Complexity / Number of Methods',
file: 'build/logs/phploc.csv',
inclusionFlag: 'INCLUDE_BY_STRING',
url: ''
]]
])

The initial idea was taken from a website which was build by
thePHP.cc but is now somewhat outdated due to new possibilities in
Jenkins 2 and upwards.

http://jenkins-php.org/
http://jenkins-php.org/inst...

Matthias Noback

Thanks for sharing! I was wondering: why is there a CSV exporter for phploc? Now I know :)