diff --git a/content/20201112124159-deprecating_service_aliases.md b/content/20201112124159-deprecating_service_aliases.md
new file mode 100644
index 0000000..d1df896
--- /dev/null
+++ b/content/20201112124159-deprecating_service_aliases.md
@@ -0,0 +1,33 @@
+---
+id: 340acfc8-c621-4578-a588-c306ebaa4d75
+title: Deprecating Service Aliases
+---
+
+# Description
+
+It's possible to deprecate [service](20201112124304-symfony_services)
+aliases
+
+# Syntax
+
+``` yaml
+# config/services.yaml
+services:
+ # ...
+
+ app.mailer:
+ alias: App\Mail\PhpMailer
+ deprecated: ~
+```
+
+## Custom error message
+
+``` yaml
+# config/services.yaml
+services:
+ # ...
+
+ app.mailer:
+ alias: App\Mail\PhpMailer
+ deprecated: 'The "%alias_id%" service alias is deprecated.'
+```
diff --git a/content/20201112124304-symfony_services.md b/content/20201112124304-symfony_services.md
new file mode 100644
index 0000000..826a8ce
--- /dev/null
+++ b/content/20201112124304-symfony_services.md
@@ -0,0 +1,21 @@
+---
+id: be8665e2-83e3-408f-b532-5c7e1fdc4729
+title: Symfony Services
+---
+
+# Decoration
+
+- [Service Decoration](20201116132826-service_decoration)
+- [Stack Decorators](20201116133113-stack_decorators)
+
+# Deprecation
+
+- [Deprecate Public Services Into Private
+ Services](20201116134758-deprecate_public_services_into_private_services)
+- [Deprecating Service
+ Aliases](20201112124159-deprecating_service_aliases)
+
+# Setters
+
+- [Configuring Services With Immutable
+ Setters](20201112131310-configuring_services_with_immutable_setters)
diff --git a/content/20201112124715-symfony_configuration.md b/content/20201112124715-symfony_configuration.md
new file mode 100644
index 0000000..a6ec94b
--- /dev/null
+++ b/content/20201112124715-symfony_configuration.md
@@ -0,0 +1,44 @@
+---
+id: 3bdd7ddf-9039-438a-abc7-435d6ccc1a61
+title: Symfony Configuration
+---
+
+- [Automatic Search Engine
+ Protection](20201112124832-automatic_search_engine_protection)
+- [Configurable Session ID](20201112125413-configurable_session_id)
+- [Configuring Services With Immutable
+ Setters](20201112131310-configuring_services_with_immutable_setters)
+
+# Controller
+
+- [Front Controller
+ Configuration](20201117114443-front_controller_configuration)
+
+# Password hashers
+
+- [Sodium password encoder](20201112133736-sodium_password_encoder)
+- [Symfony Native Encoder](20201112135851-symfony_native_encoder)
+ (best practice since [Symfony 4.3](20201112120118-symfony_4_3))
+
+# Encryption
+
+- [Ecrypted Secrets
+ Management](20201113174444-ecrypted_secrets_management)
+
+# Events
+
+[Event Listeners](20201113175527-event_listeners)
+
+# Firewalls
+
+- [Lazy Firewalls](20201113183038-lazy_firewalls)
+
+# Translator
+
+- [Pseudo-localization
+ Translator](20201117100112-pseudo_localization_translator)
+
+# RateLimiter Component
+
+- [Symfony RateLimiter
+ Component](20201117113404-symfony_ratelimiter_component)
diff --git a/content/20201112124832-automatic_search_engine_protection.md b/content/20201112124832-automatic_search_engine_protection.md
new file mode 100644
index 0000000..74319c3
--- /dev/null
+++ b/content/20201112124832-automatic_search_engine_protection.md
@@ -0,0 +1,19 @@
+---
+id: c6744b2e-f845-4ac7-83d5-ef206743e4a6
+title: Automatic Search Engine Protection
+---
+
+# Description
+
+Symfony dissalows search engine indexing for development applications. A
+`X-Robots-Tag: noindex` HTTP header is added to all responses. This can
+be manually overridden in the config.
+
+# Syntax
+
+``` yaml
+# config/packages/framework.yaml
+framework:
+ # ...
+ disallow_search_engine_index: false
+```
diff --git a/content/20201112125413-configurable_session_id.md b/content/20201112125413-configurable_session_id.md
new file mode 100644
index 0000000..9eb102e
--- /dev/null
+++ b/content/20201112125413-configurable_session_id.md
@@ -0,0 +1,19 @@
+---
+id: 9a6ba6b5-92b8-4422-a31a-3d78ba563ef2
+title: Configurable Session ID
+---
+
+# Syntax
+
+``` yaml
+# config/packages/framework.yaml
+framework:
+ session:
+ # configures the length of the string used for the session ID
+ # integer; default = 32; valid values = from 22 to 256 (both inclusive)
+ sid_length: 64
+
+ # configures the number of bits in encoded session ID character
+ # integer; default = 4; valid values: 4, 5, or 6.
+ sid_bits_per_character: 4
+```
diff --git a/content/20201112125637-symfony_json_constraint.md b/content/20201112125637-symfony_json_constraint.md
new file mode 100644
index 0000000..277715e
--- /dev/null
+++ b/content/20201112125637-symfony_json_constraint.md
@@ -0,0 +1,21 @@
+---
+id: 4e20e5e5-06b6-4280-9cd0-7b08585a762b
+title: Symfony JSON Constraint
+---
+
+# Syntax
+
+``` php
+// src/Entity/Book.php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class Book
+{
+ /**
+ * @Assert\Json(message = "This is not valid JSON")
+ */
+ protected $chapters;
+}
+```
diff --git a/content/20201112125813-symfony_json.md b/content/20201112125813-symfony_json.md
new file mode 100644
index 0000000..49c0428
--- /dev/null
+++ b/content/20201112125813-symfony_json.md
@@ -0,0 +1,8 @@
+---
+id: 675c8bfd-a684-441b-84db-76ba6ccc4e77
+title: Symfony JSON
+---
+
+# Constraints
+
+- [JSON Constraint](20201112125637-symfony_json_constraint)
diff --git a/content/20201112130234-symfony_console_iterable_progress_bars.md b/content/20201112130234-symfony_console_iterable_progress_bars.md
new file mode 100644
index 0000000..60f39f1
--- /dev/null
+++ b/content/20201112130234-symfony_console_iterable_progress_bars.md
@@ -0,0 +1,23 @@
+---
+id: b4fc3714-24f9-4eb6-9d46-3c42bdcd5be2
+title: Symfony Console Iterable Progress Bars
+---
+
+# Changes
+
+- [iterate() method](20201112130435-iterate_method)
+
+# Syntax
+
+``` php
+use Symfony\Component\Console\Helper\ProgressBar;
+
+$progressBar = new ProgressBar($output);
+$progressBar->start();
+
+// ... do some work
+$progressBar->advance();
+
+// needed to ensure that the bar reaches 100%
+$progressBar->finish();
+```
diff --git a/content/20201112130435-iterate_method.md b/content/20201112130435-iterate_method.md
new file mode 100644
index 0000000..e5b731a
--- /dev/null
+++ b/content/20201112130435-iterate_method.md
@@ -0,0 +1,32 @@
+---
+id: f5983e81-f975-4913-b6c5-5211493841b7
+title: iterate() method
+---
+
+# Syntax
+
+``` php
+$iterable = function () {
+ yield 1;
+ yield 2;
+ // ...
+};
+```
+
+``` php
+use Symfony\Component\Console\Helper\ProgressBar;
+
+$progressBar = new ProgressBar($output);
+
+foreach ($progressBar->iterate($iterable) as $value) {
+ // ... do some work
+}
+```
+
+## Non countable variable
+
+``` php
+foreach ($progressBar->iterate($iterable, 100) as $value) {
+ // ... do some work
+}
+```
diff --git a/content/20201112130710-notblank_constraint.md b/content/20201112130710-notblank_constraint.md
new file mode 100644
index 0000000..51453f5
--- /dev/null
+++ b/content/20201112130710-notblank_constraint.md
@@ -0,0 +1,24 @@
+---
+id: ba104198-9038-4219-994e-503ee655d9f3
+title: NotBlank Constraint
+---
+
+# Syntax
+
+``` php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class SomeEntity
+{
+ /**
+ * @Assert\NotBlank()
+ */
+ protected $someProperty;
+}
+```
+
+## allowNull
+
+[NotBlank allowNull](20201112130816-notblank_allownull)
diff --git a/content/20201112130816-notblank_allownull.md b/content/20201112130816-notblank_allownull.md
new file mode 100644
index 0000000..b081312
--- /dev/null
+++ b/content/20201112130816-notblank_allownull.md
@@ -0,0 +1,26 @@
+---
+id: 47dd501e-a567-481a-8d85-5eb1cd0bad15
+title: NotBlank allowNull
+---
+
+# Description
+
+The default option is `false`. If set to true then `null` values will be
+considered valid.
+
+# Syntax
+
+``` php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class SomeEntity
+{
+ /**
+ * @Assert\NotBlank(allowNull = true)
+ */
+ protected $someProperty;
+}
+
+```
diff --git a/content/20201112131310-configuring_services_with_immutable_setters.md b/content/20201112131310-configuring_services_with_immutable_setters.md
new file mode 100644
index 0000000..3a7e2bb
--- /dev/null
+++ b/content/20201112131310-configuring_services_with_immutable_setters.md
@@ -0,0 +1,71 @@
+---
+id: 51f4dd9e-6f11-485d-8544-f302e71fc036
+title: Configuring Services With Immutable Setters
+---
+
+# Description
+
+A common need in some Symfony applications is to use immutable services
+while still using traits for composing their optional features. Although
+the Symfony service container supports setter injection, they have some
+drawbacks (e.g. setters can be called more than just at the time of
+construction so you cannot be sure the dependency is not replaced during
+the lifetime of the object).
+
+A pattern to solve this problem are "wither methods". These methods
+typically use the with word as the prefix of their names (e.g.
+withPropertyName()) and they return a copy of an instance of an
+immutable class with only that one property changed:
+
+``` php
+class MyService
+{
+ use LoggerAwareTrait;
+
+ // ...
+}
+
+trait LoggerAwareTrait
+{
+ private $logger;
+
+ public function withLogger(LoggerInterface $logger)
+ {
+ $new = clone $this;
+ $new->logger = $logger;
+
+ return $new;
+ }
+}
+
+$service = new MyService();
+$service = $service->withLogger($logger);
+```
+
+# Syntax
+
+``` yaml
+# config/services.yaml
+services:
+ MyService:
+ # ...
+ calls:
+ # the TRUE argument turns this into a wither method
+ - ['withLogger', ['@logger'], true]
+```
+
+``` php
+class MyService
+{
+ // ...
+
+ /**
+ * @required
+ * @return static
+ */
+ public function withLogger(LoggerInterface $logger)
+ {
+ // ...
+ }
+}
+```
diff --git a/content/20201112131845-doctrine.md b/content/20201112131845-doctrine.md
new file mode 100644
index 0000000..4ecbce9
--- /dev/null
+++ b/content/20201112131845-doctrine.md
@@ -0,0 +1,20 @@
+---
+id: 78611b42-959f-4a7a-b1d9-97f20bf73ea0
+title: Doctrine
+---
+
+# Symfony
+
+## Validation
+
+- [Automatic Validation Based on Doctrine
+ Mapping](20201112132007-automatic_validation_based_on_doctrine_mapping)
+
+## Listeners
+
+- [Doctrine Entity
+ Listeners](20201113180551-doctrine_entity_listeners)
+
+## Types
+
+- [UUID / ULID Types](20201117101452-uuid_ulid_types)
diff --git a/content/20201112132007-automatic_validation_based_on_doctrine_mapping.md b/content/20201112132007-automatic_validation_based_on_doctrine_mapping.md
new file mode 100644
index 0000000..8f3b28c
--- /dev/null
+++ b/content/20201112132007-automatic_validation_based_on_doctrine_mapping.md
@@ -0,0 +1,39 @@
+---
+id: c7132269-2084-4d87-9888-0f860db29aaf
+title: Automatic Validation Based on Doctrine Mapping
+---
+
+# Description
+
+From [Symfony 4.3](20201112120118-symfony_4_3), Symfony introduces
+automatic validation based on Doctrine mapping.
+
+# Examples
+
+``` php
+use Doctrine\ORM\Mapping as ORM;
+
+/** @ORM\Entity */
+class SomeEntity
+{
+ // ...
+
+ /** @ORM\Column(length=4) */
+ public $pinCode;
+}
+```
+
+``` php
+$entity = new SomeEntity();
+$entity->pinCode = '1234567890';
+$violationList = $validator->validate($entity);
+```
+
+``` php
+$violationList = $validator->validate($entity);
+
+var_dump((string) $violationList);
+// Object(App\Entity\SomeEntity).columnLength:\n
+// This value is too long. It should have 4 characters or less.
+// (code d94b19cc-114f-4f44-9cc4-4138e80a87b9)\n
+```
diff --git a/content/20201112132331-unique_constraint.md b/content/20201112132331-unique_constraint.md
new file mode 100644
index 0000000..95ab317
--- /dev/null
+++ b/content/20201112132331-unique_constraint.md
@@ -0,0 +1,21 @@
+---
+id: ebceb7b8-5242-482b-b258-2cf9c25d1788
+title: Unique Constraint
+---
+
+# Syntax
+
+``` php
+// src/Entity/Person.php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class Person
+{
+ /**
+ * @Assert\Unique(message="The {{ value }} email is repeated.")
+ */
+ protected $contactEmails;
+}
+```
diff --git a/content/20201112132747-utf8_routing_option.md b/content/20201112132747-utf8_routing_option.md
new file mode 100644
index 0000000..f2a548a
--- /dev/null
+++ b/content/20201112132747-utf8_routing_option.md
@@ -0,0 +1,22 @@
+---
+id: 45b1f81a-d5f4-4f28-99d6-6a1592c75f7d
+title: UTF8 Routing Option
+---
+
+# Syntax
+
+``` php
+// PHP Annotations
+
+/**
+ * @Route("/category/{name}", name="category", utf8=true)
+ */
+public function category()
+```
+
+``` yaml
+category:
+ path: /category/{name}
+ controller: App\Controller\DefaultController::category
+ utf8: true
+```
diff --git a/content/20201112132858-symfony_utf_8_support.md b/content/20201112132858-symfony_utf_8_support.md
new file mode 100644
index 0000000..216544d
--- /dev/null
+++ b/content/20201112132858-symfony_utf_8_support.md
@@ -0,0 +1,6 @@
+---
+id: 72aaa2a1-89f1-4f86-b666-299d77e5a75f
+title: Symfony UTF-8 Support
+---
+
+- [UTF8 Routing Option](20201112132747-utf8_routing_option)
diff --git a/content/20201112133603-symfony_passwords.md b/content/20201112133603-symfony_passwords.md
new file mode 100644
index 0000000..7a20b5a
--- /dev/null
+++ b/content/20201112133603-symfony_passwords.md
@@ -0,0 +1,13 @@
+---
+id: d7cf5110-7cf3-4228-bf6a-552e20a6ffd6
+title: Symfony Passwords
+---
+
+# Hashers
+
+- [Argon2i Password
+ Hasher](20201110152730-symfony_argon2i_password_hasher)
+ Deprecated in [Symfony 4.3](20201112120118-symfony_4_3)
+- [Sodium password encoder](20201112133736-sodium_password_encoder)
+- [Native Encoder](20201112135851-symfony_native_encoder) (best
+ practice)
diff --git a/content/20201112133736-sodium_password_encoder.md b/content/20201112133736-sodium_password_encoder.md
new file mode 100644
index 0000000..f41097a
--- /dev/null
+++ b/content/20201112133736-sodium_password_encoder.md
@@ -0,0 +1,23 @@
+---
+id: ebc9cf71-62ed-44fa-98c6-b2d01fbd69fe
+title: Sodium password encoder
+---
+
+# Description
+
+In Symfony 4.3, the [Symfony Argon2i Password
+Hasher](20201110152730-symfony_argon2i_password_hasher) is deprecated.
+`SodiumPasswordEncoder` is used instead. Best practice since [Symfony
+4.3](20201112120118-symfony_4_3) is to use the [native
+encoder](20201112135851-symfony_native_encoder).
+
+# Syntax
+
+``` yaml
+# config/packages/security.yml
+security:
+ # ...
+ encoders:
+ App\Entity\User:
+ algorithm: sodium
+```
diff --git a/content/20201112134545-timezone_constraint.md b/content/20201112134545-timezone_constraint.md
new file mode 100644
index 0000000..21ff435
--- /dev/null
+++ b/content/20201112134545-timezone_constraint.md
@@ -0,0 +1,33 @@
+---
+id: ba952b1d-0609-4bc6-94b9-0192df3bf4f2
+title: Timezone Constraint
+---
+
+# Syntax
+
+``` php
+// src/Entity/UserSettings.php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class UserSettings
+{
+ /**
+ * @Assert\Timezone
+ */
+ protected $timezone;
+}
+```
+
+``` php
+// Consider valid only the timezones from countries in America continent
+
+/** @Assert\Timezone(zone=\DateTimeZone::AMERICA) */
+protected $timezone;
+
+// Consider valid only the Chinese timezones
+
+/** @Assert\Timezone(zone=\DateTimeZone::PER_COUNTRY, countryCode="CN") */
+protected $timezone;
+```
diff --git a/content/20201112134545-timezozne_constraint.md b/content/20201112134545-timezozne_constraint.md
new file mode 100644
index 0000000..74f2017
--- /dev/null
+++ b/content/20201112134545-timezozne_constraint.md
@@ -0,0 +1,39 @@
+---
+id: f028709b-5851-4a8e-8c3c-097ebbe27c39
+title: Timezozne Constraint
+---
+
+# Syntax
+
+``` php
+// src/Entity/UserSettings.php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class UserSettings
+{
+ /**
+ * @Assert\Timezone
+ */
+ protected $timezone;
+}
+```
+
+``` php
+// Consider valid only the timezones from countries in America continent
+
+/** @Assert\Timezone(zone=\DateTimeZone::AMERICA) */
+protected $timezone;
+
+// Consider valid only the Chinese timezones
+
+/** @Assert\Timezone(zone=\DateTimeZone::PER_COUNTRY, countryCode="CN") */
+protected $timezone;
+```
+
+# Related
+
+- [Symfony Constraints](20201112121938-symfony_constraints)
+- [Symfony Annotations](20201109142218-symfony_annotations)
+- [Symfony 4.3](20201112120118-symfony_4_3)
diff --git a/content/20201112134752-compromised_password_constraint.md b/content/20201112134752-compromised_password_constraint.md
new file mode 100644
index 0000000..6b52e18
--- /dev/null
+++ b/content/20201112134752-compromised_password_constraint.md
@@ -0,0 +1,30 @@
+---
+id: 1b282a21-2736-404a-a788-5a362fcba479
+title: Compromised Password Constraint
+---
+
+# Description
+
+Internally, the constraint makes an HTTP request to the API provided by
+the haveibeenpwned.com website. In the request, the validator doesn't
+send the raw password but only the few first characters of the result of
+encoding it using SHA-1.
+
+# Syntax
+
+``` php
+// src/Entity/User.php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class User
+{
+ // ...
+
+ /**
+ * @Assert\NotCompromisedPassword
+ */
+ protected $rawPassword;
+}
+```
diff --git a/content/20201112135249-mailer_component.md b/content/20201112135249-mailer_component.md
new file mode 100644
index 0000000..783ee88
--- /dev/null
+++ b/content/20201112135249-mailer_component.md
@@ -0,0 +1,73 @@
+---
+id: c6a317d3-cdda-4c9b-bf03-5e36bce8bc57
+title: Mailer Component
+---
+
+# Description
+
+Added in [Symfony 4.3](20201112120118-symfony_4_3). Out of the box the
+following services are supported:
+
+- Amazon SES
+- MailChimp
+- Mailgun
+- Gmail
+- Postmark
+- SendGrid
+
+Services need to be installed seperately:
+
+``` shell
+composer require symfony/amazon-mailer
+```
+
+And environment variables need to be configured:
+
+``` shell
+AWS_ACCESS_KEY=...
+AWS_SECRET_KEY=...
+MAILER_DSN=smtp://$AWS_ACCESS_KEY:$AWS_SECRET_KEY@ses
+```
+
+# Syntax
+
+``` php
+use Symfony\Component\Mailer\MailerInterface;
+use Symfony\Component\Mime\Email;
+
+class SomeService
+{
+ private $mailer;
+
+ public function __construct(MailerInterface $mailer)
+ {
+ $this->mailer = $mailer;
+ }
+
+ public function sendNotification()
+ {
+ $email = (new Email())
+ ->from('hello@example.com')
+ ->to('you@example.com')
+ ->subject('Time for Symfony Mailer!')
+ ->text('Sending emails is fun again!')
+ ->html('See Twig integration for better HTML integration!
');
+
+ $this->mailer->send($email);
+ }
+}
+```
+
+# Signing Messages
+
+It's also possible to [sign and
+encrypt](20201113173159-signing_and_encrypting_messages) messages.
+
+# DKIM email authentication
+
+- DKIM[^1] authentication is
+ [supported](20201117104659-dkim_email_authentication) as well.
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201112135851-symfony_native_encoder.md b/content/20201112135851-symfony_native_encoder.md
new file mode 100644
index 0000000..70adf3e
--- /dev/null
+++ b/content/20201112135851-symfony_native_encoder.md
@@ -0,0 +1,39 @@
+---
+id: cd2d3da1-7251-49f1-939b-460c92ba317b
+title: Symfony Native Encoder
+---
+
+# Description
+
+This is best practice since [Symfony 4.3](20201112120118-symfony_4_3).
+
+This value auto-selects the best possible hashing algorithm, so it
+doesn't refer to an specific algorithm and it will change in the future.
+The current implementation uses `'sodium'` if possible and otherwise, it
+falls back to `'native'`.
+
+The `'native'` config option is associated with the
+`NativePasswordEncoder` class, which is the other main change about
+password hashers in Symfony 4.3. This new encoder relies both on Symfony
+and PHP to select the best possible algorithm.
+
+The current `NativePasswordEncoder` implementation tries to use any of
+the Argon2 variants (Argon2i or Argon2id) before falling back to Bcrypt.
+However, if the `PASSWORD_DEFAULT` PHP constant changes in the future,
+that new algorithm will be selected (if PHP defines it as stronger than
+Argon2).
+
+# Syntax
+
+``` yaml
+# config/packages/security.yml
+security:
+ # ...
+ encoders:
+ App\Entity\User:
+ algorithm: auto
+```
+
+# Changes
+
+- [Password Migrations](20201113181759-password_migrations)
diff --git a/content/20201112140448-httpclient_component.md b/content/20201112140448-httpclient_component.md
new file mode 100644
index 0000000..331a789
--- /dev/null
+++ b/content/20201112140448-httpclient_component.md
@@ -0,0 +1,40 @@
+---
+id: c8b16c01-3921-4121-bb03-82f0b497eb31
+title: HttpClient Component
+---
+
+# Syntax
+
+``` php
+use Symfony\Component\HttpClient\HttpClient;
+
+$httpClient = HttpClient::create();
+$response = $httpClient->request('GET', 'https://api.github.com/repos/symfony/symfony-docs');
+```
+
+``` php
+$statusCode = $response->getStatusCode();
+// $statusCode = 200
+$content = $response->getContent();
+// returns the raw content returned by the server (JSON in this case)
+// $content = '{"id":521583, "name":"symfony-docs", ...}'
+$content = $response->toArray();
+// transforms the response JSON content into a PHP array
+// $content = ['id' => 521583, 'name' => 'symfony-docs', ...]
+```
+
+``` php
+// the response of this request will be a 403 HTTP error
+$response = $httpClient->request('GET', 'https://httpbin.org/status/403');
+
+// this code results in a Symfony\Component\HttpClient\Exception\ClientException
+// because it doesn't check the status code of the response
+$content = $response->getContent();
+
+// do this instead
+if (200 !== $response->getStatusCode()) {
+ // handle the HTTP request error (e.g. retry the request)
+} else {
+ $content = $response->getContent();
+}
+```
diff --git a/content/20201112140650-mime_component.md b/content/20201112140650-mime_component.md
new file mode 100644
index 0000000..3062fde
--- /dev/null
+++ b/content/20201112140650-mime_component.md
@@ -0,0 +1,62 @@
+---
+id: 5649f9c4-6f96-46c2-b3a2-45c6a28904c1
+title: Mime Component
+---
+
+# Syntax
+
+``` php
+use Symfony\Component\Mime\Email;
+
+$email = (new Email())
+ ->from('fabien@symfony.com')
+ ->to('foo@example.com')
+ ->subject('Important Notification')
+ ->text('Lorem ipsum...')
+ ->html('Lorem ipsum
...
')
+;
+```
+
+``` php
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Message;
+use Symfony\Component\Mime\Part\Multipart\AlternativePart;
+use Symfony\Component\Mime\Part\TextPart;
+
+$headers = (new Headers())
+ ->addMailboxListHeader('From', ['fabien@symfony.com'])
+ ->addMailboxListHeader('To', ['foo@example.com'])
+ ->addTextHeader('Subject', 'Important Notification')
+;
+
+$textContent = new TextPart('Lorem ipsum...');
+$htmlContent = new TextPart('Lorem ipsum
...
', 'html');
+$body = new AlternativePart($textContent, $htmlContent);
+
+$email = new Message($headers, $body);
+```
+
+## Twig integeration
+
+``` php
+use Symfony\Bridge\Twig\Mime\TemplatedEmail;
+
+$email = (new TemplatedEmail())
+ ->from('fabien@symfony.com')
+ ->to('foo@example.com')
+ // ...
+
+ // this method defines the path of the Twig template to render
+ ->htmlTemplate('messages/user/signup.html.twig')
+
+ // this method defines the parameters (name => value) passed to templates
+ ->context([
+ 'expiration_date' => new \DateTime('+7 days'),
+ 'username' => 'foo',
+ ])
+;
+```
+
+# Changelog
+
+- [Notification Emails](20201113174016-notification_emails)
diff --git a/content/20201112140917-number_constraints.md b/content/20201112140917-number_constraints.md
new file mode 100644
index 0000000..9005ccf
--- /dev/null
+++ b/content/20201112140917-number_constraints.md
@@ -0,0 +1,42 @@
+---
+id: adc652c4-6f67-46e9-a975-8e72b852234c
+title: Number Constraints
+---
+
+# Syntax
+
+``` php
+use Symfony\Component\Validator\Constraints as Assert;
+
+class Person
+{
+ /** @Assert\PositiveOrZero */
+ protected $siblings;
+
+ // ...
+}
+
+class Employee
+{
+ /** @Assert\Positive */
+ protected $income;
+
+ // ...
+}
+
+class UnderGroundGarage
+{
+ /** @Assert\NegativeOrZero */
+ protected $level;
+
+ // ...
+}
+
+class TransferItem
+{
+ /** @Assert\Negative */
+ protected $withdraw;
+
+ // ...
+}
+```
diff --git a/content/20201113090050-javascript_object_operators.md b/content/20201113090050-javascript_object_operators.md
new file mode 100644
index 0000000..03e2518
--- /dev/null
+++ b/content/20201113090050-javascript_object_operators.md
@@ -0,0 +1,10 @@
+---
+id: 8fa4c9b2-3c98-4706-ab59-6ac3b3238d81
+title: JavaScript Object Operators
+---
+
+# Operators
+
+- [In Operator](20201113090337-javascript_in_operator)
+- [Instanceof Operator](20201113094246-javascript_instanceof_operator)
+- [Delete Operator](20201113094652-javascript_delete_operator)
diff --git a/content/20201113090337-javascript_in_operator.md b/content/20201113090337-javascript_in_operator.md
new file mode 100644
index 0000000..78af9e7
--- /dev/null
+++ b/content/20201113090337-javascript_in_operator.md
@@ -0,0 +1,36 @@
+---
+id: 4cd0d42b-3414-4402-95f1-7498fbc52c20
+title: JavaScript In Operator
+---
+
+# Description
+
+`in`[^1] tells us if indices inside an [array](20200826201029-arrays) or
+[object](20200826201605-objects) have no associated element.
+
+# Syntax
+
+``` javascript
+const arr = ['a',,'b']
+console.log(0 in arr) // true
+console.log(1 in arr) // false
+console.log(2 in arr) // true
+console.log(arr[1]) // undefined
+```
+
+``` javascript
+const car = { make: "Honda", model: "Accord", year: 1998 };
+
+console.log("make" in car); // true
+
+delete car.make;
+if ("make" in car === false) {
+ car.make = "Suzuki";
+}
+
+console.log(car.make); //Suzuki
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113091110-javascript_object_properties.md b/content/20201113091110-javascript_object_properties.md
new file mode 100644
index 0000000..d3e5832
--- /dev/null
+++ b/content/20201113091110-javascript_object_properties.md
@@ -0,0 +1,48 @@
+---
+id: 3d5c7ec9-30af-4931-b6ab-2caa687f5951
+title: JavaScript Object Properties
+---
+
+# Description
+
+Values of the type [object](20200826201605-objects) are arbitrary
+collections of properties
+
+# Syntax
+
+``` javascript
+let tralala = {
+ distro: "Arch",
+ useWindows: false,
+ aListOfRandomThings: ["spoon", "fork", "modem", "keychain"],
+};
+
+console.log(tralala.distro);
+console.log(tralala.useWindows);
+console.log(tralala.aListOfRandomThings);
+```
+
+# Invalid binding names
+
+Properties with invalid binding names or numbers must be quoted:
+
+``` javascript
+let weirdObject = {
+ tralala: "Chipmunk",
+ "this is a long binding name with spaces": "Fill in some nonsensse here"
+}
+
+console.log(weirdObject)
+```
+
+# Non existant property
+
+Reading a non existant property returns `undefined`
+
+``` javascript
+let Object = {
+ thisExists: true,
+};
+
+console.log(Object.undefinedProperty); // undefined
+```
diff --git a/content/20201113091424-javascript_prototypes.md b/content/20201113091424-javascript_prototypes.md
new file mode 100644
index 0000000..8f392ba
--- /dev/null
+++ b/content/20201113091424-javascript_prototypes.md
@@ -0,0 +1,37 @@
+---
+id: fd03be0e-a4c0-421f-9dd7-1bddc9dece65
+title: JavaScript Prototypes
+---
+
+# Description
+
+A prototype can be seen as an [object](20200826201605-objects) another
+object extends.
+
+# Syntax
+
+``` javascript
+let protoRabbit = {
+ speak(line) {
+ console.log(`The ${this.type} rabbit says '${line}'`); // : The killer rabbit says 'SKREEEE!'
+ },
+};
+let killerRabbit = Object.create(protoRabbit);
+killerRabbit.type = "killer";
+killerRabbit.speak("SKREEEE!");
+```
+
+# Object.prototype
+
+Most objects in JavaScript eventually extend `Object.prototype` through
+parent prototype objects or directly, which provides a bunch of default
+methods[^1].
+
+``` javascript
+console.log(Object.getPrototypeOf({}) == Object.prototype); // true
+console.log(Object.getPrototypeOf(Object.prototype)); // null
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113092454-javascript_symbols.md b/content/20201113092454-javascript_symbols.md
new file mode 100644
index 0000000..10c279e
--- /dev/null
+++ b/content/20201113092454-javascript_symbols.md
@@ -0,0 +1,91 @@
+---
+id: 1e9f826e-69c6-4a52-8cf8-135abc082fb3
+title: JavaScript Symbols
+---
+
+- [JavaScript Symbol Prototype
+ Methods](20201116101443-javascript_symbol_prototype_methods)
+
+# Description
+
+These are tokens that serve as unique IDs. You create symbols via the
+factory function `Symbol()` (which is loosely similar to
+[string](20200922164551-strings) returning strings if called as a
+function).
+
+# Syntax
+
+``` javascript
+const symbol1 = Symbol();
+```
+
+## Add a description
+
+``` javascript
+const tralala = Symbol('tralala')
+console.log(tralala) // Symbol(tralala)
+```
+
+## Convert to string
+
+``` javascript
+const tralala = Symbol('tralala')
+console.log(String(tralala)) // `Symbol(tralala)`
+```
+
+## Every Symbol is unique
+
+``` javascript
+console.log(Symbol() === Symbol()) // false
+```
+
+## Property keys
+
+``` javascript
+const KEY = Symbol();
+const obj = {};
+
+obj[KEY] = 123;
+console.log(obj[KEY]); // 123
+```
+
+``` javascript
+const FOO = Symbol();
+const obj = {
+ [FOO]() {
+ return 'bar';
+ }
+};
+console.log(obj[FOO]()); // bar
+```
+
+# Use as reserved inherited method names
+
+If for some bizarre reason you want to use reserved inherited method
+names yourself (like toString) you can with Symbols.
+
+``` javascript
+const toStringSymbol = Symbol("toString");
+Array.prototype[toStringSymbol] = function() {
+ return `${this.length} cm of blue yarn`;
+};
+
+console.log([1, 2].toString()); //1, 2
+console.log([1, 2][toStringSymbol]()); // 2 cm of blue yarn
+```
+
+# Expressions
+
+Symbols can also be used in [object](20200826201605-objects) expressions
+and [classes](20201008090316-class_notation).
+
+``` javascript
+const toStringSymbol = Symbol("toString");
+
+let stringObject = {
+ [toStringSymbol]() {
+ return "a jute rope";
+ },
+};
+console.log(stringObject[toStringSymbol]()); // a jute rope
+```
diff --git a/content/20201113092902-javascript_primitives.md b/content/20201113092902-javascript_primitives.md
new file mode 100644
index 0000000..ba46389
--- /dev/null
+++ b/content/20201113092902-javascript_primitives.md
@@ -0,0 +1,6 @@
+---
+id: 78fe3d34-0c03-471c-9c4f-d9d919974b7d
+title: JavaScript Primitives
+---
+
+- [Symbols](20201113092454-javascript_symbols)
diff --git a/content/20201113093204-javascript_object_prototype_methods.md b/content/20201113093204-javascript_object_prototype_methods.md
new file mode 100644
index 0000000..e6087f5
--- /dev/null
+++ b/content/20201113093204-javascript_object_prototype_methods.md
@@ -0,0 +1,53 @@
+---
+id: 2c9b94b2-a652-42d7-97ee-731686d29e58
+title: JavaScript Object Prototype Methods
+---
+
+# Description
+
+JavaScript [objects](20200826201605-objects) have methods.
+
+# Syntax
+
+## Basic syntax
+
+Objects can also have methods
+
+``` javascript
+let robot = {};
+robot.speak = function(name) {
+ console.log(`${name} is alive!`)
+}
+
+robot.speak("Johnny 5")
+```
+
+## Properties
+
+Of course methods can use object properties and functions can be passed
+as properties
+
+``` javascript
+let robot = {name: "Johnny 5", speak};
+function speak(state) {
+ console.log(`${this.name} is ${state}!`)
+}
+
+robot.speak("alive")
+```
+
+## This
+
+`this` parameter can also be passed explicitly using a functions `call`
+method, if that's socially acceptable in your culture:
+
+``` javascript
+let robot = {name: "Johnny 5"};
+
+function speak(state) {
+ console.log(`${this.name} is ${state}!`)
+}
+
+speak.call(robot, "alive")
+
+```
diff --git a/content/20201113093613-javascript_object_keywords.md b/content/20201113093613-javascript_object_keywords.md
new file mode 100644
index 0000000..c42e24d
--- /dev/null
+++ b/content/20201113093613-javascript_object_keywords.md
@@ -0,0 +1,6 @@
+---
+id: 1daece10-b71b-420c-889e-18dc1933b516
+title: JavaScript Object Keywords
+---
+
+- [new](20201113093910-javascript_new_keyword)
diff --git a/content/20201113093910-javascript_new_keyword.md b/content/20201113093910-javascript_new_keyword.md
new file mode 100644
index 0000000..e197135
--- /dev/null
+++ b/content/20201113093910-javascript_new_keyword.md
@@ -0,0 +1,27 @@
+---
+id: 32b0031a-0830-4e8c-a39c-3bf6d8791f84
+title: JavaScript New Keyword
+---
+
+# Description
+
+When you put the keyword `new` in front of a function call, the function
+is treated as a constructor. An [object](20200826201605-objects) with
+the proper [prototype](20201113091424-javascript_prototypes) is
+automatically created, bound to `this` in the function and returned at
+the end of the function. This allows you to do OO type stuff.
+
+# Syntax
+
+``` javascript
+function Rabbit(type) {
+ this.type = type
+}
+Rabbit.prototype.speak = function(line) {
+ console.log(`The ${this.type} rabbit says '${line}'`)
+};
+
+let weirdRabbit = new Rabbit("weird")
+
+weirdRabbit.speak("I want carrots!")
+```
diff --git a/content/20201113094246-javascript_instanceof_operator.md b/content/20201113094246-javascript_instanceof_operator.md
new file mode 100644
index 0000000..aeb12e4
--- /dev/null
+++ b/content/20201113094246-javascript_instanceof_operator.md
@@ -0,0 +1,40 @@
+---
+id: 54c58f54-526f-4838-92c5-1a70d6b17a3c
+title: JavaScript Instanceof Operator
+---
+
+# Description
+
+Sometimes you want to know whether an [object](20200826201605-objects)
+was derived from a specific [class](20201008090316-class_notation). To
+do this one can use the `instanceof`
+[operator](20200613170705-operators_in_javascript).
+
+# Syntax
+
+``` javascript
+class Parent {
+ constructor(name, parentChild = "Parent") {
+ this.parentChild = parentChild;
+ this.name = name;
+ }
+
+ speak(line) {
+ console.log(`${this.parentChild} ${this.name} says '${line}'`);
+ }
+}
+
+class Child extends Parent {
+ constructor(name) {
+ super(name, "Child");
+ }
+}
+
+let parent = new Parent("Father");
+let child = new Child("Gregory");
+
+console.log(parent instanceof Parent); // true
+console.log(parent instanceof Child); // false
+console.log(child instanceof Parent); // true
+console.log(child instanceof Child); // true
+```
diff --git a/content/20201113094652-javascript_delete_operator.md b/content/20201113094652-javascript_delete_operator.md
new file mode 100644
index 0000000..c77bd18
--- /dev/null
+++ b/content/20201113094652-javascript_delete_operator.md
@@ -0,0 +1,21 @@
+---
+id: aed94d48-374c-4f3d-8dca-b6e4b3f9ae83
+title: JavaScript Delete Operator
+---
+
+# Description
+
+The `delete` operator deletes a binding (duh).
+
+# Syntax
+
+``` javascript
+let anObject = { left: 1, right: 2 };
+console.log(anObject.left); // 1
+
+delete anObject.left;
+
+console.log(anObject.left); // undefined
+console.log("left" in anObject); // false
+console.log("right" in anObject); // true
+```
diff --git a/content/20201113095226-object_keys.md b/content/20201113095226-object_keys.md
new file mode 100644
index 0000000..88d7c3b
--- /dev/null
+++ b/content/20201113095226-object_keys.md
@@ -0,0 +1,15 @@
+---
+id: e398afb7-a05b-4780-a3fb-8445f4196c31
+title: Object.keys
+---
+
+# Description
+
+`Object.keys` returns the [object](20200826201605-objects) property
+names as an array of strings
+
+# Syntax
+
+``` javascript
+console.log(Object.keys({x: 0, y: 0, z: 2}));
+```
diff --git a/content/20201113095244-object_assign.md b/content/20201113095244-object_assign.md
new file mode 100644
index 0000000..2a6ffcc
--- /dev/null
+++ b/content/20201113095244-object_assign.md
@@ -0,0 +1,17 @@
+---
+id: b7bd3cef-b42a-4f96-8e4c-b0c75c1e86e3
+title: Object.assign
+---
+
+# Description
+
+`Object.assign` copies all properties from one
+[object](20200826201605-objects) into another
+
+# Syntax
+
+``` javascript
+let objectA = { a: 1, b: 2 };
+Object.assign(objectA, { b: 3, c: 4 });
+console.log(objectA);
+```
diff --git a/content/20201113095300-object_is.md b/content/20201113095300-object_is.md
new file mode 100644
index 0000000..f28e753
--- /dev/null
+++ b/content/20201113095300-object_is.md
@@ -0,0 +1,16 @@
+---
+id: 5af724a3-745d-45a2-9d5f-e62dc9a22264
+title: Object.is
+---
+
+# Description
+
+`Object.is` provides a way of comparing values that's more precise than
+`===`
+
+``` javascript
+console.log(NaN === NaN) // false
+console.log(Object.is(NaN, NaN)) //true
+console.log(-0 === +0) // true
+console.log(Object.is(-0, +0)) // false
+```
diff --git a/content/20201113102048-object_entries.md b/content/20201113102048-object_entries.md
new file mode 100644
index 0000000..a358f59
--- /dev/null
+++ b/content/20201113102048-object_entries.md
@@ -0,0 +1,29 @@
+---
+id: 55435d35-26e1-460b-a683-ef346c9972a8
+title: Object.entries
+---
+
+# Description
+
+Returns [object](20200826201605-objects) properties as key / value
+pairs. Can be used with [maps](20201012093745-javascript_maps) as well.
+It does the opposite of
+[Object.fromEntries](20201116095124-object_fromentries).
+
+# Syntax
+
+``` javascript
+console.log(Object.entries({ one: 1, two: 2 })); // [['one', 1], ['two', 2]]
+```
+
+## Maps
+
+``` javascript
+let map = new Map(
+ Object.entries({
+ one: 1,
+ two: 2,
+ })
+);
+console.log(JSON.stringify([...map])); // [["one", 1], ["two", 2]]
+```
diff --git a/content/20201113102106-object_values.md b/content/20201113102106-object_values.md
new file mode 100644
index 0000000..5fb6caa
--- /dev/null
+++ b/content/20201113102106-object_values.md
@@ -0,0 +1,14 @@
+---
+id: 2e9254e9-0f90-404e-8f94-0b92d8355a0d
+title: Object.values
+---
+
+# Description
+
+Returns [object](20200826201605-objects) values
+
+# Syntax
+
+``` javascript
+console.log(Object.values({ one: 1, two: 2 })); // [1, 2]
+```
diff --git a/content/20201113102125-object_getownpropertydescriptors.md b/content/20201113102125-object_getownpropertydescriptors.md
new file mode 100644
index 0000000..0ca2d5f
--- /dev/null
+++ b/content/20201113102125-object_getownpropertydescriptors.md
@@ -0,0 +1,33 @@
+---
+id: 6383dfcd-c925-4d33-8338-2da22edf7ef8
+title: Object.getOwnPropertyDescriptors
+---
+
+# Description
+
+Returns property descriptors of all known properties of an
+[object](20200826201605-objects).
+
+# Syntax
+
+``` javascript
+const obj = {
+ [Symbol("foo")]: 123,
+ get bar() {
+ return "abc";
+ },
+};
+console.log(Object.getOwnPropertyDescriptors(obj));
+
+// Output:
+// { [Symbol('foo')]:
+// { value: 123,
+// writable: true,
+// enumerable: true,
+// configurable: true },
+// bar:
+// { get: [Function: bar],
+// set: undefined,
+// enumerable: true,
+// configurable: true } }
+```
diff --git a/content/20201113103917-javascript_array_functions.md b/content/20201113103917-javascript_array_functions.md
new file mode 100644
index 0000000..2617738
--- /dev/null
+++ b/content/20201113103917-javascript_array_functions.md
@@ -0,0 +1,7 @@
+---
+id: 5b9bd5a3-5440-4217-a064-ea6f932ea6c1
+title: JavaScript Array Functions
+---
+
+- [Array.from()](20201113105832-array_from)
+- [Array.of()](20201113105847-array_of)
diff --git a/content/20201113104217-array_prototype_fill.md b/content/20201113104217-array_prototype_fill.md
new file mode 100644
index 0000000..1531a56
--- /dev/null
+++ b/content/20201113104217-array_prototype_fill.md
@@ -0,0 +1,11 @@
+---
+id: 78535304-014d-4b91-9e80-faf905028338
+title: Array.prototype.fill()
+---
+
+# Syntax
+
+``` javascript
+const arr2 = new Array(2).fill(undefined);
+ // [undefined, undefined]
+```
diff --git a/content/20201113104240-array_prototype_copywithin.md b/content/20201113104240-array_prototype_copywithin.md
new file mode 100644
index 0000000..68ddfd9
--- /dev/null
+++ b/content/20201113104240-array_prototype_copywithin.md
@@ -0,0 +1,23 @@
+---
+id: 5b058b90-1a05-4c2f-a64a-a56d3c768191
+title: Array.prototype.copyWithin()
+---
+
+# Description
+
+This copies the elements whose indices are in the range \[start,end) to
+index target and subsequent indices. If the two index ranges overlap,
+care is taken that all source elements are copied before they are
+overwritten. I am confused as to how this is in any way useful.
+
+# Syntax
+
+``` typescript
+Array.prototype.copyWithin(target : number,
+ start : number, end = this.length) : This
+```
+
+``` javascript
+const arr = [0,1,2,3];
+console.log(arr.copyWithin(2, 0, 2)) // [0, 1, 0, 1]
+```
diff --git a/content/20201113104332-array_prototype_findindex.md b/content/20201113104332-array_prototype_findindex.md
new file mode 100644
index 0000000..2f8ea1c
--- /dev/null
+++ b/content/20201113104332-array_prototype_findindex.md
@@ -0,0 +1,10 @@
+---
+id: 77f7c158-fbf0-48a6-a1e6-681747e5e2f0
+title: Array.prototype.findIndex()
+---
+
+# Syntax
+
+``` javascript
+console.log([6, -6, 8].findIndex((x) => x < 0)); // 1
+```
diff --git a/content/20201113104352-array_prototype_find.md b/content/20201113104352-array_prototype_find.md
new file mode 100644
index 0000000..5e3fb7a
--- /dev/null
+++ b/content/20201113104352-array_prototype_find.md
@@ -0,0 +1,10 @@
+---
+id: 0b983a9b-2314-43a8-bd48-62eea3772821
+title: Array.prototype.find()
+---
+
+# Syntax
+
+``` javascript
+console.log([6, -6, 8].find(x => x < 0)) // -6
+```
diff --git a/content/20201113104423-array_prototype_entries.md b/content/20201113104423-array_prototype_entries.md
new file mode 100644
index 0000000..f6db2a5
--- /dev/null
+++ b/content/20201113104423-array_prototype_entries.md
@@ -0,0 +1,10 @@
+---
+id: 3b9f351b-0109-4c94-80e4-af3ac83542e6
+title: Array.prototype.entries()
+---
+
+# Syntax
+
+``` javascript
+console.log(Array.from(['a', 'b'].entries())) // [ [ 0, 'a' ], [ 1, 'b' ] ]
+```
diff --git a/content/20201113104438-array_prototype_values.md b/content/20201113104438-array_prototype_values.md
new file mode 100644
index 0000000..da2687e
--- /dev/null
+++ b/content/20201113104438-array_prototype_values.md
@@ -0,0 +1,10 @@
+---
+id: e04ce909-2885-406b-8e0e-e5846354ce86
+title: Array.prototype.values()
+---
+
+# Syntax
+
+``` javascript
+console.log(Array.from(['a', 'b'].values())) // ['a', 'b']
+```
diff --git a/content/20201113104454-array_prototype_keys.md b/content/20201113104454-array_prototype_keys.md
new file mode 100644
index 0000000..dd742a7
--- /dev/null
+++ b/content/20201113104454-array_prototype_keys.md
@@ -0,0 +1,10 @@
+---
+id: c9e2a406-1c26-4c8d-a240-974290a3eeaa
+title: Array.prototype.keys()
+---
+
+# Syntax
+
+``` javascript
+console.log(Array.from(['a', 'b'].keys())) // [0, 1]
+```
diff --git a/content/20201113104552-array_search_prototype_methods.md b/content/20201113104552-array_search_prototype_methods.md
new file mode 100644
index 0000000..90cfea0
--- /dev/null
+++ b/content/20201113104552-array_search_prototype_methods.md
@@ -0,0 +1,7 @@
+---
+id: ce59ae91-4c0b-44f6-9443-0fe2518c39ea
+title: Array Search Prototype Methods
+---
+
+- [Array.prototype.findIndex()](20201113104332-array_prototype_findindex)
+- [Array.prototype.find()](20201113104352-array_prototype_find)
diff --git a/content/20201113104619-array_iteration_prototype_methods.md b/content/20201113104619-array_iteration_prototype_methods.md
new file mode 100644
index 0000000..35e727f
--- /dev/null
+++ b/content/20201113104619-array_iteration_prototype_methods.md
@@ -0,0 +1,8 @@
+---
+id: b442cedc-1746-4c9d-949c-f382e2b8aba6
+title: Array Iteration Prototype Methods
+---
+
+- [Array.prototype.entries()](20201113104423-array_prototype_entries)
+- [Array.prototype.values()](20201113104438-array_prototype_values)
+- [Array.prototype.keys()](20201113104454-array_prototype_keys)
diff --git a/content/20201113105707-array_prototype_includes.md b/content/20201113105707-array_prototype_includes.md
new file mode 100644
index 0000000..dd0e5b5
--- /dev/null
+++ b/content/20201113105707-array_prototype_includes.md
@@ -0,0 +1,15 @@
+---
+id: d5d1956b-d2f4-4cff-bed9-40900f3e1bae
+title: Array.prototype.includes()
+---
+
+# Description
+
+Tells you if array includes a certain element:
+
+# Syntax
+
+``` javascript
+console.log(["a", "b", "c"].includes("a")); // true
+console.log(["a", "b", "c"].includes("d")); // false
+```
diff --git a/content/20201113105832-array_from.md b/content/20201113105832-array_from.md
new file mode 100644
index 0000000..249a17c
--- /dev/null
+++ b/content/20201113105832-array_from.md
@@ -0,0 +1,22 @@
+---
+id: 5ec66270-20b0-466e-85ea-dd7472a84880
+title: Array.from()
+---
+
+# Syntax
+
+``` javascript
+const arr2 = Array.from(arguments);
+```
+
+If a value is [iterable](20201014092625-javascript_iterables) (as all
+Array-like DOM data structure are by now), you can also use the
+[spread](20201014094144-spread) operator (…) to convert it to an
+[Array](20200826201029-arrays):
+
+``` javascript
+const arr1 = [...'abc'];
+ // ['a', 'b', 'c']
+const arr2 = [...new Set().add('a').add('b')];
+ // ['a', 'b']
+```
diff --git a/content/20201113105847-array_of.md b/content/20201113105847-array_of.md
new file mode 100644
index 0000000..10f21e2
--- /dev/null
+++ b/content/20201113105847-array_of.md
@@ -0,0 +1,14 @@
+---
+id: 5952e353-e25e-4a74-95ce-8099891212c5
+title: Array.of()
+---
+
+# Description
+
+This returns an array of the passed parameters
+
+# Syntax
+
+``` javascript
+console.log(Array.of(1, 2, 3, 4)) // [1, 2, 3, 4]
+```
diff --git a/content/20201113111815-es2019.md b/content/20201113111815-es2019.md
new file mode 100644
index 0000000..a9671ee
--- /dev/null
+++ b/content/20201113111815-es2019.md
@@ -0,0 +1,40 @@
+---
+id: 7aa626ee-1b0b-4d7c-8e11-751f8b9cac1f
+title: ES2019
+---
+
+# Array
+
+## Prototype methods
+
+- [Array.prototype.flatMap()](20201113112058-array_prototype_flatmap)
+- [Array.prototype.flat()](20201113112029-array_prototype_flat)
+
+### Sort
+
+- [.sort() is guaranteed to be
+ stable](20201116155810-sort_is_guaranteed_to_be_stable)
+
+# Control flow
+
+- [Optional Catch
+ Binding](20201116154824-javascript_optional_catch_binding)
+
+# Objects
+
+## Functions
+
+- [Object.fromEntries](20201116095124-object_fromentries)
+
+# Strings
+
+## String Prototype methods
+
+- [trimStart](20201116100205-trimstart)
+- [trimEnd](20201116100239-trimend)
+
+# Symbols
+
+## Symbol Prototype methods
+
+- [Symbol.prototype.description](20201116101509-symbol_prototype_description)
diff --git a/content/20201113112029-array_prototype_flat.md b/content/20201113112029-array_prototype_flat.md
new file mode 100644
index 0000000..50ed81c
--- /dev/null
+++ b/content/20201113112029-array_prototype_flat.md
@@ -0,0 +1,22 @@
+---
+id: caa62af1-768f-4ca7-95c2-ba38c703101d
+title: Array.prototype.flat()
+---
+
+# Description
+
+Flattens an [array](20200826201029-arrays).
+
+# Type Signature
+
+``` typescript
+.flat(depth = 1): any[]
+```
+
+# Syntax
+
+``` javascript
+console.log([1, 2, [3, 4], [[5, 6]]].flat(0)); // [ 1, 2, [ 3, 4 ], [ [ 5, 6 ] ] ]
+console.log([1, 2, [3, 4], [[5, 6]]].flat(1)); // [ 1, 2, 3, 4, [ 5, 6 ] ]
+console.log([1, 2, [3, 4], [[5, 6]]].flat(2)); // [ 1, 2, 3, 4, 5, 6 ]
+```
diff --git a/content/20201113112058-array_prototype_flatmap.md b/content/20201113112058-array_prototype_flatmap.md
new file mode 100644
index 0000000..f06d0ed
--- /dev/null
+++ b/content/20201113112058-array_prototype_flatmap.md
@@ -0,0 +1,31 @@
+---
+id: 1aa59b86-bbea-4eb7-beac-67c7e1460ecf
+title: Array.prototype.flatMap()
+---
+
+# Description
+
+Is the same as calling [JavaScript Maps](20201012093745-javascript_maps)
+and then flattening the result. Ie:
+
+``` javascript
+arr.map(func).flat(1)
+```
+
+# Type Signature
+
+``` typescript
+.flatMap(
+ callback: (value: T, index: number, array: T[]) => U|Array,
+ thisValue?: any
+): U[]
+```
+
+# Syntax
+
+``` javascript
+console.log(["a", "b", "c"].flatMap((x) => x)); // [ 'a', 'b', 'c' ]
+console.log(["a", "b", "c"].flatMap((x) => [x])); // [ 'a', 'b', 'c' ]
+console.log(["a", "b", "c"].flatMap((x) => [[x]])); // [ [ 'a' ], [ 'b' ], [ 'c' ] ]
+console.log(["a", "b", "c"].flatMap((x, i) => new Array(i + 1).fill(x))); // [ 'a', 'b', 'b', 'c', 'c', 'c' ]
+```
diff --git a/content/20201113113141-array_prototype_map.md b/content/20201113113141-array_prototype_map.md
new file mode 100644
index 0000000..0fd8d74
--- /dev/null
+++ b/content/20201113113141-array_prototype_map.md
@@ -0,0 +1,18 @@
+---
+id: d8cdd418-4d65-4814-b362-9db4a6730030
+title: Array.prototype.map()
+---
+
+# Description
+
+`map()` creates a new array populated with the results of calling a
+passed function on every element in the calling array.
+
+# Syntax
+
+``` javascript
+const array = [1, 2, 3, 4];
+const map1 = array.map((x) => x * 2);
+
+console.log(map1); // [(2, 4, 6, 8)]
+```
diff --git a/content/20201113115201-php_string_functions.md b/content/20201113115201-php_string_functions.md
new file mode 100644
index 0000000..624c79a
--- /dev/null
+++ b/content/20201113115201-php_string_functions.md
@@ -0,0 +1,10 @@
+---
+id: ed9de386-a191-4fd8-a842-bb7d55df52a0
+title: PHP string functions
+---
+
+# Checks
+
+- [str~contains~()](20201113115231-str_contains)
+- [str~startswith~()](20201113115424-str_starts_with)
+- [str~endswith~()](20201113115452-str_ends_with)
diff --git a/content/20201113115231-str_contains.md b/content/20201113115231-str_contains.md
new file mode 100644
index 0000000..50547dd
--- /dev/null
+++ b/content/20201113115231-str_contains.md
@@ -0,0 +1,18 @@
+---
+id: 01a0e800-3398-490b-bbb1-3abc80fb198c
+title: str~contains~()
+---
+
+# Description
+
+str~contains~()[^1] tells us if string contains other string.
+
+# Syntax
+
+``` php
+if (str_contains('Lululu I've got some apples, 'apples')) { /* … */ }
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113115424-str_starts_with.md b/content/20201113115424-str_starts_with.md
new file mode 100644
index 0000000..0092645
--- /dev/null
+++ b/content/20201113115424-str_starts_with.md
@@ -0,0 +1,19 @@
+---
+id: 264796d2-49ae-42fc-9d9d-aeea84343ef1
+title: str~startswith~()
+---
+
+# Description
+
+Checks if string starts with other string. Opposite of
+[str~endswith~()](20201113115452-str_ends_with). See RFC[^1].
+
+# Syntax
+
+``` php
+str_starts_with('haystack', 'hay') // true
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113115452-str_ends_with.md b/content/20201113115452-str_ends_with.md
new file mode 100644
index 0000000..c21e19d
--- /dev/null
+++ b/content/20201113115452-str_ends_with.md
@@ -0,0 +1,20 @@
+---
+id: 43cb053d-03b0-4845-a804-5e34da5d8db5
+title: str~endswith~()
+---
+
+# Description
+
+Check if string ends with other string. Opposite of
+[str~startswith~()](20201113115424-str_starts_with). See RFC[^1] for
+more details.
+
+# Syntax
+
+``` php
+str_ends_with('haystack', 'stack') // true
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113115907-php_type_functions.md b/content/20201113115907-php_type_functions.md
new file mode 100644
index 0000000..26249f6
--- /dev/null
+++ b/content/20201113115907-php_type_functions.md
@@ -0,0 +1,6 @@
+---
+id: 9e217634-5bde-4c98-ba86-9b63eb19b2e9
+title: PHP type functions
+---
+
+- [get~debugtype~()](20201113115939-get_debug_type)
diff --git a/content/20201113115939-get_debug_type.md b/content/20201113115939-get_debug_type.md
new file mode 100644
index 0000000..b716e8b
--- /dev/null
+++ b/content/20201113115939-get_debug_type.md
@@ -0,0 +1,28 @@
+---
+id: 62a9d222-f5a2-4131-8de7-be1b071cab26
+title: get~debugtype~()
+---
+
+# Description
+
+Returns the variable type. Differnce with `getttype()` is that
+`get_debug_type()` is more specific. See RFC[^1].
+
+# Syntax
+
+``` php
+namespace Foo;
+
+class Bar
+{
+}
+
+$bar = new Bar();
+
+echo gettype($bar)."\n"; // Object
+echo get_debug_type($bar); // Foo\Bar
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113120212-php_resource_functions.md b/content/20201113120212-php_resource_functions.md
new file mode 100644
index 0000000..db878e6
--- /dev/null
+++ b/content/20201113120212-php_resource_functions.md
@@ -0,0 +1,6 @@
+---
+id: 91bd46af-67dc-402f-ad3a-f3096eda5e7d
+title: PHP resource functions
+---
+
+- [get~resourceid~()](20201113120246-get_resource_id)
diff --git a/content/20201113120246-get_resource_id.md b/content/20201113120246-get_resource_id.md
new file mode 100644
index 0000000..577744c
--- /dev/null
+++ b/content/20201113120246-get_resource_id.md
@@ -0,0 +1,18 @@
+---
+id: bc32aee3-3e3c-42e8-b56e-8e8e7f3a8d39
+title: get~resourceid~()
+---
+
+# Description
+
+Returns a resource id as an integer. See PR[^1]
+
+# Syntax
+
+``` php
+$resourceId = get_resource_id($resource);
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113121741-php_expressions.md b/content/20201113121741-php_expressions.md
new file mode 100644
index 0000000..3382fc7
--- /dev/null
+++ b/content/20201113121741-php_expressions.md
@@ -0,0 +1,7 @@
+---
+id: efe804b8-fbb0-4644-ab98-db58a1ebaf2b
+title: PHP expressions
+---
+
+- [Match expression](20201113121813-match_expression)
+- [Switch expression](20201113122000-switch_expression)
diff --git a/content/20201113121813-match_expression.md b/content/20201113121813-match_expression.md
new file mode 100644
index 0000000..44ea157
--- /dev/null
+++ b/content/20201113121813-match_expression.md
@@ -0,0 +1,114 @@
+---
+id: 0d9d39fc-149c-4190-a06d-1de455c930a4
+title: Match expression
+---
+
+# Description
+
+Match expressions[^1] are similar to
+[switch](20201113122000-switch_expression), but with safer semantics and
+the ability to return values.
+
+# Syntax
+
+``` php
+// After
+$statement = match ($this->lexer->lookahead['type']) {
+ Lexer::T_SELECT => $this->SelectStatement(),
+ Lexer::T_UPDATE => $this->UpdateStatement(),
+ Lexer::T_DELETE => $this->DeleteStatement(),
+ default => $this->syntaxError('SELECT, UPDATE or DELETE'),
+};
+```
+
+## Return value
+
+``` php
+switch (1) {
+ case 0:
+ $result = 'Foo';
+ break;
+ case 1:
+ $result = 'Bar';
+ break;
+ case 2:
+ $result = 'Baz';
+ break;
+}
+
+echo $result;
+//> Bar
+
+echo match (1) {
+ 0 => 'Foo',
+ 1 => 'Bar',
+ 2 => 'Baz',
+};
+//> Bar
+```
+
+## No type coercion
+
+``` php
+switch ('foo') {
+ case 0:
+ $result = "Oh no!\n";
+ break;
+ case 'foo':
+ $result = "This is what I expected\n";
+ break;
+}
+echo $result;
+//> Oh no!
+
+echo match ('foo') {
+ 0 => "Oh no!\n",
+ 'foo' => "This is what I expected\n",
+};
+//> This is what I expected
+```
+
+## No fallthrough
+
+``` php
+switch ($pressedKey) {
+ case Key::RETURN_:
+ save();
+ // Oops, forgot the break
+ case Key::DELETE:
+ delete();
+ break;
+}
+
+match ($pressedKey) {
+ Key::RETURN_ => save(),
+ Key::DELETE => delete(),
+};
+
+echo match ($x) {
+ 1, 2 => 'Same for 1 and 2',
+ 3, 4 => 'Same for 3 and 4',
+};
+```
+
+## Exhaustiveness
+
+``` php
+switch ($operator) {
+ case BinaryOperator::ADD:
+ $result = $lhs + $rhs;
+ break;
+}
+
+// Forgot to handle BinaryOperator::SUBTRACT
+
+$result = match ($operator) {
+ BinaryOperator::ADD => $lhs + $rhs,
+};
+
+// Throws when $operator is BinaryOperator::SUBTRACT
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113122000-switch_expression.md b/content/20201113122000-switch_expression.md
new file mode 100644
index 0000000..5c87467
--- /dev/null
+++ b/content/20201113122000-switch_expression.md
@@ -0,0 +1,30 @@
+---
+id: f9e3afdc-2d87-4ba2-a9de-758e403bff56
+title: Switch expression
+---
+
+# Description
+
+Probably a better idea to use [match](20201113121813-match_expression).
+
+# Syntax
+
+``` php
+switch ($this->lexer->lookahead['type']) {
+ case Lexer::T_SELECT:
+ $statement = $this->SelectStatement();
+ break;
+
+ case Lexer::T_UPDATE:
+ $statement = $this->UpdateStatement();
+ break;
+
+ case Lexer::T_DELETE:
+ $statement = $this->DeleteStatement();
+ break;
+
+ default:
+ $this->syntaxError('SELECT, UPDATE or DELETE');
+ break;
+}
+```
diff --git a/content/20201113122505-php_types.md b/content/20201113122505-php_types.md
new file mode 100644
index 0000000..40cbeda
--- /dev/null
+++ b/content/20201113122505-php_types.md
@@ -0,0 +1,7 @@
+---
+id: 60007d1e-e543-46a0-bf43-509ed0bf96d1
+title: PHP Types
+---
+
+- [Union Type](20201109133923-php_union_type)
+- [Mixed Type](20201113122959-php_mixed_type)
diff --git a/content/20201113122801-php_functions.md b/content/20201113122801-php_functions.md
new file mode 100644
index 0000000..16553db
--- /dev/null
+++ b/content/20201113122801-php_functions.md
@@ -0,0 +1,8 @@
+---
+id: 40b230dd-eb86-408b-8dbb-098a19ebaf93
+title: PHP functions
+---
+
+- [Resource Functions](20201113120212-php_resource_functions)
+- [String Functions](20201113115201-php_string_functions)
+- [Type Functions](20201113115907-php_type_functions)
diff --git a/content/20201113122959-php_mixed_type.md b/content/20201113122959-php_mixed_type.md
new file mode 100644
index 0000000..1e5754e
--- /dev/null
+++ b/content/20201113122959-php_mixed_type.md
@@ -0,0 +1,13 @@
+---
+id: 13fec466-6379-452f-abdb-3360b69adcb8
+title: PHP Mixed Type
+---
+
+# Description
+
+mixed[^1] is the PHP equivalent to
+[JavaScript's](20200613170905-javascript) any type.
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113123224-php_objects.md b/content/20201113123224-php_objects.md
new file mode 100644
index 0000000..0e580ab
--- /dev/null
+++ b/content/20201113123224-php_objects.md
@@ -0,0 +1,6 @@
+---
+id: 0db5ee45-e14e-44ff-97b3-5b9320ad81cb
+title: PHP Objects
+---
+
+- [DateTime](20201113123254-datetime)
diff --git a/content/20201113123254-datetime.md b/content/20201113123254-datetime.md
new file mode 100644
index 0000000..0ed5edc
--- /dev/null
+++ b/content/20201113123254-datetime.md
@@ -0,0 +1,9 @@
+---
+id: dcb3bb34-214e-4f36-8da4-d4f0771ec1e5
+title: DateTime
+---
+
+# Methods
+
+- [createFromInterface()](20201113123327-createfrominterface)
+- [createFromImmutable()](20201113123648-createfromimmutable)
diff --git a/content/20201113123327-createfrominterface.md b/content/20201113123327-createfrominterface.md
new file mode 100644
index 0000000..112f2b8
--- /dev/null
+++ b/content/20201113123327-createfrominterface.md
@@ -0,0 +1,17 @@
+---
+id: 6e9abc7a-fef2-4450-9694-8311eb70d7cf
+title: createFromInterface()
+---
+
+# Description
+
+Similar to [createFromImmutable()](20201113123648-createfromimmutable),
+but this method creates a date from any object that uses
+`DateTimeInterface`
+
+# Syntax
+
+``` php
+DateTime::createFromInterface(DateTimeInterface $other);
+DateTimeImmutable::createFromInterface(DateTimeInterface $other);
+```
diff --git a/content/20201113123648-createfromimmutable.md b/content/20201113123648-createfromimmutable.md
new file mode 100644
index 0000000..a9048b7
--- /dev/null
+++ b/content/20201113123648-createfromimmutable.md
@@ -0,0 +1,16 @@
+---
+id: 984344e1-ee68-4475-b792-71f2a6e0b766
+title: createFromImmutable()
+---
+
+# Description
+
+Similar to [createFromInterface()](20201113123327-createfrominterface),
+but this function creates a `DateTime` object from a `DateTimeImmutable`
+one.
+
+# Syntax
+
+``` php
+DateTime::createFromImmutable(new DateTimeImmutable())
+```
diff --git a/content/20201113172025-symfony_5_0.md b/content/20201113172025-symfony_5_0.md
new file mode 100644
index 0000000..b7afe7f
--- /dev/null
+++ b/content/20201113172025-symfony_5_0.md
@@ -0,0 +1,64 @@
+---
+id: 571452e9-b81e-42a3-95a2-306375073bc5
+title: Symfony 5.0
+---
+
+# Components
+
+## Mailer
+
+- [Signing and Encrypting
+ Messages](20201113173159-signing_and_encrypting_messages)
+
+## Mime
+
+- [Notification Emails](20201113174016-notification_emails)
+
+## Console
+
+- [Horizontal Tables](20201113180928-horizontal_tables)
+- [horizontalTable()](20201113181235-horizontaltable)
+- [Definition Lists](20201113181356-definition_lists)
+
+## HttpFoundation
+
+- [IP Address Anonymizer](20201113182523-ip_address_anonymizer)
+
+## String
+
+- [String Component](20201113183442-string_component)
+
+# Constraints
+
+## Type Constraint
+
+- [Type Constraint Can Be An Array of
+ Types](20201113172816-type_constraint_can_be_an_array_of_types)
+
+# Doctrine
+
+- [Invokable Doctrine Entity
+ Listeners](20201113180722-invokable_doctrine_entity_listeners)
+
+# Encryption
+
+- [Ecrypted Secrets
+ Management](20201113174444-ecrypted_secrets_management)
+
+# Event Listeners
+
+- [Simpler Event Listeners](20201113180227-simpler_event_listeners)
+
+# Firewalls
+
+- [Lazy Firewalls](20201113183038-lazy_firewalls)
+
+# Forms
+
+## Types
+
+- [Week Form Type](20201113182753-week_form_type)
+
+# Password
+
+- [Password Migrations](20201113181759-password_migrations)
diff --git a/content/20201113172517-type_constraint.md b/content/20201113172517-type_constraint.md
new file mode 100644
index 0000000..dfc021b
--- /dev/null
+++ b/content/20201113172517-type_constraint.md
@@ -0,0 +1,45 @@
+---
+id: 942ca5cd-5ca6-4ec9-9dd6-652a0b791245
+title: Type Constraint
+---
+
+# Description
+
+Validates that a given value is of a specific type. This type can be any
+of the valid PHP types[^1], any of the PHP ctype functions[^2] (e.g.
+alnum, alpha, digit, etc.) and also the FQCN of any class
+
+# Syntax
+
+``` php
+// src/Entity/Author.php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class Author
+{
+ /**
+ * @Assert\Type("Ramsey\Uuid\UuidInterface")
+ */
+ protected $id;
+
+ /**
+ * @Assert\Type("string")
+ */
+ protected $firstName;
+
+ // ...
+}
+```
+
+# Changelog
+
+- [Type Constraint Can Be An Array of
+ Types](20201113172816-type_constraint_can_be_an_array_of_types)
+
+# Footnotes
+
+[^1]:
+
+[^2]:
diff --git a/content/20201113172816-type_constraint_can_be_an_array_of_types.md b/content/20201113172816-type_constraint_can_be_an_array_of_types.md
new file mode 100644
index 0000000..5280ea6
--- /dev/null
+++ b/content/20201113172816-type_constraint_can_be_an_array_of_types.md
@@ -0,0 +1,23 @@
+---
+id: e8cb1177-6034-441f-a23e-bcdbf5a31fa6
+title: Type Constraint Can Be An Array of Types
+---
+
+# Syntax
+
+``` php
+// src/Entity/Author.php
+namespace App\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class Author
+{
+ // ...
+
+ /**
+ * @Assert\Type(type={"alpha", "digit"})
+ */
+ protected $accessCode;
+}
+```
diff --git a/content/20201113173159-signing_and_encrypting_messages.md b/content/20201113173159-signing_and_encrypting_messages.md
new file mode 100644
index 0000000..7f16339
--- /dev/null
+++ b/content/20201113173159-signing_and_encrypting_messages.md
@@ -0,0 +1,41 @@
+---
+id: 9256ee27-61e2-4c9f-b49c-bc2efe01d528
+title: Signing and Encrypting Messages
+---
+
+# Description
+
+Emails can be signed an encrypted using the S/MIME[^1] standard.
+
+# Syntax
+
+## Signing
+
+``` php
+use Symfony\Component\Mime\Crypto\SMimeSigner;
+use Symfony\Component\Mime\Email;
+
+$email = (new Email())->from('...')->to('...')->html('...');
+
+$signer = new SMimeSigner('/path/to/certificate.crt', '/path/to/certificate-private-key.key');
+$signedEmail = $signer->sign($email);
+// now use the Mailer to send this $signedEmail instead of the original $email
+
+```
+
+## Encrypting
+
+``` php
+use Symfony\Component\Mime\Crypto\SMimeEncrypter;
+use Symfony\Component\Mime\Email;
+
+$email = (new Email())->from('...')->to('...')->html('...');
+
+$encrypter = new SMimeEncrypter('/path/to/certificate.crt');
+$encryptedEmail = $encrypter->encrypt($email);
+// now use the Mailer to send this $encryptedEmail instead of the original $email
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113174016-notification_emails.md b/content/20201113174016-notification_emails.md
new file mode 100644
index 0000000..f023167
--- /dev/null
+++ b/content/20201113174016-notification_emails.md
@@ -0,0 +1,28 @@
+---
+id: b6e6c503-c614-4bf3-872a-3a6202e2dbfb
+title: Notification Emails
+---
+
+# Description
+
+Standardized messages used to send notifications to yourself
+
+# Syntax
+
+``` php
+use Symfony\Bridge\Twig\Mime\NotificationEmail;
+
+$email = (new NotificationEmail())
+ ->from('fabien@example.com')
+ ->to('fabien@example.org')
+ ->subject('My first notification email via Symfony')
+ ->markdown(<<action('More info?', 'https://example.com/')
+ ->importance(NotificationEmail::IMPORTANCE_HIGH)
+;
+```
diff --git a/content/20201113174255-encryption.md b/content/20201113174255-encryption.md
new file mode 100644
index 0000000..3fa00f5
--- /dev/null
+++ b/content/20201113174255-encryption.md
@@ -0,0 +1,9 @@
+---
+id: b3ac98d7-046d-4f86-b487-36df5d184718
+title: Encryption
+---
+
+- [Signing and Encrypting
+ Messages](20201113173159-signing_and_encrypting_messages)
+- [Ecrypted Secrets
+ Management](20201113174444-ecrypted_secrets_management)
diff --git a/content/20201113174444-ecrypted_secrets_management.md b/content/20201113174444-ecrypted_secrets_management.md
new file mode 100644
index 0000000..1f563fb
--- /dev/null
+++ b/content/20201113174444-ecrypted_secrets_management.md
@@ -0,0 +1,53 @@
+---
+id: e9ea6e53-c383-4b23-b90c-77cd202f8002
+title: Ecrypted Secrets Management
+---
+
+# Description
+
+Encryption based feature to manage secrets.
+
+# Steps
+
+## 1. Generate keys to encrypt/decrypt secret
+
+Uses libsodium[^1] and public key cryptography.
+
+``` shell
+php bin/console secrets:generate-keys
+```
+
+## 2. Upload private key
+
+Upload the private key to your remote server using SSH or any other safe
+means and store it in the same `config/secrets//`
+directory.
+
+## 3. Create a new secret to store the contents
+
+``` shell
+php bin/console secrets:set DATABASE_URL
+
+Please type the secret value:
+**************
+
+[OK] Secret "DATABASE_URL" encrypted in "config/secrets/dev/"
+you can commit it.
+```
+
+# Using secret
+
+Use this new secret as any other normal env var in your configuration
+files and Symfony will decrypt the value transparently when needed.
+
+``` yaml
+# config/packages/doctrine.yaml
+doctrine:
+ dbal:
+ url: "%env(DATABASE_URL)%"
+ # ...
+```
+
+# Footnotes
+
+[^1]:
diff --git a/content/20201113175016-events.md b/content/20201113175016-events.md
new file mode 100644
index 0000000..726e751
--- /dev/null
+++ b/content/20201113175016-events.md
@@ -0,0 +1,18 @@
+---
+id: cdbbd32f-7f21-453d-abbf-f04f8180587b
+title: Events
+---
+
+# Events
+
+- [ConsoleEvents::SIGNAL](20201117115146-consoleevents_signal)
+- [LogoutEvent](20201116133907-logoutevent)
+
+# Listeners
+
+- [Event Listeners](20201113175527-event_listeners)
+
+# Subscribers
+
+- [Subscribing to events in the micro
+ kernel](20201109150109-subscribing_to_events_in_the_micro_kernel)
diff --git a/content/20201113175145-listeners.md b/content/20201113175145-listeners.md
new file mode 100644
index 0000000..6b326d8
--- /dev/null
+++ b/content/20201113175145-listeners.md
@@ -0,0 +1,13 @@
+---
+id: 87b446e7-b094-417c-a948-b0bb01955c13
+title: Listeners
+---
+
+# Events
+
+- [Event Listeners](20201113175527-event_listeners)
+
+# Doctrine
+
+- [Doctrine Entity
+ Listeners](20201113180551-doctrine_entity_listeners)
diff --git a/content/20201113175249-subscribers.md b/content/20201113175249-subscribers.md
new file mode 100644
index 0000000..e9897b6
--- /dev/null
+++ b/content/20201113175249-subscribers.md
@@ -0,0 +1,9 @@
+---
+id: 2071b494-c2e9-4443-87bc-a71e1ff97b27
+title: Subscribers
+---
+
+# Events
+
+- [Subscribing to events in the micro
+ kernel](20201109150109-subscribing_to_events_in_the_micro_kernel)
diff --git a/content/20201113175527-event_listeners.md b/content/20201113175527-event_listeners.md
new file mode 100644
index 0000000..2439158
--- /dev/null
+++ b/content/20201113175527-event_listeners.md
@@ -0,0 +1,19 @@
+---
+id: d0882e91-d271-4c96-8d83-16500eadfb85
+title: Event Listeners
+---
+
+# Syntax
+
+## Pre Symfony 5.0
+
+``` yaml
+# config/services.yaml
+services:
+ tags:
+ - { name: kernel.event_listener, event: kernel.request }
+```
+
+## Symfony 5.0
+
+[Simpler Event Listeners](20201113180227-simpler_event_listeners)
diff --git a/content/20201113180227-simpler_event_listeners.md b/content/20201113180227-simpler_event_listeners.md
new file mode 100644
index 0000000..cbc19d5
--- /dev/null
+++ b/content/20201113180227-simpler_event_listeners.md
@@ -0,0 +1,28 @@
+---
+id: 0ada3ab8-c8d8-4803-9c7d-7e044533f793
+title: Simpler Event Listeners
+---
+
+# Syntax
+
+``` yaml
+# config/services.yaml
+services:
+ App\EventListener\MyRequestListener:
+ tags:
+ - { name: kernel.event_listener }
+```
+
+``` php
+namespace App\EventListener;
+
+use Symfony\Component\HttpKernel\Event\RequestEvent;
+
+final class MyRequestListener
+{
+ public function __invoke(RequestEvent $event): void
+ {
+ // ...
+ }
+}
+```
diff --git a/content/20201113180551-doctrine_entity_listeners.md b/content/20201113180551-doctrine_entity_listeners.md
new file mode 100644
index 0000000..e1dfee9
--- /dev/null
+++ b/content/20201113180551-doctrine_entity_listeners.md
@@ -0,0 +1,11 @@
+---
+id: f966e2d6-6e78-40c5-b8ac-73f3ea4f24fe
+title: Doctrine Entity Listeners
+---
+
+# Syntax
+
+## Symfony 5.0
+
+[Invokable Doctrine Entity
+Listeners](20201113180722-invokable_doctrine_entity_listeners)
diff --git a/content/20201113180722-invokable_doctrine_entity_listeners.md b/content/20201113180722-invokable_doctrine_entity_listeners.md
new file mode 100644
index 0000000..5384505
--- /dev/null
+++ b/content/20201113180722-invokable_doctrine_entity_listeners.md
@@ -0,0 +1,36 @@
+---
+id: f5d0ea95-75b4-48e9-99b4-7e420fd40956
+title: Invokable Doctrine Entity Listeners
+---
+
+# Syntax
+
+``` php
+namespace App\EventListener;
+
+use App\Entity\User;
+use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
+
+class UserChangedNotifier
+{
+ public function __invoke(User $user, LifecycleEventArgs $event)
+ {
+ // ...
+ }
+}
+```
+
+``` yaml
+services:
+ # ...
+
+ App\EventListener\UserChangedNotifier:
+ tags:
+ -
+ name: 'doctrine.orm.entity_listener'
+ entity: 'App\Entity\User'
+ # before, when not defining the method name, Symfony looked for
+ # a method called after the event (e.g. 'postUpdate()') Now it
+ # will also look for an '__invoke()' method
+ event: 'postUpdate'
+```
diff --git a/content/20201113180928-horizontal_tables.md b/content/20201113180928-horizontal_tables.md
new file mode 100644
index 0000000..1a7b59f
--- /dev/null
+++ b/content/20201113180928-horizontal_tables.md
@@ -0,0 +1,26 @@
+---
+id: f236ec20-fe58-437d-bfe1-a1ed58bcf3e0
+title: Horizontal Tables
+---
+
+# Syntax
+
+``` php
+$table
+ ->setHeaders(['ISBN', 'Title', 'Author'])
+ ->setRows([
+ // ... the rows ...
+ ])
+ ->setHorizontal()
+;
+```
+
+# Output
+
+``` shell
++--------+-----------------+----------------------+-----------------------+--------------------------+
+| ISBN | 99921-58-10-7 | 9971-5-0210-0 | 960-425-059-0 | 80-902734-1-6 |
+| Title | Divine Comedy | A Tale of Two Cities | The Lord of the Rings | And Then There Were None |
+| Author | Dante Alighieri | Charles Dickens | J. R. R. Tolkien | Agatha Christie |
++--------+-----------------+----------------------+-----------------------+--------------------------+
+```
diff --git a/content/20201113181152-symfony_style_for_commands.md b/content/20201113181152-symfony_style_for_commands.md
new file mode 100644
index 0000000..c3725ec
--- /dev/null
+++ b/content/20201113181152-symfony_style_for_commands.md
@@ -0,0 +1,8 @@
+---
+id: 9d8559b3-c0ea-40c9-99d8-a9af58b9d722
+title: Symfony Style For Commands
+---
+
+# Methods
+
+- [horizontalTable()](20201113181235-horizontaltable)
diff --git a/content/20201113181235-horizontaltable.md b/content/20201113181235-horizontaltable.md
new file mode 100644
index 0000000..f87337e
--- /dev/null
+++ b/content/20201113181235-horizontaltable.md
@@ -0,0 +1,21 @@
+---
+id: 5bae3aba-682a-4e09-b6b5-e71fabd8072d
+title: horizontalTable()
+---
+
+# Syntax
+
+``` php
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+protected function execute(InputInterface $input, OutputInterface $output)
+{
+ $io = new SymfonyStyle($input, $output);
+ $io->horizontalTable(
+ ['ISBN', 'Title', 'Author'],
+ [
+ // ... the rows ...
+ ]
+ );
+}
+```
diff --git a/content/20201113181356-definition_lists.md b/content/20201113181356-definition_lists.md
new file mode 100644
index 0000000..f0b3d61
--- /dev/null
+++ b/content/20201113181356-definition_lists.md
@@ -0,0 +1,32 @@
+---
+id: e745d46a-809d-4c00-ada0-c2b99d1d4007
+title: Definition Lists
+---
+
+# Syntax
+
+``` php
+use Symfony\Component\Console\Helper\TableSeparator;
+
+$io->definitionList(
+ ['Version' => '4.4.0'],
+ ['Long-Term Support' => 'Yes'],
+ new TableSeparator(),
+ 'Timeline',
+ ['End of maintenance' => '11/2022'],
+ ['End of life' => '11/2023']
+);
+```
+
+# Output
+
+``` shell
+-------------------- ---------
+Version 4.4.0
+Long-Term Support Yes
+-------------------- ---------
+Timeline
+End of maintenance 11/2022
+End of life 11/2023
+-------------------- ---------
+```
diff --git a/content/20201113181759-password_migrations.md b/content/20201113181759-password_migrations.md
new file mode 100644
index 0000000..55b2ddb
--- /dev/null
+++ b/content/20201113181759-password_migrations.md
@@ -0,0 +1,42 @@
+---
+id: 11f17415-46cc-4100-ab3d-4e552dd8886e
+title: Password Migrations
+---
+
+# Description
+
+Automatically upgrade password hashes by using
+`PasswordUpgraderInterface` interface
+
+# Syntax
+
+``` yaml
+# config/packages/security.yaml
+security:
+ # ...
+ encoders:
+ App\Entity\User:
+ algorithm: 'argon2i'
+ migrate_from: 'bcrypt'
+```
+
+``` php
+// src/Repository/UserRepository.php
+namespace App\Repository;
+
+// ...
+use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
+
+class UserRepository extends EntityRepository implements PasswordUpgraderInterface
+{
+ // ...
+
+ public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
+ {
+ // this code is only an example; the exact code will depend on
+ // your own application needs
+ $user->setPassword($newEncodedPassword);
+ $this->getEntityManager()->flush($user);
+ }
+}
+```
diff --git a/content/20201113182438-httpfoundation_component.md b/content/20201113182438-httpfoundation_component.md
new file mode 100644
index 0000000..291c05b
--- /dev/null
+++ b/content/20201113182438-httpfoundation_component.md
@@ -0,0 +1,8 @@
+---
+id: 9b8a1375-52e2-4b8b-9373-7b3457a4b194
+title: HttpFoundation Component
+---
+
+# IpUtils
+
+- [IP Address Anonymizer](20201113182523-ip_address_anonymizer)
diff --git a/content/20201113182523-ip_address_anonymizer.md b/content/20201113182523-ip_address_anonymizer.md
new file mode 100644
index 0000000..938794b
--- /dev/null
+++ b/content/20201113182523-ip_address_anonymizer.md
@@ -0,0 +1,22 @@
+---
+id: 8a367c4d-c104-41ff-953b-23d5d154028f
+title: IP Address Anonymizer
+---
+
+# Description
+
+Removes last byte for IPV4 addresses and last 8 bytes for IPV6
+
+# Syntax
+
+``` php
+use Symfony\Component\HttpFoundation\IpUtils;
+
+$ipv4 = '123.234.235.236';
+$anonymousIpv4 = IPUtils::anonymize($ipv4);
+// $anonymousIpv4 = '123.234.235.0'
+
+$ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
+$anonymousIpv6 = IPUtils::anonymize($ipv6);
+// $anonymousIpv6 = '2a01:198:603:10::'
+```
diff --git a/content/20201113182753-week_form_type.md b/content/20201113182753-week_form_type.md
new file mode 100644
index 0000000..78f4e73
--- /dev/null
+++ b/content/20201113182753-week_form_type.md
@@ -0,0 +1,33 @@
+---
+id: 34002025-52f5-410c-ad48-25a27bbad197
+title: Week Form Type
+---
+
+# Description
+
+`WeekType` form field that allows users to modify data that represents a
+specific ISO 8601[^1] week number
+
+# Syntax
+
+``` php
+use Symfony\Component\Form\Extension\Core\Type\WeekType;
+
+$builder->add('startDateTime', WeekType::class, [
+ // use this if you store week numbers as strings ('2011-W17')
+ 'input' => 'string',
+ // use this if you store week numbers as arrays (e.g. [2011, 17])
+ 'input' => 'array',
+
+ // renders two