Insphpect

This tool is currently proof-of-concept. Your feedback and evaluation is valuable in helping to improve it and ensure its reports are meaninful.

Please click here to complete a short survey to tell us what you think. It should take less than 5 minutes and help further this research project!

Symfony\Component\Routing\Generator\CompiledUrlGenerator

Detected issues

Issue Method Line number
Inheritance 21

Code

Click highlighted lines for details

<?php/* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */namespace Symfony\Component\Routing\Generator;use Psr\Log\LoggerInterface;use Symfony\Component\Routing\Exception\RouteNotFoundException;use Symfony\Component\Routing\RequestContext;/** * Generates URLs based on rules dumped by CompiledUrlGeneratorDumper. */

Inheritance

Summary

Inheritance reduces flexibility because it:

Favour composition over inheritance

The oft-repeated phrase Favour composition over inheritance exists for a reason. There is no reason to ever use inheritance when you are dealing with classes you own, and usually there are alternatives when you are extending a class someone else wrote.

As with most programming practices, there are trade-offs when choosing which approach to use. Inheritance can be easy to use and understand, it can also simplify designs[1], but composition is always more flexible[1][2][3][4]

Background

By design, inheritance creates tight coupling because it is impossible to change the relationship between classes at runtime. To alter the relationship, the code must be amended.

The popular book Design Patters: Elements of Resusable Object-Oriented Software by Gamma et al[1] has a long section on replacing inheritance with composition. Holub[5] quotes a speech by James Gosling, the inventor of Java, who said he would leave out inheritance if he were to design Java again.

One of the problems with implementing an abstract class with inheritance is that the derived class is so tightly coupled to the base class

Martin[6]

Tight Coupling

Consider the classic inheritance Employee-Person example:

 
class Person {
    public 
String name;
    public 
String address;
    public 
String gender;
    public 
Date birthdate;
}


class 
Employee extends Person {
    public 
String jobTitle;
    public 
int salary;
    public 
Date startDate;
}

This is inflexible because:

  • A person cannot ever have more than one job (and refactoring the code to allow this is difficult using inheritance)

  • Only people can have jobs, it is impossible to represent robots in factories or animals who work (e.g. police dogs) because Employee is a subclass of Person. Using inhertiance there is no way to substitute Person for sommething else (e.g. Robot)

Instead, by representing the class structure using has-a and composition (which is loosely coupled by design), these problems can be avoided:

 
Person tom = new Person('Tom', new Job('Software Developer'30000));

Animal rex = new Animal('Rex', new Job('Police Dog'));

Robot curiosityRover = new Robot('Curiosity', new Job('Exploration of Mars'));

and the classes could be written to allow multiple jobs:

 
Person tom = new Person('Tom');
tom.addJob(new Job('Software Developer'));
tom.addJob(new Job('University Lecturer'));

Holub[5] sums up the problem as:

Why should you avoid implementation inheritance? The first problem is that explicit use of concrete class names locks you into specific implementations, making down-the-line changes unnecessarily difficult.

The major problem with inheritance is that is essentially static, there is no way to dynamically change the base class like you can with composition. When a method is invoked such as this.drive(), the drive method is a very tight coupling. It is either the method in the same class or a parent class and there is no way to override it without making a new subclass. However, when using composition this.engine.drive(), the Engine object can be substituted at runtime and the drive() method could be any one of an infinite possible number of implementations.

For more information on tight coupling, click here

Tight coupling is the primary limit on flexibility of inheritance but there are several other issues inheritance introduces:

The fragile base class problem

Inheritance can also introduce bugs which are difficult to anticipate because the author of the base class does not know how it is being used by subclasses. A simplified version of an example given by Aldrich[7] is as follows:

Consider two classes written by different authors. The classes act as counters, the first one counts in steps of 1 or 2 and the second counter, counts in double the speed:

 class Counter {
    private 
int counter 0;

    public 
void add1() {
        
counter counter 1;
    }

    public 
void add2() {
        
counter counter 2;
    }
}


class 
CountInTwos extends Counter {
    public 
void add1() {
        
add2();
    }

    public 
void add2() {
        
add2();
        
add2();
    }
}

These classes work perfectly. However, there is no way for the author of the parent class to know how or where their base class is being used. It's not unreasonable for them to re-write their class at some point:

 class Counter {
    private 
int counter 0;

    public 
void add1() {
        
counter counter 1;
    }

    public 
void add2() {
        
add1();
        
add1();
    }
}

Anywhere Counter is used still works perfectly, all unit tests on the class still pass and the API hasn't changed. However, this affects CountInTwos: There is now an infinite loop: The add1() function calls add2(), which calls add1(), creating infinite recursion. The subclass's functionality has been broken by a seemingly harmless change in the base-class.

This is action at a distance as a change in one place inadvertently breaks code elsewhere.

Aldrich[7] suggests several solutions to this problem, however they involve careful class design and requiring the author of the base class to design their class around the problem in order to prevent it. A simpler solution is avoiding inheritance in the first place[5][8][9][1][10] giving the author of what was the parent class a lot less to worry about.

Inheritance breaks encapsulation

Because inheritance exposes a subclass to details of its parent's implementation, it's often said that 'inheritance breaks encapsulation'

Gamma et al[1]

Though both Composition and Inheritance allows you to reuse code, one of the disadvantage of Inheritance is that it breaks encapsulation. If sub class is depending on super class behavior for its operation, it suddenly becomes fragile. When behavior of super class changes, functionality in sub class may get broken, without any change on its part.

Paul[2]

