The Final Word About final?

The Final Word About final?

In this talk, Stuart looks at PHP's `final` keyword, it's impact on type-hinting and code re-use, and how the SOLID principles can be used to strike a balance between code re-use and modification.

Presented at PHP South West in Bristol on Wednesday 8th November 2017.

2c1dc90ff7bf69097a151677624777d2?s=128

Stuart Herbert

November 08, 2017
Tweet

Transcript

  1. A presentation by @stuherbert
 for @GanbaroDigital The Final Word about

    Final? A SOLID Approach To Extensibility
  2. @GanbaroDigital I’m here to talk to you about `final` in

    a type-hinting world.
  3. @GanbaroDigital final class Bar { // ... }

  4. @GanbaroDigital final class Foo { public function doAction( Bar $input

    ) { // ... } }
  5. @GanbaroDigital final class Foo { public function doAction( Bar $input

    ) { // ... } }
  6. @GanbaroDigital ?? ?? Do you use `final` in your own

    code?
  7. @GanbaroDigital ?? ?? Why do you mark your classes as

    final?
  8. @GanbaroDigital In This Talk 1. `final` and PHP type-hinting 2.

    An example of `final` in the wild 3. A SOLID approach
  9. @GanbaroDigital In This Talk 1. `final` and PHP type-hinting 2.

    An example of `final` in the wild 3. A SOLID approach
  10. @GanbaroDigital In This Talk 1. `final` and PHP type-hinting 2.

    An example of `final` in the wild 3. A SOLID approach
  11. @GanbaroDigital Not everyone will agree that `final` causes problems.

  12. @GanbaroDigital These are my experiences.

  13. @GanbaroDigital Other opinions are out there.

  14. @GanbaroDigital final & PHP Type-Hinting

  15. @GanbaroDigital ?? ?? When was `final` introduced to the PHP

    language?
  16. @GanbaroDigital `final` is a PHP keyword introduced in PHP 5.0.0.

    13th July 2004.
  17. @GanbaroDigital ?? ?? When was type-hinting introduced to the PHP

    language?
  18. @GanbaroDigital class names as type-hints also introduced in PHP 5.0.0.

    13th July 2004.
  19. @GanbaroDigital Let’s look at classical inheritance first ... ... with

    type-hinting.
  20. @GanbaroDigital class Foo { public function doAction( Bar $input )

    { // ... } }
  21. @GanbaroDigital class Foo { public function doAction( Bar $input )

    { // ... } }
  22. @GanbaroDigital Class Foo Method doAction(Bar $input)

  23. @GanbaroDigital Class Bar Class Foo Method doAction(Bar $input)

  24. @GanbaroDigital Class Bar Class Foo Method doAction(Bar $input) ✓

  25. @GanbaroDigital Class Bar

  26. @GanbaroDigital Class Bar Class C1 extends Bar

  27. @GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends

    Bar
  28. @GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends

    Bar Class C3 extends Bar
  29. @GanbaroDigital Class Bar Class Foo Method doAction(Bar $input)

  30. @GanbaroDigital Class Bar Class Foo Method doAction(Bar $input) ✓

  31. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar Class Foo Method doAction(Bar $input)
  32. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar Class Foo Method doAction(Bar $input) ✓ ✓ ✓
  33. @GanbaroDigital ?? ?? What changes if we mark class Bar

    (our method type-hint) as `final`?
  34. @GanbaroDigital class Foo { public function doAction( Bar $input )

    { // ... } }
  35. @GanbaroDigital class Bar { // ... }

  36. @GanbaroDigital final class Bar { // ... }

  37. @GanbaroDigital Class Bar Class Foo Method doAction(Bar $input)

  38. @GanbaroDigital Class Bar Class Foo Method doAction(Bar $input) ✓

  39. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar Class Foo Method doAction(Bar $input)
  40. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar Class Foo Method doAction(Bar $input) ✗ ✗ ✗
  41. @GanbaroDigital ?? ?? Why won’t our method accept child classes

    of class Bar?
  42. @GanbaroDigital Class Bar

  43. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar ✗ ✗ ✗ Class Bar
  44. @GanbaroDigital `final` classes cannot be extended via inheritance

  45. @GanbaroDigital “ The behaviour of `final` classes can be consumed,

    not modified.
  46. @GanbaroDigital “ `final` classes lock down type-hinted method parameters.

  47. @GanbaroDigital “It’s unreasonable to expect package maintainers to know 100%

    of all possible variants.
  48. @GanbaroDigital ?? ?? How do we workaround a `final` class

    in type-hints?
  49. @GanbaroDigital ?? ?? What if ... we extend Foo to

    override the type-hint?
  50. @GanbaroDigital class Foo { public function doAction( Bar $input )

    { // ... } }
  51. @GanbaroDigital class MyFoo extends Foo { public function doAction( Bar

    $input ) { // ... } }
  52. @GanbaroDigital class MyFoo extends Foo { public function doAction( MyBar

    $input ) { // ... } }
  53. @GanbaroDigital PHP Warning: Declaration of MyFoo::doAction(MyBar $input) should be compatible

    with Foo::doAction(Bar $input) in test.php on line 19
  54. @GanbaroDigital The code runs today. Assume today’s warnings may become

    tomorrow’s fatal errors.
  55. @GanbaroDigital

  56. @GanbaroDigital class Foo { public function doAction( Bar $input )

    { // ... } }
  57. @GanbaroDigital final class Foo { public function doAction( Bar $input

    ) { // ... } }
  58. @GanbaroDigital PHP Fatal error: Class MyFoo may not inherit from

    final class (Foo) in test.php on line 19
  59. @GanbaroDigital `final` is excellent at protecting behaviour from third-party change.

  60. @GanbaroDigital But when that behaviour is incomplete or outright broken

    ...
  61. @GanbaroDigital final In The Wild

  62. @GanbaroDigital UUID RFC

  63. @GanbaroDigital https://wiki.php.net/rfc/uuid

  64. @GanbaroDigital final class UUID { // ... }

  65. @GanbaroDigital UUIDs are globally-unique, non co-ordinated values.

  66. @GanbaroDigital They are useful as primary keys.

  67. @GanbaroDigital function doSomething (int $id, ...) { // ... }

  68. @GanbaroDigital function doSomething (UUID $id, ...) { // ... }

  69. @GanbaroDigital ?? ?? What if we want to abstract away

    our ID scheme?
  70. @GanbaroDigital function doSomething (UUID $id, ...) { // ... }

  71. @GanbaroDigital function doSomething (MyID $id, ...) { // ... }

  72. @GanbaroDigital class MyID extends UUID { // ... }

  73. @GanbaroDigital class MyID extends UUID { // ... } ✗

  74. @GanbaroDigital `final` class UUID prevents us doing so via inheritance.

  75. @GanbaroDigital ?? ?? How could we work around this?

  76. @GanbaroDigital “ Use the decorator pattern for abstracting underlying behaviour.

  77. @GanbaroDigital class MyID extends UUID { // ... }

  78. @GanbaroDigital class MyID { private $uuid; public function __toString( return

    (string)$this->uuid; ); }
  79. @GanbaroDigital class MyID { private $uuid; public function __toString( return

    (string)$this->uuid; ); }
  80. @GanbaroDigital class MyID { private $uuid; public function __toString( return

    (string)$this->uuid; ); }
  81. @GanbaroDigital UUIDs can be used as primary keys.

  82. @GanbaroDigital UUIDs perform poorly as primary keys in popular database

    engines.
  83. @GanbaroDigital The solution: CombGUIDs

  84. @GanbaroDigital Jimmy Nelson, The Cost of GUIDs as Primary Keys,

    March 8th, 2002 http://www.informit.com/articles/ article.aspx?p=25862
  85. @GanbaroDigital xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

  86. @GanbaroDigital xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx { -- MySQL -- }

  87. @GanbaroDigital xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx { --- SQL Server --- }

  88. @GanbaroDigital ?? ?? Should we use the decorator pattern to

    provide CombGUIDs?
  89. @GanbaroDigital In this case, we are not adding an abstraction.

    We are adding a variant.
  90. @GanbaroDigital The PHP UUID RFC did not include CombGUIDs.

  91. @GanbaroDigital class MysqlCombGUID extends UUID { // ... }

  92. @GanbaroDigital class SqlServerCombGUID extends UUID { // ... }

  93. @GanbaroDigital final class UUID { // ... }

  94. @GanbaroDigital The PHP UUID RFC did not include CombGUIDs ...

  95. @GanbaroDigital ... and prevented anyone else from doing so from

    userland.
  96. @GanbaroDigital “It’s unreasonable to expect package maintainers to know 100%

    of all possible variants.
  97. @GanbaroDigital ?? ?? When is it reasonable to actively prevent

    third-party variants?
  98. @GanbaroDigital ?? ?? How do we solve this?

  99. @GanbaroDigital A SOLID Approach

  100. @GanbaroDigital SOLID are design principles for object-oriented code

  101. @GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov

    substitution • Interface segregation • Dependency inversion
  102. @GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov

    substitution • Interface segregation • Dependency inversion
  103. @GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov

    substitution • Interface segregation • Dependency inversion
  104. @GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov

    substitution • Interface segregation • Dependency inversion
  105. @GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov

    substitution • Interface segregation • Dependency inversion
  106. @GanbaroDigital I’m going to focus on how `final` affects two

    SOLID principles.
  107. @GanbaroDigital `final` Affects ...? • Single responsibility • Open/closed •

    Liskov substitution • Interface segregation • Dependency inversion
  108. @GanbaroDigital `final` Affects ... • Single responsibility • Open/closed •

    Liskov substitution • Interface segregation • Dependency inversion
  109. @GanbaroDigital Open/closed: open for extension, closed for modification.

  110. @GanbaroDigital `final` ensures that a class is closed for modification!

  111. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar ✗ ✗ ✗ Class Bar
  112. @GanbaroDigital We’ve also seen that `final` ensures that the consuming

    class
 is closed for extension.
  113. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar Class Foo Method doAction(Bar $input) ✗ ✗ ✗
  114. @GanbaroDigital Liskov substitution principle: type T may be substituted by

    a sub-type S without altering the desirable properties of the program.
  115. @GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends

    Bar Class C3 extends Bar
  116. @GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends

    Bar Class C3 extends Bar with caveats!
  117. @GanbaroDigital `final` prevents us creating sub-type S at all!

  118. @GanbaroDigital final class Bar { // ... }

  119. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar ✗ ✗ ✗ Class Bar
  120. @GanbaroDigital ?? ?? Can `final` classes also be SOLID?

  121. @GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov

    substitution • Interface segregation • Dependency inversion
  122. @GanbaroDigital ?? ?? What if we used interfaces for type-hinting?

  123. @GanbaroDigital final class Bar { // ... }

  124. @GanbaroDigital final class Bar implements Interface1 { // ... }

  125. @GanbaroDigital class Foo { public function doAction( Bar $input )

    { // ... } }
  126. @GanbaroDigital class Foo { public function doAction( Interface1 $input )

    { // ... } }
  127. @GanbaroDigital Class Foo Method doAction(Bar $input)

  128. @GanbaroDigital Class Foo Method doAction(Interface1 $input)

  129. @GanbaroDigital Class Bar Class Foo Method doAction(Interface1 $input)

  130. @GanbaroDigital Class Bar Class Foo Method doAction(Interface1 $input) ✗

  131. @GanbaroDigital Class Bar (Interface1) Class Foo Method doAction(Interface1 $input)

  132. @GanbaroDigital Class Bar (Interface1) Class Foo Method doAction(Interface1 $input) ✓

  133. @GanbaroDigital Class Bar (Interface1)

  134. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar ✗ ✗ ✗ Class Bar (Interface1)
  135. @GanbaroDigital Class Bar is still closed for modification. ✓

  136. @GanbaroDigital Class Foo is open to new variants. ✓

  137. @GanbaroDigital Class Bar (Interface1) Class C1 (Interface1) Class C2 (Interface1)

    Class C3 (Interface1)
  138. @GanbaroDigital Class C1 (Interface1) Class C2 (Interface1) Class C3 (Interface1)

    Class Foo Method doAction(Interface1 $input)
  139. @GanbaroDigital Class C1 (Interface1) Class C2 (Interface1) Class C3 (Interface1)

    Class Foo Method doAction(Interface1 $input) ✓ ✓ ✓
  140. @GanbaroDigital ?? ?? What about sub-types and Liskov’s substitution principle?

  141. @GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class

    C3 extends Bar ✗ ✗ ✗ Class Bar (Interface1)
  142. @GanbaroDigital Class Bar (Interface1) Class C1 (Interface1) Class C2 (Interface1)

    Class C3 (Interface1)
  143. @GanbaroDigital final class Bar implements Interface1 { // ... }

  144. @GanbaroDigital interface SubType1 extends Interface1 { // ... }

  145. @GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends

    Bar Class C3 extends Bar with caveats!
  146. @GanbaroDigital Interface1 SubType 2 SubType1 SubType3

  147. @GanbaroDigital

  148. @GanbaroDigital final class Foo { public function doAction( Interface1 $input

    ) { // ... } }
  149. @GanbaroDigital

  150. @GanbaroDigital final class Foo { public function doAction( Interface1 $input

    ) { // ... } }
  151. @GanbaroDigital final class Foo implements Interface2 { public function doAction(

    Interface1 $input ) { // ... } }
  152. @GanbaroDigital “ Behaviour is closed to modification. The system is

    open for extension (via variants).
  153. @GanbaroDigital UUID RFC

  154. @GanbaroDigital final class UUID { // ... }

  155. @GanbaroDigital function doSomething (UUID $id, ...) { // ... }

  156. @GanbaroDigital

  157. @GanbaroDigital interface UUID { // ... }

  158. @GanbaroDigital class MysqlCombGUID extends UUID { // ... }

  159. @GanbaroDigital class MysqlCombGUID extends UUID { // ... } ✗

  160. @GanbaroDigital class MysqlCombGUID implements UUID { // ... }

  161. @GanbaroDigital class SqlServerCombGUID implements UUID
 { // ... }

  162. @GanbaroDigital Userland UUID variants would be fully supported.

  163. @GanbaroDigital class MyID extends UUID { // ... }

  164. @GanbaroDigital class MyID extends UUID { // ... } ✗

  165. @GanbaroDigital class MyID { private $uuid; public function __toString( return

    (string)$this->uuid; ); }
  166. @GanbaroDigital Abstractions should still be done via aggregation & the

    decorator pattern.
  167. @GanbaroDigital Abstractions would benefit from some interfaces too!

  168. @GanbaroDigital In Summary

  169. @GanbaroDigital “ Use the decorator pattern for abstracting underlying behaviour.

  170. @GanbaroDigital “ The behaviour of `final` classes can be consumed,

    not modified.
  171. @GanbaroDigital “ `final` classes lock down type-hinted method parameters.

  172. @GanbaroDigital “It’s unreasonable to expect package maintainers to know 100%

    of all possible variants.
  173. @GanbaroDigital “ Use interface inheritance for new variants.

  174. @GanbaroDigital “ `final` classes that don’t implement interfaces restrict package

    re-use.
  175. @GanbaroDigital “ `final` classes that don’t implement interfaces are not

    SOLID.
  176. @GanbaroDigital “ `final` classes that don’t implement interfaces are a

    code smell.
  177. @GanbaroDigital “Don’t use `final` classes as type-hints. Use interfaces.

  178. @GanbaroDigital

  179. @GanbaroDigital “ Behaviour is closed to modification. The system is

    open for extension (via variants).
  180. Thank You Any Questions? A presentation by @stuherbert
 for @GanbaroDigital