-
Notifications
You must be signed in to change notification settings - Fork 1
/
challenges.yml
2512 lines (2319 loc) · 100 KB
/
challenges.yml
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
68
69
70
71
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Extending
extending_inheritfunctionalitycoding:
question: |
Your team has been working hard building the `DeathStar` class, only
to find out that the Rebels have just destroyed it! Time to rebuild!
Long live the Empire!
question_steps:
- |
Create a new class called `DeathStarII` in the `DeathStarII.php` file
and make it inherit all of the functionality from the original `DeathStar`.
- In `index.php`, instantiate a new `DeathStarII` object and set it to a `$deathStar` variable.
step: extending-inherit-functionality
files:
index.php: ~
DeathStarII.php: ~
DeathStar.php:
read_only: true
features:
editor: ~
browser: ~
grading:
check_variable:
type: php
assertTrue: variable_exists('deathStar')
failure: Make sure the `$deathStar` variable is exist
assert_instance_of_1:
type: php
assertTrue: get_class(variable('deathStar')) === 'DeathStarII'
failure: The `$deathStar` variable exists, but is not set to a `DeathStarII` object.
assert_instance_of_2:
type: php
assertTrue: is_subclass_of(variable('deathStar'), '\DeathStar')
failure: The `DeathStarII` class is not extending `DeathStar` one.
extending_callnonexistentmethodmc:
question: |
Look at these two classes:
```php
class Ship
{
public function getName()
{
return 'Starfighter';
}
}
```
```php
class JediShip extends Ship
{
public function getFavoriteJedi()
{
return 'Yoda';
}
}
```
Suppose we instantiate both objects:
```php
$ship = new Ship();
$jediShip = new JediShip();
```
Which of the following lines will cause an error?
explanation: |
The `Ship` object does *not* have a `getFavoriteJedi()` method
on it - only `JediShip` has this. But, since `JediShip extends Ship`,
the `JediShip` *does* have a `getName()` method: it inherits it from
`Ship`.
features:
multiple_choice:
choices:
a: |
```php
echo $ship->getName();
```
b: |
```php
echo $ship->getFavoriteJedi();
```
c: |
```php
echo $jediShip->getName();
```
d: Both (B) and (C) will cause an error
correct: b
# Override
override_overrideinheritmethodcoding:
question: |
Well, we learned some hard lessons after the destruction of the original `DeathStar`,
and we don't want to repeat them!
question_steps:
- Override the `getWeakness()` method in `DeathStarII` and make it return `null`. Phew, problem solved!
step: override-override-inherited-method
files:
DeathStarII.php: ~
index.php:
read_only: true
DeathStar.php:
read_only: true
features:
editor: ~
browser: ~
grading:
assert_method:
type: php
assertTrue: variable('new').getWeakness() === null
failure: The `getWeakness()` method of `DeathStarII` class does not return `null`.
override_calloverriddenmethodmc:
question: |
Look at the following classes:
```php
class Ship
{
public function printType()
{
echo 'Empire Ship';
$this->printMotto();
}
public function printMotto()
{
echo 'I like to fly!';
}
}
```
```php
class RebelShip extends Ship
{
public function printType()
{
echo 'Rebel Ship';
}
}
```
What is the result of the following code:
```php
$ship = new Ship();
$rebelShip = new RebelShip();
$ship->printType();
$rebelShip->printType();
```
explanation: |
For `Ship`, `printType()` prints "Empire Ship" and then also
calls `printMotto()`. But for `RebelShip`, `printType()` only
prints "Rebel Ship". *Nothing* calls the `printMotto()`
function in this case.
features:
multiple_choice:
choices:
a: Empire ShipRebel Ship
b: Empire ShipI like to fly!Rebel Ship
c: Empire ShipI like to fly!Rebel ShipI like to fly!
d: RebelShipRebelShip
correct: b
# ProtectedVisibility
protectedvisibility_getinheritedpropertycoding:
question: |
The construction of the `DeathStarII` continues, but we need access
to the `planetarySuperLaserRange` property from the original, because
we're going to make it fire twice as far!
question_steps:
- Fix the `DeathStar` class so that the new `getSpecs()` method works
step: protected-visibility-get-inherited-property
files:
DeathStar.php: ~
DeathStarII.php:
read_only: true
index.php:
read_only: true
features:
editor: ~
browser: ~
grading:
check_property:
type: php
assertTrue: reflection_class('\DeathStar').hasProperty('planetarySuperLaserRange')
failure: The `planetarySuperLaserRange` property does not exist in the `DeathStar` class.
check_property_visibility:
type: php
assertTrue: reflection_class('\DeathStar').getProperty('planetarySuperLaserRange').isProtected()
failure: The `planetarySuperLaserRange` property should have protected visibility.
protectedvisibility_propertyvisibilitymc:
question: |
Check out the following code:
```php
// Ship.php
class Ship
{
public $name;
protected $weaponPower;
private $defense;
public function getDefense()
{
return $this->defense;
}
}
```
```php
// JediShip.php
class JediShip extends Ship
{
public function getWeaponPower()
{
return $this->weaponPower;
}
}
```
```php
// index.php
$jediShip = new JediShip();
$jediShip->name;
$jediShip->weaponPower;
```
Which of the above code will give us an error?
explanation: |
Since the `$defense` property is `private`, we can only access it from
within the `Ship` class. But that's exactly what we're doing in (B),
so that's fine.
The `$weaponPower` property is `protected`. That means we can access it
only from inside `Ship`, `JediShip` or any other sub-classes. That's why
(C) is valid. But in (A), we're accessing `weaponPower` from `index.php`.
Accessing a property or method from outside of the class is only allowed
if it is public. This is a bad code.
features:
multiple_choice:
choices:
a: '`$jediShip->weaponPower` in `index.php`'
b: '`return $this->defense` in `Ship.php`'
c: '`return $this->weaponPower` in `JediShip.php`'
d: None of the above is bad code - we're awesome!
correct: a
# Parent
parent_callparentmethodcoding:
question: |
It took too long to travel to planets to destroy them
in the first DeathStar, so Darth wants the laser range
on the new DeathStar to be *twice* as far! Brilliant!
question_steps:
- |
Override the `getLaserRange()` method in `DeathStarII` to make
this happen. (But don't repeat the `2000000` value!)
- Call the parent function and then multiply that value by 2!
step: parent-call-parent-method
files:
DeathStarII.php: ~
DeathStar.php:
read_only: true
index.php:
read_only: true
features:
editor: ~
browser: ~
grading:
check_method:
type: php
assertTrue: method_exists(variable('deathStar'), 'getLaserRange')
failure: The `getLaserRange()` method does not exist for the `DeathStarII` class - did you create it?
assert_method:
type: php
assertTrue: 4000000 === variable('deathStar').getLaserRange()
failure: The `getLaserRange()` method in the `DeathStarII` class should return a doubled value of the parent method.
# AbstractShip
abstractship_extendabstractclasscoding:
question: |
We've just gotten word that the Rebels have *also*
destroyed the `DeathStarII`. Wow, rotten luck. Anyways,
it sounds like we'll be creating blue prints for many
different types of DeathStars in the future, to keep
the Rebels guessing.
To make this easier...
question_steps:
- Create an `AbstractDeathStar` class and move all of the shared code into it.
- |
Update `DeathStar` and `DeathStarII` to extend this new class and make sure to get rid of anything in those classes
that you've moved into the new parent class.
step: abstract-ship-extend-abstract-class
files:
AbstractDeathStar.php: ~
DeathStar.php: ~
DeathStarII.php: ~
index.php:
read_only: true
features:
editor: ~
browser: ~
grading:
check_class:
type: php
assertTrue: class_exists('\AbstractDeathStar')
failure: The `AbstractDeathStar` class does not exist! Did you create it?
check_methods:
type: php
assertTrue: reflection_class('\AbstractDeathStar').hasMethod('setWeaponPower') and reflection_class('\AbstractDeathStar').hasMethod('getWeaponPower') and reflection_class('\AbstractDeathStar').hasMethod('makeFiringNoise')
failure: The `AbstractDeathStar` class should have `getWeaponPower()`, `setWeaponPower()`, and `makeFiringNoise()` methods.
check_method:
type: php
assertFalse: reflection_class('\AbstractDeathStar').hasMethod('setCrewSize')
failure: The `AbstractDeathStar` class should not have a `setCrewSize()` method! The `DeathStarII` just has a hardcoded crew size, so does not need this setter. Move it to `Deathstar`.
assert_subclass_of:
type: php
assertTrue: reflection_class('\DeathStar').isSubclassOf('\AbstractDeathStar') and reflection_class('\DeathStarII').isSubclassOf('\AbstractDeathStar')
failure: The both `DeathStar` and `DeathStarII` classes should extend `AbstractDeathStar` one.
check_methods_removed:
type: php
assertFalse: |
false
or inputContains('DeathStar.php', 'getWeaponPower')
or inputContains('DeathStar.php', 'setWeaponPower')
or inputContains('DeathStar.php', 'makeFiringNoise')
failure: The `DeathStar` class should not have `getWeaponPower()`, `setWeaponPower()`, `makeFiringNoise()` methods.
check_property_removed:
type: php
assertFalse: inputContains('DeathStar.php', 'private $weaponPower')
failure: The `DeathStar` class does not need `$weaponPower` property anymore.
abstractship_inheritanceoverheadmc:
question: |
A co-worker created a few classes and has asked for
your advice about organizing them:
```php
class Ship
{
private $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
// other stuff...
}
```
```php
class Person
{
private $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
// other stuff...
}
```
Your teammate is wondering if this can be organized better.
Which if the following is the best advice?
explanation: |
Even though both classes share some code, a `Ship`
and a `Person` fundamentally aren't the same thing,
and probably don't have any other overlapping code.
So, you *could* create an `AbstractNamedItem`, but
that's a bit awkward. And remember, you can only
extend *one* class, so make sure your parent class
makes sense.
In this case, the best action is to do nothing: leave
these two blueprints totally independent. In a future
episode, we'll talk about traits: a cool way to help
remove duplication without inheritance.
features:
multiple_choice:
choices:
a: |
Create a new `AbstractNamedItem` class that has the
`name` property and the `getName()` and `setName()`
methods. Then, make `Person` and `Ship` extend this
class.
b: |
Make `Ship` extend `Person`, and remove all the
duplicated code in `Person`.
c: |
Leave things exactly like they are now.
correct: c
# AddingAbstract
addingabstract_functiontypehintwithclassnamemc:
question: |
Check out these classes that the intern created,
which all have confusing names:
```php
abstract class OtherClass extends GreatClass
{
}
```
```php
abstract class SomeClass extends OtherClass
{
}
```
```php
class GreatClass
{
}
```
```php
class MyClass extends OtherClass
{
}
```
```php
class Puppy extends SomeClass
{
}
```
```php
function doSomething(OtherClass $thing)
{
// ...
}
```
Based on the type-hint, objects of which classes could be passed
to the `doSomething()` function?
explanation: |
Since the type-hint is `OtherClass`, any `OtherClass` object or sub-classes
are accepted. The sub-classes are `MyClass` and `SomeClass` (directly)
and also `Puppy` (though `SomeClass`). But we can instantiate objects
*only* of `MyClass` and `Puppy` classes, because `OtherClass` and `SomeClass`
are abstract!
features:
multiple_choice:
choices:
a: '`OtherClass` and `SomeClass`'
b: '`OtherClass` and `MyClass`'
c: '`MyClass` and `Puppy`'
d: '`OtherClass` and `GreatClass`'
correct: c
addingabstract_createabstractmethodcoding:
question: |
When Darth is browsing all the different `DeathStar` models, we want to print
out a little description that describes each one. Inside of that description,
we want to include the "laser range", but it's different based on the model.
question_steps:
- Create an abstract method called `getLaserRange()` in `AbstractDeathStar`.
- Use that in `getDescription()` to set the `$range` variable.
- Make sure the `DeathStar` class has range = 500.
- Lastly, make sure the `DeathStarII` class has range = 900.
step: adding-abstract-create-abstract-method
files:
AbstractDeathStar.php: ~
DeathStarII.php: ~
DeathStar.php: ~
index.php:
read_only: true
features:
editor: ~
browser: ~
grading:
look_for_method:
type: php
assertTrue: reflection_class('\AbstractDeathStar').hasMethod('getLaserRange')
failure: Method `getLaserRange()` does not exist in the `AbstractDeathStar` class. Did you create it?
assert_abstract:
type: php
assertTrue: reflection_class('\AbstractDeathStar').getMethod('getLaserRange').isAbstract()
failure: Method `getLaserRange()` declared in the `AbstractDeathStar` class should be abstract.
assert_method_value:
type: php
assertTrue: 500 == variable('deathStar').getLaserRange() and 900 == variable('deathStar2').getLaserRange()
failure: Method `getLaserRange()` should return `500` for the `DeathStar` class and `900` for the `DeathStarII`.
assert_method_call:
type: php
assertTrue: inputContains('AbstractDeathStar.php', '->getLaserRange')
failure: Did you call `getLaserRange()` method in the `AbstractDeathStar` class?
check_output:
type: http
assertTrue: body() matches '/500/' and body() matches '/900/'
failure: I don't see laser ranges in the output - did you print it?
# BrokenShip
brokenship_createdeathstariiicoding:
question: |
I feel like we're *always* designing new DeathStars. Well, no time to complain
time to start the DeathStarIII!
question_steps:
- Create a new `DeathStarIII` class, make it extend `AbstractDeathStar`, and fill in any missing abstract methods.
- Lastly, print out the description in `index.php`.
step: broken-ship-create-deathstartiii-class
files:
index.php: ~
DeathStarIII.php: ~
AbstractDeathStar.php:
read_only: true
features:
editor: ~
browser: ~
grading:
assert_subclass_of:
type: php
assertTrue: reflection_class('\DeathStarIII').isSubclassOf('\AbstractDeathStar')
failure: The `DeathStarIII` class should extend the `AbstractDeathStar` one.
look_for_variable:
type: php
assertTrue: variable_exists('deathStar3')
failure: I don't see the `$deathStar3` variable in `index.php` - did you create it?
assert_instance_of:
type: php
assertTrue: get_class(variable('deathStar3')) === 'DeathStarIII'
failure: The `$deathStar3` variable exists, but is not set to a `DeathStarIII` object.
look_for_text:
type: http
assertTrue: css('h3').text() matches '/with a range of/'
failure: Hmm, did you print the `DeathStarIII` description in the `h3` tag?
assert_number:
type: http
assertTrue: css('h3').text() matches '/with a range of [0-9]+/'
failure: The `getLaserRange()` method on `DeathStarIII` class must returns a number.
# PdoShipStorage
pdoshipstorage_decomposestringtransformercoding:
question: |
Tired from working on the DeathStar, you challenged the intern (let's call her
"Morgan") to create a class that can reverse a string and upper case every other letter.
"Ha!" Morgan says, "This is simple!". To show off, Morgan creates the `StringTransformer`
class and *even* makes it cache the results to be super-performant.
But wait you say! Combining the string transformation *and* caching into the same
class make `StringTransformer` responsible for two jobs. Help show Morgan the intern
a better way.
question_steps:
- Creating a new `Cache` class with two methods `fetchFromCache($key)` and `saveToCache($key, $val)`.
- Then, pass this into `StringTransformer` and use it to cache, instead of using your own logic.
step: pdo-ship-storage-decompose-string-transformer
files:
index.php: ~
StringTransformer.php: ~
Cache.php: ~
features:
editor: ~
browser: ~
grading:
check_class:
type: php
assertTrue: class_exists('\Cache')
failure: Class `Cache` does not exist. Did you create it?
check_method_from_cache:
type: php
assertTrue: reflection_class('\Cache').hasMethod('fetchFromCache')
failure: Method `fetchFromCache()` does not exist in the `Cache` class.
check_method:
type: php
assertTrue: reflection_class('\Cache').hasMethod('saveToCache')
failure: Method `saveToCache()` does not exist in the `Cache` class.
check_variable:
type: php
assertTrue: variable_exists('transformer')
failure: I don't see the `$transformer` variable in `index.php` anymore - did you delete it?
assert_variable:
type: php
assertTrue: get_class(variable('transformer')) === 'StringTransformer'
failure: Make sure you give the `StringTransformer` class a `__construct()`. It should have one argument - a `Cache` object.
assert_method:
type: php
assertTrue: reflection_class('\StringTransformer').hasMethod('__construct')
failure: Make sure you give the `StringTransformer` class a `__construct()`. It should have one argument - a `Cache` object.
look_for_function:
type: http
assertFalse: body() matches '/file_get_contents/'
failure: I still see `file_get_contents()` inside of `StringTransformer`. Make sure you've moved all of the caching logic into the Cache class.
# @TODO Create a mocked Cache, pass it into
# @TODO $transformer and assert that its cache methods are called
pdoshipstorage_stringtransformerdecompositionadvantagesmc:
question: |
In the previous challenge, you split the logic from `StringTransformer`
into two different classes. What are the advantages of this?
explanation: |
All of these are advantages! Before, you might not even realize that
the `StringTransformer` had caching logic, but now its very obvious:
the caching logic is in a class called `Cache` and you can see that
the `StringTransformer` requires a `Cache` object. You could also use
the `Cache` class in other situations to cache other things. And you
could even - with a little bit of work - create a new `Cache` class
that caches via something like Redis, and pass *this* to `StringTransformer`
to cache using a different method.
features:
multiple_choice:
choices:
a: Each class is smaller and so easier to understand
b: The `Cache` class could be re-used to cache other things
c: You could easily use the `StringTransformer`, but cache using a different mechanism, like *Redis*
d: All of these are real advantages
correct: d
# AbstractShipLoader
abstractshiploader_createabstractplanetcoding:
question: |
Annoyed that the DeathStars are being destroyed, the Empire has decided to transform into
a video game company. Awesome, they'll never see it coming! Two different teammates have
already created two classes to model this: `SolidPlanet` and `GasPlanet`. They look and
work differently, but both have `getRadius()` and `getHexColor()` methods. You've built a
`PlanetRenderer` class with a `render()` method, but it's not quite working yet.
question_steps:
- Create an `AbstractPlanet` class and update any other code you need to make these planets render!
step: abstract-ship-loader-create-abstract-planet
files:
AbstractPlanet.php: ~
SolidPlanet.php: ~
GasPlanet.php: ~
PlanetRenderer.php: ~
index.php:
read_only: true
features:
editor: ~
browser: ~
grading:
check_class:
type: php
assertTrue: class_exists('\AbstractPlanet')
failure: Class `AbstractPlanet` does not exist! Did you create it?
assert_class_abstract:
type: php
assertTrue: reflection_class('\AbstractPlanet').isAbstract()
failure: Class `AbstractPlanet` should be declared as abstract.
check_method_1:
type: php
assertTrue: reflection_class('\AbstractPlanet').hasMethod('getRadius')
failure: Method `getRadius()` does not exist in the `AbstractPlanet` class.
assert_method_1_abstract:
type: php
assertTrue: reflection_class('\AbstractPlanet').getMethod('getRadius').isAbstract()
failure: Method `getRadius()` should be declared as abstract.
check_method_2:
type: php
assertTrue: reflection_class('\AbstractPlanet').hasMethod('getHexColor')
failure: Method `getHexColor()` does not exist in the `AbstractPlanet` class.
assert_method_2_abstract:
type: php
assertTrue: reflection_class('\AbstractPlanet').getMethod('getHexColor').isAbstract()
failure: Method `getHexColor()` should be declared as abstract.
check_subclass_of_1:
type: php
assertTrue: reflection_class('\SolidPlanet').isSubclassOf('\AbstractPlanet')
failure: Class `SolidPlanet` should inherit the `AbstractPlanet` one.
check_subclass_of_2:
type: php
assertTrue: reflection_class('\GasPlanet').isSubclassOf('\AbstractPlanet')
failure: Class `GasPlanet` should inherit the `AbstractPlanet` one.
# Interfaces
interfaces_implementplanetinterfacecoding:
question: |
After watching this last episode, you realize that `AbstractPlanet` should really
be an interface. I've given you a head start by creating the `PlanetInterface`.
question_steps:
- Update all of your code to use it and get these planets rendering again!
step: interfaces-implement-planetinterface
files:
SolidPlanet.php: ~
GasPlanet.php: ~
PlanetRenderer.php: ~
PlanetInterface.php:
read_only: true
index.php:
read_only: true
features:
editor: ~
browser: ~
grading:
assert_implementing_1:
type: php
assertTrue: reflection_class('\SolidPlanet').implementsInterface('\PlanetInterface')
failure: Class `SolidPlanet` should implement the `PlanetInterface` interface.
assert_implementing_2:
type: php
assertTrue: reflection_class('\GasPlanet').implementsInterface('\PlanetInterface')
failure: Class `GasPlanet` should implement the `PlanetInterface` interface.
interfaces_interfacevsabstractclassmc:
question: |
You over-hear the intern Bob telling another teammate about the differences between
abstract classes and interfaces. He's *mostly* right, but he got one detail wrong.
Which of the following is *not* true:
explanation: |
`C` is the only answer that's incorrect: both interfaces and abstract classes can
force you to implement methods in the classes that use them. So in many ways, they
are the same.
So why use one or the other? Well, a class can implement *many* interfaces, which
makes interfaces more attractive, especially for re-usable code. But, an abstract
class can contain *real* methods, which can help you reduce code duplication between
classes. They're similar, but not the same.
features:
multiple_choice:
choices:
a: Classes can implement many interfaces, but only extend one class.
b: Abstract classes can contain concrete methods, but interfaces can't.
c: Interfaces force the user to implement certain methods, abstract classes do not.
d: |
Even though Interfaces don't use the `abstract` keyword before methods,
those methods act just like abstract methods in an abstract class.
correct: c
interfaces_implementweaponinterfacecoding:
question: |
Finally something fun! Now you're working on creating different weapons for the
spaceships in our game.
question_steps:
- |
Looking at the `WeaponInterface` create a new `LaserWeapon` class that implements
this interface. You can return anything you want from the methods. Just don't let the
freedom go to your head.
- Use the class to print out the weapon's range, just to see that things are working.
step: interfaces-implement-weaponinterface
files:
index.php: ~
LaserWeapon.php: ~
WeaponInterface.php:
read_only: true
features:
editor: ~
browser: ~
grading:
check_class:
type: php
assertTrue: class_exists('\LaserWeapon')
failure: Class `LaserWeapon` could not be found. Did you create it?
check_implementing:
type: php
assertTrue: reflection_class('\LaserWeapon').implementsInterface('\WeaponInterface')
failure: Class `LaserWeapon` should implement the `WeaponInterface` interface.
look_for_method:
type: php
# TODO Wondering how can we get expressions from both http and php types togeather in one assertion?
# I think we have a preblem here for now:
#assertTrue: body() matches /get_variable('laserWeapon').getWeaponRange()/
assertTrue: inputContains('index.php', 'getWeaponRange')
failure: Seems you forgot to output the laser weapon range. Did you print the result of the `getWeaponRange()` method?
# Episode 4
# class-constants
classconstants_replacestringswithconstantscoding:
question: |
Our cool `GasPlanet` class is smart! We tell it what
"main element" the planet is made out of (e.g. ammonia),
and `getHexColor()` tells us the color of the planet.
But, it's too easy to make a typo - e.g. `amonia` - when
passing in the element.
question_steps:
- |
Add 4 new constants to `GasPlanet` - one for each of
the 4 materials. Give them descriptive names, like
`MATERIAL_AMMONIA`.
- |
Ok! Replace the strings in your code with these
constants.
step: class-constants-replace-strings-with-constants
files:
index.php: ~
GasPlanet.php: ~
PlanetInterface.php:
read_only: true
PlanetRenderer.php:
read_only: true
features:
editor: ~
browser: ~
grading:
check_constant_1:
type: php
assertTrue: reflection_class('\GasPlanet').hasConstant('MATERIAL_AMMONIA')
failure: Did you add a `MATERIAL_AMMONIA` constant to the `GasPlanet` class?
check_constant_2:
type: php
assertTrue: reflection_class('\GasPlanet').hasConstant('MATERIAL_HYDROGEN')
failure: Did you add a `MATERIAL_HYDROGEN` constant to the `GasPlanet` class?
check_constant_3:
type: php
assertTrue: reflection_class('\GasPlanet').hasConstant('MATERIAL_HELIUM')
failure: Did you add a `MATERIAL_HELIUM` constant to the `GasPlanet` class?
check_constant_4:
type: php
assertTrue: reflection_class('\GasPlanet').hasConstant('MATERIAL_METHANE')
failure: Did you add a `MATERIAL_METHANE` constant to the `GasPlanet` class?
check_constant_usage_1:
type: php
assertTrue: |
true
and inputContains('GasPlanet.php', '::MATERIAL_METHANE')
and inputContains('GasPlanet.php', '::MATERIAL_HYDROGEN')
and inputContains('GasPlanet.php', '::MATERIAL_HELIUM')
and inputContains('GasPlanet.php', '::MATERIAL_METHANE')
failure: Replace all hardcoded strings with the new `MATERIAL_*` constants in the `GasPlanet` class.
check_constant_usage_2:
type: php
assertTrue: |
true
and inputContains('index.php', 'GasPlanet::MATERIAL_AMMONIA')
and inputContains('index.php', 'GasPlanet::MATERIAL_METHANE')
failure: Replace the hardcoded strings with the new `MATERIAL_AMMONIA` and `MATERIAL_METHANE` constants in the `index.php`.
classconstants_uppercaseconstantsmc:
question: Why are constants always upper-case (e.g. `MATERIAL_AMMONIA`)?
explanation: |
The upper-casing of constants is just a standard, but standards
are good to follow! A constant is distinguished from a property
by their keyword and the use of a `$`: `const PIZZA` versus `private $pizza`.
features:
multiple_choice:
choices:
a: "It's just a standard: constants don't *need* to be upper-case"
b: The upper-case characters distinguish them from normal properties
c: Upper-case class constants are used so that they don't conflict with core PHP constants
d: The upper-case characters help keep memory usage lower
correct: a
classconstants_setconstantmc:
question: |
Suppose you want to change the value of a constant.
Check out the following code:
```php
class GasPlanet
{
const MATERIAL_AMMONIA = 'ammonia';
}
```
```php
<?php
// ...
GasPlanet::MATERIAL_AMMONIA = 'helium';
```
Is this legal?
explanation: |
Constants can *never* be changed - that's the whole point! And
they *must* be declared inside of the class with the `const` keyword.
If you need a value that *changes*, then you don't need a constant,
you need a *property*. And as you'll learn in a few minutes, properties
can be static (like constants) or non-static (stay-tuned!).
features:
multiple_choice:
choices:
a: |
Yes, but it's not recommended. Constants are values that
are not *meant* to be modified.
b: |
No, unless you removed the `const MATERIAL_AMMONIA = 'ammonia';`
from `GasPlanet`. Constants can only be set *once* either via
the `const` keyword, or by setting them from outside the class.
c: Nope - changing constants is totally *not* allowed, ever.
d: |
Yes, though it's recommended to change constants from inside
the class where they are declared (i.e. `GasPlanet`).
correct: c
classconstants_hexcolorconstantsmc:
question: |
The intern has gone *crazy* with constants, by even making the
hex strings into constants:
```php
class GasPlanet implements PlanetInterface
{
// ...
const COLOR_BROWN_ISH = '663300';
const COLOR_BLUE_BRIGHT = '0066FF';
const COLOR_GRAY_DARK = '464646';
// ...
public function getHexColor()
{
// a "fake" map of elements to colors
switch ($this->mainElement) {
case GasPlanet::MATERIAL_AMMONIA:
return GasPlanet::COLOR_BROWN_ISH;
case GasPlanet::MATERIAL_METHANE:
return GasPlanet::COLOR_BLUE_BRIGHT;
default:
return GasPlanet::COLOR_GRAY_DARK;
}
}
}
```
Is this a good step forward?
explanation: |
Not *all* strings or numbers should be turned into constants.
If you're thinking about making something a constant, ask yourself:
* Are these strings used multiple times?
* Are these strings used from *outside* of the class?
* If someone makes a minor typo, will the result be drastically different?
* Is it important for us to have a list of all of these possible values in one spot.
In this case, the answer is no to *all* of these questions. For
example, if we typo `ammonia` as `amonia`, the planet would have
a drastically different color. But if we typo `663300` as `663301`,
that won't make much difference, because the hex values are not
being used as *exact keys* to decide how the code behaves.
features:
multiple_choice:
choices:
a: |
Not really. These hex colors aren't used outside of this
class ever (or even multiple times *inside* of this class),
so there's no advantage to making these constants.
b: "Absolutely: it's important to make all static strings into constants."
c: "No: the hex colors shouldn't be constants, in case we need to change them later."
correct: a
# static-vs-non-static-and-self
staticvsnonstaticandself_staticpropertymc:
question: |
The intern is *loving* the idea of static properties, but I feel
like he's a little confused. But, he's got a good spirit! Let's
check out his code and offer him some advice:
```php
class SolidPlanet
{
private $radius;
private static $hexColor;
private $hasBeenVisitedByAliens;
private $numberOfHabitablePlanetsInUniverse;
}
```
Which of the following are true:
explanation: |
When determining static or non-static, ask yourself this question:
> Does *each* individual planet have its own value for this
> property? Or is there just one value for *all* planets?
Looking at each property, here's how it looks:
* `radius`: Each planet has a different radius: *not static*
* `hexColor`: Each planet also has its own color: *non static*
* `hasBeenVisitedByAliens`: Some planets may have been visited by aliens
and others not: *non static*
* `$numberOfHabitablePlanetsInUniverse`: This is a global number,
it applies to *all* planets: it's not a property of just one
planet: *static*
When in doubt, use non-static!
features:
multiple_choice:
choices:
a: The `$hexColor` property should *not* be static
b: The `$radius` property should be static
c: The `$numberOfHabitablePlanetsInUniverse` should be static
d: Both (A) and (C) are true!