The fragile base class problem exists because inheritance subtly breaks encapsulation[8][1]. In the example above, the CountInTwos class is subtly exposed to the implementation of the parent class. Changes in the parent class are reflected in child class meaning the two classes are not self-contained. It is expected API changes will always have an affect elsewhere no matter the relation type, with inheritance implementation changes in one class can affect the outcome of another class, even if the arguments and return values of the method do not change.

Protected and public properties also break encapsulation in a much more obvious way. Protected properties share data between two classes breaking encapsulation[11].

The diamond problem

A major problem with inheritance is that it becomes difficult to share code in large inheritance trees. Consider the following example:

 
class Bird extends FlyingAnimal {
    
}

class 
Fish extends SwimmingAnimal {
    
}

How would a developer model a penguin in this inheritance tree. Hurn[12] and van Dongen[13] also provide detailed examples of this problem.

Whenever you get an inheritance hierarchy you run into this problem when the project becomes large enough, you will find you need to share some properties of two classes at opposite ends of the hierarchy.

The only fix becomes a large refactoring of the class hierarchy or duplicating code. As duplicating code is a large maintainability problem[11][14], the first is more favourable, however the problem can be avoided all together by favouring composition over inheritance at the start of a project[15].

This does to apply to languages which allow allow for Multiple Inheritance which solves the diamond problem, however it doesn't fix the static method calls or the situations above e.g. multiple jobs or different things that can have jobs.

Separation of concerns, single responsibility principle

The final problem with inheritance is that it breaks the single-responsibility-principle. The single responsibility principle was coined by Martin[16] who defines it as:

A class should have only one reason to change

And later reiterated it as

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

Martin[17]

If a subclass an a parent class are sharing an API but are otherwise different, they will change at different times for different reasons. By definition changing the base class will change the behaviour of subclass, giving the subclass at least two reasons to change.

In the case of inheritance this has little practical effect and breaking the single responsibility principle in this way doesn't cause the same issues as it normally would because there is some separation between classes. However it's worth noting that a subclass will always export two APIs.

Further reading

References

  1. Gamma, E., Helm, R., Johnson, R., Vlissides, J. (1994) Design Patterns: Elements of Reusable Object-Oriented Software. ISBN: 0201633612. Addison Wesley.
  2. Paul, J. (2013) 5 Reasons to Use Composition over Inheritance in Java and OOP [online]. Available from: http://javarevisited.blogspot.com/2013/06/why-favor-composition-over-inheritance-java-oops-design.html
  3. Johansson, M. (2015) Composition over Inheritance [online]. Available from: https://medium.com/humans-create-software/composition-over-inheritance-cb6f88070205
  4. van Gurp, J., Bosch, J. (2001) Design, implementation and evolution of object oriented frameworks: concepts and guidelines. Software- Practice and Experience 31, pp.277-300.
  5. Holub, A. (2010) Why extends is evil [online]. Available from: http://www.javaworld.com/article/2073649/core-java/why-extends-is-evil.html
  6. Martin, R. (2000) Design Principles and Design Patterns [online]. Available from: http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf
  7. Aldrich, J. (2004) Selective Open Recursion: A Solution to the Fragile Base Class Problem. Carnegie Mellon University .
  8. Biberstein, M., Sreedhar, V., Zaks, A. (2002) A Case For Sealing Classes In Java. IBM Research .
  9. Bloch, J. (2008) Effective Java: Second Edition ISBN: 978-0321356680. Addison-Wesley.
  10. van Dongen, J. (2014) The Fragile Base Class Problem [online]. Available from: http://www.web-brainz.co.uk/fragile
  11. Martin, R. (2008) Clean Code: A Handbook of Agile Software Craftsmanship ISBN: 978-0132350884. Prentice Hall.
  12. Hurn, S. (2014) Favor Composition Over Inheritance [online]. Available from: https://codingdelight.com/2014/01/16/favor-composition-over-inheritance-part-1/
  13. van Dongen, J. (2014) Why composition is often better than inheritance [online]. Available from: http://joostdevblog.blogspot.co.uk/2014/07/why-composition-is-often-better-than.html
  14. Hunt, A., Thomas, D. (1999) The Pragmatic Programmer ISBN: 978-0201616224. Addison Wesley.
  15. Otander, J. (2015) Mixins over Inheritance [online]. Available from: http://alisoftware.github.io/swift/protocol/2015/11/08/mixins-over-inheritance/
  16. Martin, R. (2003) SRP: The Single Responsibility Principle [online]. Available from: https://drive.google.com/file/d/0ByOwmqah_nuGNHEtcU5OekdDMkk/view
  17. Martin, R. (2014) The Single Responsibility Principle [online]. Available from: https://8thlight.com/blog/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html
{ private $compiledRoutes = []; private $defaultLocale; public function __construct(array $compiledRoutes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null) { $this->compiledRoutes = $compiledRoutes; $this->context = $context; $this->logger = $logger; $this->defaultLocale = $defaultLocale; } public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH) { $locale = $parameters['_locale'] ?? $this->context->getParameter('_locale') ?: $this->defaultLocale; if (null !== $locale) { do { if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) { $name .= '.'.$locale; break; } } while (false !== $locale = strstr($locale, '_', true)); } if (!isset($this->compiledRoutes[$name])) { throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); } list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = $this->compiledRoutes[$name]; if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { if (!\in_array('_locale', $variables, true)) { unset($parameters['_locale']); } elseif (!isset($parameters['_locale'])) { $parameters['_locale'] = $defaults['_locale']; } } return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); }}