|
| 1 | +You have been given a few pieces of code (look for `attributes.php`, `deserialize.php` & `the-attributes-of-success.php` in your working directory). |
| 2 | + |
| 3 | +Your entry point is `the-attributes-of-success.php`. This is the file you should edit and work on. The other files should not be modified. However, they are included by `the-attributes-of-success.php`. |
| 4 | + |
| 5 | +You can run and verify your program like so: |
| 6 | + |
| 7 | +```sh |
| 8 | +$ {appname} run the-attributes-of-success.php |
| 9 | +$ {appname} verify the-attributes-of-success.php |
| 10 | +``` |
| 11 | + |
| 12 | +Your task is split into two sections. The overall task is to write a class using properties and attributes which describe how to map data to an instance of the class. |
| 13 | + |
| 14 | +The data will be passed to you in a JSON encoded string via the first command line argument. |
| 15 | + |
| 16 | +The data will represent a product review. |
| 17 | + |
| 18 | +You pass the `JSON` data and the name of your class to a function named `deserialize` which is provided to you in the file `deserialize.php`. For reference, its signature is: |
| 19 | + |
| 20 | +```php |
| 21 | +function deserialize(string $data, string $className): object; |
| 22 | +``` |
| 23 | + |
| 24 | +It will return to you an instance of `$className` with the data from the `JSON` string `$data` mapped to its properties. |
| 25 | + |
| 26 | +You should dump out the object using `var_dump`. |
| 27 | + |
| 28 | +### Task 1 - Annotate a class with existing attributes |
| 29 | + |
| 30 | +Create a class named `Review` with five public properties which represent the data of the review. The properties should all be `string` types and should be named `comment`, `starRating`, `date`, `id` and `reviewer`. |
| 31 | + |
| 32 | +The class should use the Attribute `Deserialize` so that our `deserialize` function knows that this is a valid class to use. |
| 33 | + |
| 34 | +By default, our `deserialize` function will use the names of the class properties to locate the field value in the `JSON` data. |
| 35 | + |
| 36 | +For example, when passing the following class to our `deserialise` function it would look for the `sku` key in the `JSON` data and set it on the `sku` property. |
| 37 | + |
| 38 | +```php |
| 39 | +#[Deserialize] |
| 40 | +class Product { |
| 41 | + public string $sku; |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +#### Mapping properties |
| 46 | + |
| 47 | +However, the `starRating` key does not exist in the `JSON` data. It exists as `rating`. So here, we need to use the `Map` attribute. By using the `Map` attribute we can tell our `deserialize` function to fetch the star rating from a different key in the `JSON` data. |
| 48 | + |
| 49 | +Use it like so: |
| 50 | + |
| 51 | +```php |
| 52 | +#[Deserialize] |
| 53 | +class Product { |
| 54 | + #[Map('reference')] |
| 55 | + public string $sku; |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +Where `reference` is the key in the `JSON` data you want to use rather than `sku`. |
| 60 | + |
| 61 | +#### Skipping properties |
| 62 | + |
| 63 | +We don't care about the ID value, this relates to a 3rd party system and is not relevant in our code. |
| 64 | + |
| 65 | +Use the `Skip` attribute on the `id` property of our `Review` class to tell our `deserialize` function to skip this piece of data. |
| 66 | + |
| 67 | +### Task 2 - Create your own attribute |
| 68 | + |
| 69 | +By now you should be able to call the `deserialize` function with the `JSON` data and your class name. |
| 70 | + |
| 71 | +When executing your program with |
| 72 | + |
| 73 | +```sh |
| 74 | +$ {appname} run the-attributes-of-success.php |
| 75 | +``` |
| 76 | + |
| 77 | +You should see a dump of your `Review` instance. |
| 78 | + |
| 79 | +Here comes our problem: The reviewers name is not anonymous. We have to comply with strict privacy laws, we cannot display this data without the reviewer's permission. |
| 80 | + |
| 81 | +For now, we will have to obfuscate this data. We can accomplish this using a custom attribute. |
| 82 | + |
| 83 | +#### Create the obfuscate method |
| 84 | + |
| 85 | +Create a method on your `Review` class named `obfuscateReviewer`. It should take a string input, run it through the `md5` function and return it. |
| 86 | + |
| 87 | +#### Create an attribute |
| 88 | + |
| 89 | +Create an attribute named `Obfuscate`. It should have a public property named `key` and it's constructor should assign the first passed in string value to this property. |
| 90 | + |
| 91 | +The attribute must be designated as an attribute by using the `Attribute` attribute (confused much??) and it should target methods only. |
| 92 | + |
| 93 | +Targets designate where an attribute can be used, on classes, methods or properties and so on. For reference, the `Deserialize` attribute can only be used on classes and hence its target is `Attribute::TARGET_CLASS`. |
| 94 | + |
| 95 | +See below for an example of designating a class as an attribute and configuring its target as classes only. |
| 96 | + |
| 97 | +```php |
| 98 | +#[Attribute(Attribute::TARGET_CLASS)] //This is how we designate `MyAttribute` as an attribute with its target. |
| 99 | +class MyAttribute { |
| 100 | + public function __construct(string $someValue) { |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +#### Use the attribute |
| 106 | + |
| 107 | +The last step is to use the attribute on your `obfuscateReviewer` method. We need to tell our `deserialize` function that this method should be called when accessing the `reviewer` key from the `JSON` data. |
| 108 | + |
| 109 | +Use the `Obfuscate` attribute on the method and pass to it the name of the key in the `JSON` data we want the obfuscator to run over, which is the `reviewer` key. |
| 110 | + |
| 111 | +When the `deserialize` function sees a method with the `Obfuscate` attribute it will use the key found in the public property named `key` of the attribute. It will find the value referenced by that key in the `JSON` data and pass it to the obfuscator method. |
| 112 | + |
| 113 | +Finally, the returned data will be set on the `Review` object instance. |
| 114 | + |
| 115 | +### Dump your object |
| 116 | + |
| 117 | +The last task is to dump your object instance out using the PHP function `var_dump` - we use this output to verify the structure and data in your `Review` instance. |
| 118 | + |
| 119 | +### The advantages of Attributes |
| 120 | + |
| 121 | +* Add Metadata to classes, methods, properties and arguments and so on |
| 122 | +* They can replace PHP doc blocks, each with custom parsers and rules to a unified standard supported by PHP |
| 123 | +* The data can be introspected using PHP's Reflection API's |
| 124 | +* PHP Core and extensions can provide new behaviour and runtime configuration which is opt-in, such as conditionally declaring functions, deprecating features and so on |
| 125 | + |
| 126 | +---------------------------------------------------------------------- |
| 127 | +## HINTS |
| 128 | + |
| 129 | +Documentation on the Attributes feature can be found by pointing your browser here: |
| 130 | +[https://www.php.net/manual/en/language.attributes.overview.php]() |
| 131 | + |
| 132 | +Remember, do not edit `attributes.php` or `deserialize.php` - verification will fail if you do. Feel free to read the files to get a better understanding of the deserialization process. |
| 133 | + |
| 134 | +You must call the `deserialize` function and you must use the `var_dump` function to output your deserialized object. |
| 135 | + |
| 136 | +If you want to see the `JSON` data - use `var_dump` to dump it out. |
| 137 | + |
| 138 | + |
| 139 | +## Extra |
| 140 | + |
| 141 | +If you're not sure how to access command line arguments - you should maybe try a different workshop which covers that topic. Try `learnyouphp`. |
| 142 | + |
| 143 | +`json_decode` can fail if it is passed a malformed string. Wrap the decode in a `try\catch` statement and pass the `JSON_THROW_ON_ERROR` flag to `json_decode`'s fourth parameter. |
0 commit comments