├── .gitignore
├── .idea
├── inspectionProfiles
│ └── Project_Default.xml
├── ipc19se-writing-really-good-code.iml
├── misc.xml
├── modules.xml
├── php-inspections-ea-ultimate.xml
├── php.xml
└── vcs.xml
├── .php_cs.dist
├── README.md
├── doc
└── prices.png
├── phive.xml
├── phpunit.xml
├── psalm.xml
├── src
├── Exception.php
├── Good.php
├── Market.php
├── Milk.php
├── Offer.php
├── OutOfRangeException.php
├── Pound.php
├── PriceList.php
├── PriceListBuilder.php
├── Quantity.php
└── autoload.php
├── tests
├── GoodTest.php
├── MarketTest.php
├── OfferTest.php
├── PoundTest.php
├── PriceListBuilderTest.php
├── PriceListTest.php
└── QuantityTest.php
└── tools
├── php-cs-fixer
├── phpab
├── phpunit
├── phpunit.phar
└── psalm
/.gitignore:
--------------------------------------------------------------------------------
1 | .php_cs.cache
2 | .phpunit.result.cache
3 |
4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
5 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
6 |
7 | # User-specific stuff
8 | .idea/**/workspace.xml
9 | .idea/**/tasks.xml
10 | .idea/**/usage.statistics.xml
11 | .idea/**/dictionaries
12 | .idea/**/shelf
13 |
14 | # Generated files
15 | .idea/**/contentModel.xml
16 |
17 | # Sensitive or high-churn files
18 | .idea/**/dataSources/
19 | .idea/**/dataSources.ids
20 | .idea/**/dataSources.local.xml
21 | .idea/**/sqlDataSources.xml
22 | .idea/**/dynamic.xml
23 | .idea/**/uiDesigner.xml
24 | .idea/**/dbnavigator.xml
25 |
26 | # Gradle
27 | .idea/**/gradle.xml
28 | .idea/**/libraries
29 |
30 | # Gradle and Maven with auto-import
31 | # When using Gradle or Maven with auto-import, you should exclude module files,
32 | # since they will be recreated, and may cause churn. Uncomment if using
33 | # auto-import.
34 | # .idea/modules.xml
35 | # .idea/*.iml
36 | # .idea/modules
37 |
38 | # CMake
39 | cmake-build-*/
40 |
41 | # Mongo Explorer plugin
42 | .idea/**/mongoSettings.xml
43 |
44 | # File-based project format
45 | *.iws
46 |
47 | # IntelliJ
48 | out/
49 |
50 | # mpeltonen/sbt-idea plugin
51 | .idea_modules/
52 |
53 | # JIRA plugin
54 | atlassian-ide-plugin.xml
55 |
56 | # Cursive Clojure plugin
57 | .idea/replstate.xml
58 |
59 | # Crashlytics plugin (for Android Studio and IntelliJ)
60 | com_crashlytics_export_strings.xml
61 | crashlytics.properties
62 | crashlytics-build.properties
63 | fabric.properties
64 |
65 | # Editor-based Rest Client
66 | .idea/httpRequests
67 |
68 | # Android studio 3.1+ serialized cache file
69 | .idea/caches/build_file_checksums.ser
70 | /website/public/css/styles.css
71 | /website/public/css/styles.css.map
72 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
--------------------------------------------------------------------------------
/.idea/ipc19se-writing-really-good-code.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/php-inspections-ea-ultimate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/php.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.php_cs.dist:
--------------------------------------------------------------------------------
1 | setRiskyAllowed(true)
4 | ->setRules(
5 | [
6 | 'align_multiline_comment' => true,
7 | 'array_indentation' => true,
8 | 'array_syntax' => ['syntax' => 'short'],
9 | 'binary_operator_spaces' => [
10 | 'operators' => [
11 | '=' => 'align',
12 | '=>' => 'align',
13 | ],
14 | ],
15 | 'blank_line_after_namespace' => true,
16 | 'blank_line_before_statement' => [
17 | 'statements' => [
18 | 'break',
19 | 'continue',
20 | 'declare',
21 | 'do',
22 | 'for',
23 | 'foreach',
24 | 'if',
25 | 'include',
26 | 'include_once',
27 | 'require',
28 | 'require_once',
29 | 'return',
30 | 'switch',
31 | 'throw',
32 | 'try',
33 | 'while',
34 | 'yield',
35 | ],
36 | ],
37 | 'braces' => true,
38 | 'cast_spaces' => true,
39 | 'class_attributes_separation' => ['elements' => ['const', 'method', 'property']],
40 | 'combine_consecutive_issets' => true,
41 | 'combine_consecutive_unsets' => true,
42 | 'compact_nullable_typehint' => true,
43 | 'concat_space' => ['spacing' => 'one'],
44 | 'declare_equal_normalize' => ['space' => 'none'],
45 | 'declare_strict_types' => true,
46 | 'dir_constant' => true,
47 | 'elseif' => true,
48 | 'encoding' => true,
49 | 'full_opening_tag' => true,
50 | 'function_declaration' => true,
51 | 'indentation_type' => true,
52 | 'is_null' => true,
53 | 'line_ending' => true,
54 | 'list_syntax' => ['syntax' => 'short'],
55 | 'logical_operators' => true,
56 | 'lowercase_cast' => true,
57 | 'lowercase_constants' => true,
58 | 'lowercase_keywords' => true,
59 | 'lowercase_static_reference' => true,
60 | 'magic_constant_casing' => true,
61 | 'method_argument_space' => ['ensure_fully_multiline' => true],
62 | 'modernize_types_casting' => true,
63 | 'multiline_comment_opening_closing' => true,
64 | 'multiline_whitespace_before_semicolons' => true,
65 | 'native_constant_invocation' => true,
66 | 'native_function_casing' => true,
67 | 'native_function_invocation' => true,
68 | 'new_with_braces' => false,
69 | 'no_alias_functions' => true,
70 | 'no_alternative_syntax' => true,
71 | 'no_blank_lines_after_class_opening' => true,
72 | 'no_blank_lines_after_phpdoc' => true,
73 | 'no_blank_lines_before_namespace' => true,
74 | 'no_closing_tag' => true,
75 | 'no_empty_comment' => true,
76 | 'no_empty_phpdoc' => true,
77 | 'no_empty_statement' => true,
78 | 'no_extra_blank_lines' => true,
79 | 'no_homoglyph_names' => true,
80 | 'no_leading_import_slash' => true,
81 | 'no_leading_namespace_whitespace' => true,
82 | 'no_mixed_echo_print' => ['use' => 'print'],
83 | 'no_multiline_whitespace_around_double_arrow' => true,
84 | 'no_null_property_initialization' => true,
85 | 'no_php4_constructor' => true,
86 | 'no_short_bool_cast' => true,
87 | 'no_short_echo_tag' => true,
88 | 'no_singleline_whitespace_before_semicolons' => true,
89 | 'no_spaces_after_function_name' => true,
90 | 'no_spaces_inside_parenthesis' => true,
91 | 'no_superfluous_elseif' => true,
92 | 'no_superfluous_phpdoc_tags' => true,
93 | 'no_trailing_comma_in_list_call' => true,
94 | 'no_trailing_comma_in_singleline_array' => true,
95 | 'no_trailing_whitespace' => true,
96 | 'no_trailing_whitespace_in_comment' => true,
97 | 'no_unneeded_control_parentheses' => true,
98 | 'no_unneeded_curly_braces' => true,
99 | 'no_unneeded_final_method' => true,
100 | 'no_unreachable_default_argument_value' => true,
101 | 'no_unset_on_property' => true,
102 | 'no_unused_imports' => true,
103 | 'no_useless_else' => true,
104 | 'no_useless_return' => true,
105 | 'no_whitespace_before_comma_in_array' => true,
106 | 'no_whitespace_in_blank_line' => true,
107 | 'non_printable_character' => true,
108 | 'normalize_index_brace' => true,
109 | 'object_operator_without_whitespace' => true,
110 | 'ordered_class_elements' => [
111 | 'order' => [
112 | 'use_trait',
113 | 'constant_public',
114 | 'constant_protected',
115 | 'constant_private',
116 | 'property_public_static',
117 | 'property_protected_static',
118 | 'property_private_static',
119 | 'property_public',
120 | 'property_protected',
121 | 'property_private',
122 | 'method_public_static',
123 | 'construct',
124 | 'destruct',
125 | 'magic',
126 | 'phpunit',
127 | 'method_public',
128 | 'method_protected',
129 | 'method_private',
130 | 'method_protected_static',
131 | 'method_private_static',
132 | ],
133 | ],
134 | 'ordered_imports' => true,
135 | 'ordered_interfaces' => [
136 | 'direction' => 'ascend',
137 | 'order' => 'alpha',
138 | ],
139 | 'phpdoc_add_missing_param_annotation' => true,
140 | 'phpdoc_align' => true,
141 | 'phpdoc_annotation_without_dot' => true,
142 | 'phpdoc_indent' => true,
143 | 'phpdoc_no_access' => true,
144 | 'phpdoc_no_empty_return' => true,
145 | 'phpdoc_no_package' => true,
146 | 'phpdoc_order' => true,
147 | 'phpdoc_return_self_reference' => true,
148 | 'phpdoc_scalar' => true,
149 | 'phpdoc_separation' => true,
150 | 'phpdoc_single_line_var_spacing' => true,
151 | 'phpdoc_to_comment' => true,
152 | 'phpdoc_trim' => true,
153 | 'phpdoc_trim_consecutive_blank_line_separation' => true,
154 | 'phpdoc_types' => ['groups' => ['simple', 'meta']],
155 | 'phpdoc_types_order' => true,
156 | 'phpdoc_var_without_name' => true,
157 | 'pow_to_exponentiation' => true,
158 | 'protected_to_private' => true,
159 | 'return_assignment' => true,
160 | 'return_type_declaration' => ['space_before' => 'none'],
161 | 'self_accessor' => true,
162 | 'semicolon_after_instruction' => true,
163 | 'set_type_to_cast' => true,
164 | 'short_scalar_cast' => true,
165 | 'simplified_null_return' => true,
166 | 'single_blank_line_at_eof' => true,
167 | 'single_import_per_statement' => true,
168 | 'single_line_after_imports' => true,
169 | 'single_quote' => true,
170 | 'standardize_not_equals' => true,
171 | 'ternary_to_null_coalescing' => true,
172 | 'trailing_comma_in_multiline_array' => true,
173 | 'trim_array_spaces' => true,
174 | 'unary_operator_spaces' => true,
175 | 'visibility_required' => [
176 | 'elements' => [
177 | 'const',
178 | 'method',
179 | 'property',
180 | ],
181 | ],
182 | 'void_return' => true,
183 | 'whitespace_after_comma_in_array' => true,
184 | ]
185 | )
186 | ->setFinder(
187 | PhpCsFixer\Finder::create()
188 | ->files()
189 | ->in(__DIR__ . '/src')
190 | ->in(__DIR__ . '/tests')
191 | ->notName('autoload.php')
192 | );
193 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Writing Really Good Code
2 | ## Sebastian Bergmann and Arne Blankerts
3 |
4 | > What makes up clean code? How does code turn out well? And how do you write really good code? In this workshop you will not only learn the answers to these questions. You will have the opportunity to immediately apply what you learn in a practical exercise. First, we will show how Domain-Driven Design and Test-Driven Development can be used to solve problems. Needless to say that we will cover topics such as Clean Code and SOLID along the way. Coached by the trainers, you will then work in pairs on additional features for the software we developed during the live coding. We will round up the day with a review where you will get feedback on the code you created. You will need to bring your own laptop to really benefit from this workshop. A recent version of PHP 7 and PHPUnit as well as your IDE of choice are all that is needed. No frameworks or third-party code are required.
5 | >
6 | > https://thephp.cc/dates/2019/06/international-php-conference-spring-edition/writing-really-good-code
7 |
8 | Code written for/during the "Writing Really Good Code" workshop at International PHP Conference 2019 (Spring Edition).
9 |
10 | This is example code that is not production-ready. It is intended for studying and learning purposes.
11 |
12 | (c) 2019 thePHP.cc. All rights reserved.
13 |
--------------------------------------------------------------------------------
/doc/prices.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thePHPcc/ipc19se-writing-really-good-code/dd922e309c0ce66f1b72c1ac75bae4175a6164b8/doc/prices.png
--------------------------------------------------------------------------------
/phive.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | tests
15 |
16 |
17 |
18 |
19 |
20 | src
21 |
22 | src/autoload.php
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/Exception.php:
--------------------------------------------------------------------------------
1 | amount()->amount() *
15 | $this->priceFor($offer->good())->amount()
16 | );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Milk.php:
--------------------------------------------------------------------------------
1 | amount = $unit;
19 | $this->good = $good;
20 | }
21 |
22 | public function amount(): Quantity
23 | {
24 | return $this->amount;
25 | }
26 |
27 | public function good(): Good
28 | {
29 | return $this->good;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/OutOfRangeException.php:
--------------------------------------------------------------------------------
1 | amount = $amount;
14 | }
15 |
16 | public function amount(): int
17 | {
18 | return $this->amount;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/PriceList.php:
--------------------------------------------------------------------------------
1 | prices = $prices;
27 | }
28 |
29 | public function current(): Pound
30 | {
31 | return $this->prices[$this->position];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/PriceListBuilder.php:
--------------------------------------------------------------------------------
1 | ensureIsGreaterThan0($amount);
14 |
15 | $this->amount = $amount;
16 | }
17 |
18 | public function amount(): int
19 | {
20 | return $this->amount;
21 | }
22 |
23 | private function ensureIsGreaterThan0(int $amount): void
24 | {
25 | if ($amount < 1) {
26 | throw new OutOfRangeException(
27 | \sprintf(
28 | '"%d" is not greater than 0',
29 | $amount
30 | )
31 | );
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/autoload.php:
--------------------------------------------------------------------------------
1 | '/Exception.php',
11 | 'clansofcaledonia\\good' => '/Good.php',
12 | 'clansofcaledonia\\market' => '/Market.php',
13 | 'clansofcaledonia\\milk' => '/Milk.php',
14 | 'clansofcaledonia\\offer' => '/Offer.php',
15 | 'clansofcaledonia\\outofrangeexception' => '/OutOfRangeException.php',
16 | 'clansofcaledonia\\pound' => '/Pound.php',
17 | 'clansofcaledonia\\pricelist' => '/PriceList.php',
18 | 'clansofcaledonia\\pricelistbuilder' => '/PriceListBuilder.php',
19 | 'clansofcaledonia\\quantity' => '/Quantity.php'
20 | );
21 | }
22 | $cn = strtolower($class);
23 | if (isset($classes[$cn])) {
24 | require __DIR__ . $classes[$cn];
25 | }
26 | },
27 | true,
28 | false
29 | );
30 | // @codeCoverageIgnoreEnd
31 |
--------------------------------------------------------------------------------
/tests/GoodTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($milk->isMilk());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/MarketTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(new Pound(5), $market->priceFor(Good::milk()));
21 | }
22 |
23 | public function testMilkCanBeSoldToTheMarket(): Market
24 | {
25 | $market = new Market;
26 |
27 | $payment = $market->sellTo(
28 | new Offer(
29 | new Quantity(2),
30 | Good::milk()
31 | )
32 | );
33 |
34 | $this->assertEquals(new Pound(10), $payment);
35 |
36 | return $market;
37 | }
38 |
39 | /**
40 | * @depends testMilkCanBeSoldToTheMarket
41 | */
42 | public function testSellingMilkToTheMarketReducesMilkPrice(Market $market): void
43 | {
44 | $this->assertEquals(new Pound(4), $market->priceFor(Good::milk()));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/OfferTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(new Quantity(1), $offer->amount());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/PoundTest.php:
--------------------------------------------------------------------------------
1 | assertSame($amount, $p->amount());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/PriceListBuilderTest.php:
--------------------------------------------------------------------------------
1 | milkPrices();
19 |
20 | $this->assertEquals(new Pound(5), $milkPrices->current());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/PriceListTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(new Pound(4), $prices->current());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/QuantityTest.php:
--------------------------------------------------------------------------------
1 | assertSame(1, $unit->amount());
16 | }
17 |
18 | /**
19 | * @dataProvider invalidAmounts
20 | */
21 | public function testAmountMustBeGreaterThan0(int $amount): void
22 | {
23 | $this->expectException(OutOfRangeException::class);
24 |
25 | new Quantity($amount);
26 | }
27 |
28 | public function invalidAmounts(): array
29 | {
30 | return [
31 | [
32 | 0,
33 | -1,
34 | ],
35 | ];
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tools/php-cs-fixer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thePHPcc/ipc19se-writing-really-good-code/dd922e309c0ce66f1b72c1ac75bae4175a6164b8/tools/php-cs-fixer
--------------------------------------------------------------------------------
/tools/phpab:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thePHPcc/ipc19se-writing-really-good-code/dd922e309c0ce66f1b72c1ac75bae4175a6164b8/tools/phpab
--------------------------------------------------------------------------------
/tools/phpunit:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thePHPcc/ipc19se-writing-really-good-code/dd922e309c0ce66f1b72c1ac75bae4175a6164b8/tools/phpunit
--------------------------------------------------------------------------------
/tools/phpunit.phar:
--------------------------------------------------------------------------------
1 | phpunit
--------------------------------------------------------------------------------
/tools/psalm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thePHPcc/ipc19se-writing-really-good-code/dd922e309c0ce66f1b72c1ac75bae4175a6164b8/tools/psalm
--------------------------------------------------------------------------------