Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
A presentation by @stuherbert for @GanbaroDigital The Final Word about Final? A SOLID Approach To Extensibility
Slide 2
Slide 2 text
@GanbaroDigital I’m here to talk to you about `final` in a type-hinting world.
Slide 3
Slide 3 text
@GanbaroDigital final class Bar { // ... }
Slide 4
Slide 4 text
@GanbaroDigital final class Foo { public function doAction( Bar $input ) { // ... } }
Slide 5
Slide 5 text
@GanbaroDigital final class Foo { public function doAction( Bar $input ) { // ... } }
Slide 6
Slide 6 text
@GanbaroDigital ?? ?? Do you use `final` in your own code?
Slide 7
Slide 7 text
@GanbaroDigital ?? ?? Why do you mark your classes as final?
Slide 8
Slide 8 text
@GanbaroDigital In This Talk 1. `final` and PHP type-hinting 2. An example of `final` in the wild 3. A SOLID approach
Slide 9
Slide 9 text
@GanbaroDigital In This Talk 1. `final` and PHP type-hinting 2. An example of `final` in the wild 3. A SOLID approach
Slide 10
Slide 10 text
@GanbaroDigital In This Talk 1. `final` and PHP type-hinting 2. An example of `final` in the wild 3. A SOLID approach
Slide 11
Slide 11 text
@GanbaroDigital Not everyone will agree that `final` causes problems.
Slide 12
Slide 12 text
@GanbaroDigital These are my experiences.
Slide 13
Slide 13 text
@GanbaroDigital Other opinions are out there.
Slide 14
Slide 14 text
@GanbaroDigital final & PHP Type-Hinting
Slide 15
Slide 15 text
@GanbaroDigital ?? ?? When was `final` introduced to the PHP language?
Slide 16
Slide 16 text
@GanbaroDigital `final` is a PHP keyword introduced in PHP 5.0.0. 13th July 2004.
Slide 17
Slide 17 text
@GanbaroDigital ?? ?? When was type-hinting introduced to the PHP language?
Slide 18
Slide 18 text
@GanbaroDigital class names as type-hints also introduced in PHP 5.0.0. 13th July 2004.
Slide 19
Slide 19 text
@GanbaroDigital Let’s look at classical inheritance first ... ... with type-hinting.
Slide 20
Slide 20 text
@GanbaroDigital class Foo { public function doAction( Bar $input ) { // ... } }
Slide 21
Slide 21 text
@GanbaroDigital class Foo { public function doAction( Bar $input ) { // ... } }
Slide 22
Slide 22 text
@GanbaroDigital Class Foo Method doAction(Bar $input)
Slide 23
Slide 23 text
@GanbaroDigital Class Bar Class Foo Method doAction(Bar $input)
Slide 24
Slide 24 text
@GanbaroDigital Class Bar Class Foo Method doAction(Bar $input) ✓
Slide 25
Slide 25 text
@GanbaroDigital Class Bar
Slide 26
Slide 26 text
@GanbaroDigital Class Bar Class C1 extends Bar
Slide 27
Slide 27 text
@GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends Bar
Slide 28
Slide 28 text
@GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar
Slide 29
Slide 29 text
@GanbaroDigital Class Bar Class Foo Method doAction(Bar $input)
Slide 30
Slide 30 text
@GanbaroDigital Class Bar Class Foo Method doAction(Bar $input) ✓
Slide 31
Slide 31 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar Class Foo Method doAction(Bar $input)
Slide 32
Slide 32 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar Class Foo Method doAction(Bar $input) ✓ ✓ ✓
Slide 33
Slide 33 text
@GanbaroDigital ?? ?? What changes if we mark class Bar (our method type-hint) as `final`?
Slide 34
Slide 34 text
@GanbaroDigital class Foo { public function doAction( Bar $input ) { // ... } }
Slide 35
Slide 35 text
@GanbaroDigital class Bar { // ... }
Slide 36
Slide 36 text
@GanbaroDigital final class Bar { // ... }
Slide 37
Slide 37 text
@GanbaroDigital Class Bar Class Foo Method doAction(Bar $input)
Slide 38
Slide 38 text
@GanbaroDigital Class Bar Class Foo Method doAction(Bar $input) ✓
Slide 39
Slide 39 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar Class Foo Method doAction(Bar $input)
Slide 40
Slide 40 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar Class Foo Method doAction(Bar $input) ✗ ✗ ✗
Slide 41
Slide 41 text
@GanbaroDigital ?? ?? Why won’t our method accept child classes of class Bar?
Slide 42
Slide 42 text
@GanbaroDigital Class Bar
Slide 43
Slide 43 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar ✗ ✗ ✗ Class Bar
Slide 44
Slide 44 text
@GanbaroDigital `final` classes cannot be extended via inheritance
Slide 45
Slide 45 text
@GanbaroDigital “ The behaviour of `final` classes can be consumed, not modified.
Slide 46
Slide 46 text
@GanbaroDigital “ `final` classes lock down type-hinted method parameters.
Slide 47
Slide 47 text
@GanbaroDigital “It’s unreasonable to expect package maintainers to know 100% of all possible variants.
Slide 48
Slide 48 text
@GanbaroDigital ?? ?? How do we workaround a `final` class in type-hints?
Slide 49
Slide 49 text
@GanbaroDigital ?? ?? What if ... we extend Foo to override the type-hint?
Slide 50
Slide 50 text
@GanbaroDigital class Foo { public function doAction( Bar $input ) { // ... } }
Slide 51
Slide 51 text
@GanbaroDigital class MyFoo extends Foo { public function doAction( Bar $input ) { // ... } }
Slide 52
Slide 52 text
@GanbaroDigital class MyFoo extends Foo { public function doAction( MyBar $input ) { // ... } }
Slide 53
Slide 53 text
@GanbaroDigital PHP Warning: Declaration of MyFoo::doAction(MyBar $input) should be compatible with Foo::doAction(Bar $input) in test.php on line 19
Slide 54
Slide 54 text
@GanbaroDigital The code runs today. Assume today’s warnings may become tomorrow’s fatal errors.
Slide 55
Slide 55 text
@GanbaroDigital
Slide 56
Slide 56 text
@GanbaroDigital class Foo { public function doAction( Bar $input ) { // ... } }
Slide 57
Slide 57 text
@GanbaroDigital final class Foo { public function doAction( Bar $input ) { // ... } }
Slide 58
Slide 58 text
@GanbaroDigital PHP Fatal error: Class MyFoo may not inherit from final class (Foo) in test.php on line 19
Slide 59
Slide 59 text
@GanbaroDigital `final` is excellent at protecting behaviour from third-party change.
Slide 60
Slide 60 text
@GanbaroDigital But when that behaviour is incomplete or outright broken ...
Slide 61
Slide 61 text
@GanbaroDigital final In The Wild
Slide 62
Slide 62 text
@GanbaroDigital UUID RFC
Slide 63
Slide 63 text
@GanbaroDigital https://wiki.php.net/rfc/uuid
Slide 64
Slide 64 text
@GanbaroDigital final class UUID { // ... }
Slide 65
Slide 65 text
@GanbaroDigital UUIDs are globally-unique, non co-ordinated values.
Slide 66
Slide 66 text
@GanbaroDigital They are useful as primary keys.
Slide 67
Slide 67 text
@GanbaroDigital function doSomething (int $id, ...) { // ... }
Slide 68
Slide 68 text
@GanbaroDigital function doSomething (UUID $id, ...) { // ... }
Slide 69
Slide 69 text
@GanbaroDigital ?? ?? What if we want to abstract away our ID scheme?
Slide 70
Slide 70 text
@GanbaroDigital function doSomething (UUID $id, ...) { // ... }
Slide 71
Slide 71 text
@GanbaroDigital function doSomething (MyID $id, ...) { // ... }
Slide 72
Slide 72 text
@GanbaroDigital class MyID extends UUID { // ... }
Slide 73
Slide 73 text
@GanbaroDigital class MyID extends UUID { // ... } ✗
Slide 74
Slide 74 text
@GanbaroDigital `final` class UUID prevents us doing so via inheritance.
Slide 75
Slide 75 text
@GanbaroDigital ?? ?? How could we work around this?
Slide 76
Slide 76 text
@GanbaroDigital “ Use the decorator pattern for abstracting underlying behaviour.
Slide 77
Slide 77 text
@GanbaroDigital class MyID extends UUID { // ... }
Slide 78
Slide 78 text
@GanbaroDigital class MyID { private $uuid; public function __toString( return (string)$this->uuid; ); }
Slide 79
Slide 79 text
@GanbaroDigital class MyID { private $uuid; public function __toString( return (string)$this->uuid; ); }
Slide 80
Slide 80 text
@GanbaroDigital class MyID { private $uuid; public function __toString( return (string)$this->uuid; ); }
Slide 81
Slide 81 text
@GanbaroDigital UUIDs can be used as primary keys.
Slide 82
Slide 82 text
@GanbaroDigital UUIDs perform poorly as primary keys in popular database engines.
Slide 83
Slide 83 text
@GanbaroDigital The solution: CombGUIDs
Slide 84
Slide 84 text
@GanbaroDigital Jimmy Nelson, The Cost of GUIDs as Primary Keys, March 8th, 2002 http://www.informit.com/articles/ article.aspx?p=25862
Slide 85
Slide 85 text
@GanbaroDigital xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Slide 86
Slide 86 text
@GanbaroDigital xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx { -- MySQL -- }
Slide 87
Slide 87 text
@GanbaroDigital xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx { --- SQL Server --- }
Slide 88
Slide 88 text
@GanbaroDigital ?? ?? Should we use the decorator pattern to provide CombGUIDs?
Slide 89
Slide 89 text
@GanbaroDigital In this case, we are not adding an abstraction. We are adding a variant.
Slide 90
Slide 90 text
@GanbaroDigital The PHP UUID RFC did not include CombGUIDs.
Slide 91
Slide 91 text
@GanbaroDigital class MysqlCombGUID extends UUID { // ... }
Slide 92
Slide 92 text
@GanbaroDigital class SqlServerCombGUID extends UUID { // ... }
Slide 93
Slide 93 text
@GanbaroDigital final class UUID { // ... }
Slide 94
Slide 94 text
@GanbaroDigital The PHP UUID RFC did not include CombGUIDs ...
Slide 95
Slide 95 text
@GanbaroDigital ... and prevented anyone else from doing so from userland.
Slide 96
Slide 96 text
@GanbaroDigital “It’s unreasonable to expect package maintainers to know 100% of all possible variants.
Slide 97
Slide 97 text
@GanbaroDigital ?? ?? When is it reasonable to actively prevent third-party variants?
Slide 98
Slide 98 text
@GanbaroDigital ?? ?? How do we solve this?
Slide 99
Slide 99 text
@GanbaroDigital A SOLID Approach
Slide 100
Slide 100 text
@GanbaroDigital SOLID are design principles for object-oriented code
Slide 101
Slide 101 text
@GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov substitution • Interface segregation • Dependency inversion
Slide 102
Slide 102 text
@GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov substitution • Interface segregation • Dependency inversion
Slide 103
Slide 103 text
@GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov substitution • Interface segregation • Dependency inversion
Slide 104
Slide 104 text
@GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov substitution • Interface segregation • Dependency inversion
Slide 105
Slide 105 text
@GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov substitution • Interface segregation • Dependency inversion
Slide 106
Slide 106 text
@GanbaroDigital I’m going to focus on how `final` affects two SOLID principles.
Slide 107
Slide 107 text
@GanbaroDigital `final` Affects ...? • Single responsibility • Open/closed • Liskov substitution • Interface segregation • Dependency inversion
Slide 108
Slide 108 text
@GanbaroDigital `final` Affects ... • Single responsibility • Open/closed • Liskov substitution • Interface segregation • Dependency inversion
Slide 109
Slide 109 text
@GanbaroDigital Open/closed: open for extension, closed for modification.
Slide 110
Slide 110 text
@GanbaroDigital `final` ensures that a class is closed for modification!
Slide 111
Slide 111 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar ✗ ✗ ✗ Class Bar
Slide 112
Slide 112 text
@GanbaroDigital We’ve also seen that `final` ensures that the consuming class is closed for extension.
Slide 113
Slide 113 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar Class Foo Method doAction(Bar $input) ✗ ✗ ✗
Slide 114
Slide 114 text
@GanbaroDigital Liskov substitution principle: type T may be substituted by a sub-type S without altering the desirable properties of the program.
Slide 115
Slide 115 text
@GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar
Slide 116
Slide 116 text
@GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar with caveats!
Slide 117
Slide 117 text
@GanbaroDigital `final` prevents us creating sub-type S at all!
Slide 118
Slide 118 text
@GanbaroDigital final class Bar { // ... }
Slide 119
Slide 119 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar ✗ ✗ ✗ Class Bar
Slide 120
Slide 120 text
@GanbaroDigital ?? ?? Can `final` classes also be SOLID?
Slide 121
Slide 121 text
@GanbaroDigital SOLID Principles • Single responsibility • Open/closed • Liskov substitution • Interface segregation • Dependency inversion
Slide 122
Slide 122 text
@GanbaroDigital ?? ?? What if we used interfaces for type-hinting?
Slide 123
Slide 123 text
@GanbaroDigital final class Bar { // ... }
Slide 124
Slide 124 text
@GanbaroDigital final class Bar implements Interface1 { // ... }
Slide 125
Slide 125 text
@GanbaroDigital class Foo { public function doAction( Bar $input ) { // ... } }
Slide 126
Slide 126 text
@GanbaroDigital class Foo { public function doAction( Interface1 $input ) { // ... } }
Slide 127
Slide 127 text
@GanbaroDigital Class Foo Method doAction(Bar $input)
Slide 128
Slide 128 text
@GanbaroDigital Class Foo Method doAction(Interface1 $input)
Slide 129
Slide 129 text
@GanbaroDigital Class Bar Class Foo Method doAction(Interface1 $input)
Slide 130
Slide 130 text
@GanbaroDigital Class Bar Class Foo Method doAction(Interface1 $input) ✗
Slide 131
Slide 131 text
@GanbaroDigital Class Bar (Interface1) Class Foo Method doAction(Interface1 $input)
Slide 132
Slide 132 text
@GanbaroDigital Class Bar (Interface1) Class Foo Method doAction(Interface1 $input) ✓
Slide 133
Slide 133 text
@GanbaroDigital Class Bar (Interface1)
Slide 134
Slide 134 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar ✗ ✗ ✗ Class Bar (Interface1)
Slide 135
Slide 135 text
@GanbaroDigital Class Bar is still closed for modification. ✓
Slide 136
Slide 136 text
@GanbaroDigital Class Foo is open to new variants. ✓
Slide 137
Slide 137 text
@GanbaroDigital Class Bar (Interface1) Class C1 (Interface1) Class C2 (Interface1) Class C3 (Interface1)
Slide 138
Slide 138 text
@GanbaroDigital Class C1 (Interface1) Class C2 (Interface1) Class C3 (Interface1) Class Foo Method doAction(Interface1 $input)
Slide 139
Slide 139 text
@GanbaroDigital Class C1 (Interface1) Class C2 (Interface1) Class C3 (Interface1) Class Foo Method doAction(Interface1 $input) ✓ ✓ ✓
Slide 140
Slide 140 text
@GanbaroDigital ?? ?? What about sub-types and Liskov’s substitution principle?
Slide 141
Slide 141 text
@GanbaroDigital Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar ✗ ✗ ✗ Class Bar (Interface1)
Slide 142
Slide 142 text
@GanbaroDigital Class Bar (Interface1) Class C1 (Interface1) Class C2 (Interface1) Class C3 (Interface1)
Slide 143
Slide 143 text
@GanbaroDigital final class Bar implements Interface1 { // ... }
Slide 144
Slide 144 text
@GanbaroDigital interface SubType1 extends Interface1 { // ... }
Slide 145
Slide 145 text
@GanbaroDigital Class Bar Class C1 extends Bar Class C2 extends Bar Class C3 extends Bar with caveats!
Slide 146
Slide 146 text
@GanbaroDigital Interface1 SubType 2 SubType1 SubType3
Slide 147
Slide 147 text
@GanbaroDigital
Slide 148
Slide 148 text
@GanbaroDigital final class Foo { public function doAction( Interface1 $input ) { // ... } }
Slide 149
Slide 149 text
@GanbaroDigital
Slide 150
Slide 150 text
@GanbaroDigital final class Foo { public function doAction( Interface1 $input ) { // ... } }
Slide 151
Slide 151 text
@GanbaroDigital final class Foo implements Interface2 { public function doAction( Interface1 $input ) { // ... } }
Slide 152
Slide 152 text
@GanbaroDigital “ Behaviour is closed to modification. The system is open for extension (via variants).
Slide 153
Slide 153 text
@GanbaroDigital UUID RFC
Slide 154
Slide 154 text
@GanbaroDigital final class UUID { // ... }
Slide 155
Slide 155 text
@GanbaroDigital function doSomething (UUID $id, ...) { // ... }
Slide 156
Slide 156 text
@GanbaroDigital
Slide 157
Slide 157 text
@GanbaroDigital interface UUID { // ... }
Slide 158
Slide 158 text
@GanbaroDigital class MysqlCombGUID extends UUID { // ... }
Slide 159
Slide 159 text
@GanbaroDigital class MysqlCombGUID extends UUID { // ... } ✗
Slide 160
Slide 160 text
@GanbaroDigital class MysqlCombGUID implements UUID { // ... }
Slide 161
Slide 161 text
@GanbaroDigital class SqlServerCombGUID implements UUID { // ... }
Slide 162
Slide 162 text
@GanbaroDigital Userland UUID variants would be fully supported.
Slide 163
Slide 163 text
@GanbaroDigital class MyID extends UUID { // ... }
Slide 164
Slide 164 text
@GanbaroDigital class MyID extends UUID { // ... } ✗
Slide 165
Slide 165 text
@GanbaroDigital class MyID { private $uuid; public function __toString( return (string)$this->uuid; ); }
Slide 166
Slide 166 text
@GanbaroDigital Abstractions should still be done via aggregation & the decorator pattern.
Slide 167
Slide 167 text
@GanbaroDigital Abstractions would benefit from some interfaces too!
Slide 168
Slide 168 text
@GanbaroDigital In Summary
Slide 169
Slide 169 text
@GanbaroDigital “ Use the decorator pattern for abstracting underlying behaviour.
Slide 170
Slide 170 text
@GanbaroDigital “ The behaviour of `final` classes can be consumed, not modified.
Slide 171
Slide 171 text
@GanbaroDigital “ `final` classes lock down type-hinted method parameters.
Slide 172
Slide 172 text
@GanbaroDigital “It’s unreasonable to expect package maintainers to know 100% of all possible variants.
Slide 173
Slide 173 text
@GanbaroDigital “ Use interface inheritance for new variants.
Slide 174
Slide 174 text
@GanbaroDigital “ `final` classes that don’t implement interfaces restrict package re-use.
Slide 175
Slide 175 text
@GanbaroDigital “ `final` classes that don’t implement interfaces are not SOLID.
Slide 176
Slide 176 text
@GanbaroDigital “ `final` classes that don’t implement interfaces are a code smell.
Slide 177
Slide 177 text
@GanbaroDigital “Don’t use `final` classes as type-hints. Use interfaces.
Slide 178
Slide 178 text
@GanbaroDigital
Slide 179
Slide 179 text
@GanbaroDigital “ Behaviour is closed to modification. The system is open for extension (via variants).
Slide 180
Slide 180 text
Thank You Any Questions? A presentation by @stuherbert for @GanbaroDigital