From 63cf640062de27b22cb0f5fdf3b58bb5b86d9da6 Mon Sep 17 00:00:00 2001 From: Diego Aguiar Date: Thu, 4 Feb 2021 17:25:25 -0700 Subject: [PATCH] Revert all unrelated commits to this project --- .env | 8 +- .gitignore | 1 - _tuts/README.md | 5 + _tuts/command-move-flush-out-of-loop.diff | 14 + _tuts/env-homepage-wall-time-assert.diff | 12 + .../env-move-scenario-to-blackfire-yaml.diff | 25 + _tuts/env-remove-comment.diff | 12 + _tuts/env-trying-comparison.diff | 15 + _tuts/env-using-variable.diff | 13 + ...first-profile-make-direct-count-query.diff | 35 + _tuts/first-profile-property-caching.diff | 29 + _tuts/first-profile-revert-prop-caching.diff | 26 + _tuts/first-profile-switch-to-prod.diff | 13 + _tuts/first-profile-use-proper-caching.diff | 42 + _tuts/metrics-blackfire-yaml-with-1-test.diff | 11 + _tuts/n-1-extra-lazy.diff | 13 + _tuts/n-1-revert-join.diff | 13 + _tuts/n-1-use-join.diff | 13 + _tuts/player-add-1-expect.diff | 14 + _tuts/player-add-1-scenario-2-pages.diff | 17 + _tuts/player-add-failing-expect.diff | 12 + _tuts/player-bootstrap-scenario-bkf.diff | 10 + _tuts/player-example-assert.diff | 13 + _tuts/player-fix-failing-expect.diff | 13 + _tuts/player-fix-typo.diff | 13 + _tuts/player-typo-a-method-name.diff | 13 + _tuts/sdk-add-configuration.diff | 24 + _tuts/sdk-add-priority.diff | 14 + _tuts/sdk-add-terminateevent.diff | 47 + .../.gitignore => _tuts/sdk-delete-me1.diff | 0 _tuts/sdk-disable-auto-profiling.diff | 14 + _tuts/sdk-install-it.diff | 178 + _tuts/sdk-make-subscriber.diff | 27 + _tuts/sdk-manually-create-probe.diff | 27 + _tuts/sdk-manually-enable-disable-probe.diff | 23 + _tuts/sdk-only-on-master-requests.diff | 15 + _tuts/sdk-refactor-to-subscriber.diff | 55 + _tuts/sdk-remove-enable-disable.diff | 24 + _tuts/sdk-showing-close.diff | 12 + _tuts/sfcloud-add-database.diff | 23 + _tuts/sfcloud-enable-bf-extension.diff | 13 + _tuts/sfcloud-project-init.diff | 69 + _tuts/sfcloud-tweak-php-version.diff | 12 + _tuts/steps.json | 260 + _tuts/test-add-test-assertion.diff | 36 + _tuts/test-blackfire-group.diff | 15 + _tuts/test-clean-up-original-method.diff | 25 + _tuts/test-fix-typo.diff | 13 + _tuts/test-refactor-into-separate-method.diff | 21 + _tuts/test-refactor-to-1-http-request.diff | 64 + _tuts/test-typo-count.diff | 13 + .../timeline-exit-earlier-in-subscriber.diff | 28 + _tuts/timeline-go-back-to-dev-env.diff | 13 + _tuts/timeline-use-service-subscriber.diff | 87 + assets/app.js | 12 - assets/bootstrap.js | 5 - assets/controllers.json | 4 - assets/controllers/hello_controller.js | 16 - assets/styles/app.css | 3 - bin/console | 13 +- composer.json | 24 +- composer.lock | 5378 ++++++----------- config/bundles.php | 1 + config/packages/cache.yaml | 6 +- config/packages/doctrine.yaml | 14 +- config/packages/doctrine_migrations.yaml | 8 +- config/packages/framework.yaml | 1 - config/packages/prod/deprecations.yaml | 8 - config/packages/prod/doctrine.yaml | 24 +- config/packages/prod/monolog.yaml | 9 +- config/packages/prod/routing.yaml | 3 - config/packages/routing.yaml | 5 +- config/packages/security.yaml | 5 +- config/packages/test/monolog.yaml | 7 +- config/packages/test/twig.yaml | 2 - config/packages/test/webpack_encore.yaml | 2 - config/packages/twig.yaml | 4 + config/packages/webpack_encore.yaml | 30 +- config/preload.php | 5 - config/routes/annotations.yaml | 4 - config/routes/dev/framework.yaml | 3 - config/routes/dev/twig.yaml | 3 + config/services.yaml | 10 +- package.json | 4 +- phpunit.xml.dist | 10 +- public/css/bigfoot.css | 20 - public/img/bigfoot.png | Bin 29863 -> 0 bytes public/index.php | 15 +- sfcasts/assertions.md | 72 + sfcasts/auto-profile.md | 77 + sfcasts/blackfire-cli.md | 81 + sfcasts/blackfire-player-expects.md | 149 + sfcasts/blackfire-player.md | 115 + sfcasts/build-basics.md | 112 + sfcasts/build-scenarios.md | 122 + sfcasts/cache-compare.md | 95 + sfcasts/call-graph.md | 133 + sfcasts/cloud-environments.md | 87 + sfcasts/command-line-scripts.md | 157 + sfcasts/comparison-build-tests.md | 146 + sfcasts/comparisons.md | 60 + sfcasts/custom-metrics.md | 91 + sfcasts/deploy.md | 108 + sfcasts/env-profile.md | 71 + sfcasts/environment-vars.md | 78 + sfcasts/environments.md | 127 + sfcasts/function-list.md | 123 + sfcasts/install.md | 137 + sfcasts/instantiation.md | 115 + sfcasts/intro.md | 93 + sfcasts/manual-instrumentation.md | 164 + sfcasts/metadata.yml | 39 +- sfcasts/metrics.md | 203 + sfcasts/n-plus-one-joins.md | 115 + sfcasts/n-plus-one.md | 104 + sfcasts/performance-tests.md | 114 + sfcasts/probe-create-subscriber.md | 157 + sfcasts/profile-all.md | 106 + sfcasts/property-caching.md | 144 + sfcasts/recommendations.md | 124 + sfcasts/service-subscriber.md | 120 + sfcasts/sf-cloud-database.md | 151 + sfcasts/staging-environments.md | 145 + sfcasts/the-pieces.md | 54 + sfcasts/timeline-surprise.md | 84 + sfcasts/timeline.md | 111 + src/DataFixtures/AppFixtures.php | 2 +- src/Kernel.php | 53 +- src/Migrations/.gitignore | 0 .../Migrations}/Version20190919165125.php | 0 .../Migrations}/Version20190919170420.php | 0 .../Migrations}/Version20190919170603.php | 0 .../Migrations}/Version20190919170845.php | 0 .../Migrations}/Version20190919171138.php | 0 .../Migrations}/Version20190919173458.php | 0 .../Migrations}/Version20190919174624.php | 0 .../Migrations}/Version20191010181756.php | 0 .../Migrations}/Version20191010184120.php | 0 src/Repository/BigFootSightingRepository.php | 2 +- symfony.lock | 123 +- tests/bootstrap.php | 11 - webpack.config.js | 32 +- 142 files changed, 8031 insertions(+), 3691 deletions(-) create mode 100644 _tuts/README.md create mode 100644 _tuts/command-move-flush-out-of-loop.diff create mode 100644 _tuts/env-homepage-wall-time-assert.diff create mode 100644 _tuts/env-move-scenario-to-blackfire-yaml.diff create mode 100644 _tuts/env-remove-comment.diff create mode 100644 _tuts/env-trying-comparison.diff create mode 100644 _tuts/env-using-variable.diff create mode 100644 _tuts/first-profile-make-direct-count-query.diff create mode 100644 _tuts/first-profile-property-caching.diff create mode 100644 _tuts/first-profile-revert-prop-caching.diff create mode 100644 _tuts/first-profile-switch-to-prod.diff create mode 100644 _tuts/first-profile-use-proper-caching.diff create mode 100644 _tuts/metrics-blackfire-yaml-with-1-test.diff create mode 100644 _tuts/n-1-extra-lazy.diff create mode 100644 _tuts/n-1-revert-join.diff create mode 100644 _tuts/n-1-use-join.diff create mode 100644 _tuts/player-add-1-expect.diff create mode 100644 _tuts/player-add-1-scenario-2-pages.diff create mode 100644 _tuts/player-add-failing-expect.diff create mode 100644 _tuts/player-bootstrap-scenario-bkf.diff create mode 100644 _tuts/player-example-assert.diff create mode 100644 _tuts/player-fix-failing-expect.diff create mode 100644 _tuts/player-fix-typo.diff create mode 100644 _tuts/player-typo-a-method-name.diff create mode 100644 _tuts/sdk-add-configuration.diff create mode 100644 _tuts/sdk-add-priority.diff create mode 100644 _tuts/sdk-add-terminateevent.diff rename migrations/.gitignore => _tuts/sdk-delete-me1.diff (100%) create mode 100644 _tuts/sdk-disable-auto-profiling.diff create mode 100644 _tuts/sdk-install-it.diff create mode 100644 _tuts/sdk-make-subscriber.diff create mode 100644 _tuts/sdk-manually-create-probe.diff create mode 100644 _tuts/sdk-manually-enable-disable-probe.diff create mode 100644 _tuts/sdk-only-on-master-requests.diff create mode 100644 _tuts/sdk-refactor-to-subscriber.diff create mode 100644 _tuts/sdk-remove-enable-disable.diff create mode 100644 _tuts/sdk-showing-close.diff create mode 100644 _tuts/sfcloud-add-database.diff create mode 100644 _tuts/sfcloud-enable-bf-extension.diff create mode 100644 _tuts/sfcloud-project-init.diff create mode 100644 _tuts/sfcloud-tweak-php-version.diff create mode 100644 _tuts/steps.json create mode 100644 _tuts/test-add-test-assertion.diff create mode 100644 _tuts/test-blackfire-group.diff create mode 100644 _tuts/test-clean-up-original-method.diff create mode 100644 _tuts/test-fix-typo.diff create mode 100644 _tuts/test-refactor-into-separate-method.diff create mode 100644 _tuts/test-refactor-to-1-http-request.diff create mode 100644 _tuts/test-typo-count.diff create mode 100644 _tuts/timeline-exit-earlier-in-subscriber.diff create mode 100644 _tuts/timeline-go-back-to-dev-env.diff create mode 100644 _tuts/timeline-use-service-subscriber.diff delete mode 100644 assets/app.js delete mode 100644 assets/bootstrap.js delete mode 100644 assets/controllers.json delete mode 100644 assets/controllers/hello_controller.js delete mode 100644 assets/styles/app.css delete mode 100644 config/packages/prod/deprecations.yaml delete mode 100644 config/packages/prod/routing.yaml delete mode 100644 config/packages/test/twig.yaml delete mode 100644 config/packages/test/webpack_encore.yaml delete mode 100644 config/preload.php delete mode 100644 config/routes/dev/framework.yaml create mode 100644 config/routes/dev/twig.yaml delete mode 100644 public/css/bigfoot.css delete mode 100644 public/img/bigfoot.png create mode 100644 sfcasts/assertions.md create mode 100644 sfcasts/auto-profile.md create mode 100644 sfcasts/blackfire-cli.md create mode 100644 sfcasts/blackfire-player-expects.md create mode 100644 sfcasts/blackfire-player.md create mode 100644 sfcasts/build-basics.md create mode 100644 sfcasts/build-scenarios.md create mode 100644 sfcasts/cache-compare.md create mode 100644 sfcasts/call-graph.md create mode 100644 sfcasts/cloud-environments.md create mode 100644 sfcasts/command-line-scripts.md create mode 100644 sfcasts/comparison-build-tests.md create mode 100644 sfcasts/comparisons.md create mode 100644 sfcasts/custom-metrics.md create mode 100644 sfcasts/deploy.md create mode 100644 sfcasts/env-profile.md create mode 100644 sfcasts/environment-vars.md create mode 100644 sfcasts/environments.md create mode 100644 sfcasts/function-list.md create mode 100644 sfcasts/install.md create mode 100644 sfcasts/instantiation.md create mode 100644 sfcasts/intro.md create mode 100644 sfcasts/manual-instrumentation.md create mode 100644 sfcasts/metrics.md create mode 100644 sfcasts/n-plus-one-joins.md create mode 100644 sfcasts/n-plus-one.md create mode 100644 sfcasts/performance-tests.md create mode 100644 sfcasts/probe-create-subscriber.md create mode 100644 sfcasts/profile-all.md create mode 100644 sfcasts/property-caching.md create mode 100644 sfcasts/recommendations.md create mode 100644 sfcasts/service-subscriber.md create mode 100644 sfcasts/sf-cloud-database.md create mode 100644 sfcasts/staging-environments.md create mode 100644 sfcasts/the-pieces.md create mode 100644 sfcasts/timeline-surprise.md create mode 100644 sfcasts/timeline.md create mode 100644 src/Migrations/.gitignore rename {migrations => src/Migrations}/Version20190919165125.php (100%) rename {migrations => src/Migrations}/Version20190919170420.php (100%) rename {migrations => src/Migrations}/Version20190919170603.php (100%) rename {migrations => src/Migrations}/Version20190919170845.php (100%) rename {migrations => src/Migrations}/Version20190919171138.php (100%) rename {migrations => src/Migrations}/Version20190919173458.php (100%) rename {migrations => src/Migrations}/Version20190919174624.php (100%) rename {migrations => src/Migrations}/Version20191010181756.php (100%) rename {migrations => src/Migrations}/Version20191010184120.php (100%) delete mode 100644 tests/bootstrap.php diff --git a/.env b/.env index 8a59212..1f68332 100644 --- a/.env +++ b/.env @@ -22,9 +22,7 @@ APP_SECRET=77edcf2d21c9bb9eb67233d29c618543 ###> doctrine/doctrine-bundle ### # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url -# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml -# -# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" -# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" -DATABASE_URL=mysql://root:@127.0.0.1:3306/solid?serverVersion=5.7 +# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" +# Configure your db driver and server_version in config/packages/doctrine.yaml +DATABASE_URL=mysql://root:@127.0.0.1:3306/blackfire ###< doctrine/doctrine-bundle ### diff --git a/.gitignore b/.gitignore index 97a35da..3ceef2d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ /.env.local /.env.local.php /.env.*.local -/config/secrets/prod/prod.decrypt.private.php /public/bundles/ /var/ /vendor/ diff --git a/_tuts/README.md b/_tuts/README.md new file mode 100644 index 0000000..5d0079d --- /dev/null +++ b/_tuts/README.md @@ -0,0 +1,5 @@ +# Hello there! + +The files in this directory cannot be modified directly: we use an internal tool +to manage them. If you find an issue with the code, you can open an issue on the +repository. In fact, that would be awesome :). diff --git a/_tuts/command-move-flush-out-of-loop.diff b/_tuts/command-move-flush-out-of-loop.diff new file mode 100644 index 0000000..ff99607 --- /dev/null +++ b/_tuts/command-move-flush-out-of-loop.diff @@ -0,0 +1,14 @@ +diff --git a/src/Command/UpdateSightingScoresCommand.php b/src/Command/UpdateSightingScoresCommand.php +index 163c167..d279548 100644 +--- a/src/Command/UpdateSightingScoresCommand.php ++++ b/src/Command/UpdateSightingScoresCommand.php +@@ -46,8 +46,8 @@ class UpdateSightingScoresCommand extends Command + + $score = ceil(min($characterCount / 500, 10)); + $sighting->setScore($score); +- $this->entityManager->flush(); + } ++ $this->entityManager->flush(); + $io->progressFinish(); + } + } diff --git a/_tuts/env-homepage-wall-time-assert.diff b/_tuts/env-homepage-wall-time-assert.diff new file mode 100644 index 0000000..d7e9a6e --- /dev/null +++ b/_tuts/env-homepage-wall-time-assert.diff @@ -0,0 +1,12 @@ +diff --git a/.blackfire.yaml b/.blackfire.yaml +index 2aeea6b..169cbf4 100644 +--- a/.blackfire.yaml ++++ b/.blackfire.yaml +@@ -15,6 +15,7 @@ scenarios: | + expect status_code() == 200 + expect css("tbody.js-sightings-list tr").count() > 10 + assert metrics.sql.queries.count < 30 ++ assert main.wall_time < 100ms + + click link("Log In") + name "Log in page" diff --git a/_tuts/env-move-scenario-to-blackfire-yaml.diff b/_tuts/env-move-scenario-to-blackfire-yaml.diff new file mode 100644 index 0000000..538b8e7 --- /dev/null +++ b/_tuts/env-move-scenario-to-blackfire-yaml.diff @@ -0,0 +1,25 @@ +diff --git a/.blackfire.yaml b/.blackfire.yaml +index 8a2bd73..7f465f2 100644 +--- a/.blackfire.yaml ++++ b/.blackfire.yaml +@@ -3,3 +3,20 @@ + path: "/.*" + assertions: + - "metrics.http.requests.count <= 1" ++ ++scenarios: | ++ #!blackfire-player ++ ++ scenario ++ name "Basic Visit" ++ ++ visit url('/') ++ name "Homepage" ++ expect status_code() == 200 ++ expect css("tbody.js-sightings-list tr").count() > 10 ++ # won't work until we're using Blackfire environment ++ assert metrics.sql.queries.count < 30 ++ ++ click link("Log In") ++ name "Log in page" ++ expect status_code() == 200 diff --git a/_tuts/env-remove-comment.diff b/_tuts/env-remove-comment.diff new file mode 100644 index 0000000..46e178a --- /dev/null +++ b/_tuts/env-remove-comment.diff @@ -0,0 +1,12 @@ +diff --git a/.blackfire.yaml b/.blackfire.yaml +index 7f465f2..2aeea6b 100644 +--- a/.blackfire.yaml ++++ b/.blackfire.yaml +@@ -14,7 +14,6 @@ scenarios: | + name "Homepage" + expect status_code() == 200 + expect css("tbody.js-sightings-list tr").count() > 10 +- # won't work until we're using Blackfire environment + assert metrics.sql.queries.count < 30 + + click link("Log In") diff --git a/_tuts/env-trying-comparison.diff b/_tuts/env-trying-comparison.diff new file mode 100644 index 0000000..73f882c --- /dev/null +++ b/_tuts/env-trying-comparison.diff @@ -0,0 +1,15 @@ +diff --git a/.blackfire.yaml b/.blackfire.yaml +index 169cbf4..be4ff48 100644 +--- a/.blackfire.yaml ++++ b/.blackfire.yaml +@@ -3,6 +3,10 @@ + path: "/.*" + assertions: + - "metrics.http.requests.count <= 1" ++ "Pages are not suddenly *much* slower": ++ path: "/.*" ++ assertions: ++ - "percent(main.wall_time) < 30%" + + scenarios: | + #!blackfire-player diff --git a/_tuts/env-using-variable.diff b/_tuts/env-using-variable.diff new file mode 100644 index 0000000..73bccfc --- /dev/null +++ b/_tuts/env-using-variable.diff @@ -0,0 +1,13 @@ +diff --git a/.blackfire.yaml b/.blackfire.yaml +index be4ff48..21a6d91 100644 +--- a/.blackfire.yaml ++++ b/.blackfire.yaml +@@ -19,7 +19,7 @@ scenarios: | + expect status_code() == 200 + expect css("tbody.js-sightings-list tr").count() > 10 + assert metrics.sql.queries.count < 30 +- assert main.wall_time < 100ms ++ assert main.wall_time < (100ms * var('speed_coefficient', 1)) + + click link("Log In") + name "Log in page" diff --git a/_tuts/first-profile-make-direct-count-query.diff b/_tuts/first-profile-make-direct-count-query.diff new file mode 100644 index 0000000..8cff237 --- /dev/null +++ b/_tuts/first-profile-make-direct-count-query.diff @@ -0,0 +1,35 @@ +diff --git a/src/Service/CommentHelper.php b/src/Service/CommentHelper.php +index 7a96a4f..f2bff11 100644 +--- a/src/Service/CommentHelper.php ++++ b/src/Service/CommentHelper.php +@@ -3,20 +3,20 @@ + namespace App\Service; + + use App\Entity\User; ++use App\Repository\CommentRepository; + + class CommentHelper + { +- public function countRecentCommentsForUser(User $user): int ++ private $commentRepository; ++ ++ public function __construct(CommentRepository $commentRepository) + { +- $comments = $user->getComments(); +- $commentCount = 0; +- $recentDate = new \DateTimeImmutable('-3 months'); +- foreach ($comments as $comment) { +- if ($comment->getCreatedAt() > $recentDate) { +- $commentCount++; +- } +- } ++ $this->commentRepository = $commentRepository; ++ } + +- return $commentCount; ++ public function countRecentCommentsForUser(User $user): int ++ { ++ return $this->commentRepository ++ ->countForUser($user, new \DateTimeImmutable('-3 months')); + } + } diff --git a/_tuts/first-profile-property-caching.diff b/_tuts/first-profile-property-caching.diff new file mode 100644 index 0000000..416a6db --- /dev/null +++ b/_tuts/first-profile-property-caching.diff @@ -0,0 +1,29 @@ +diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php +index 2cd5e63..7be2947 100644 +--- a/src/Twig/AppExtension.php ++++ b/src/Twig/AppExtension.php +@@ -12,6 +12,8 @@ class AppExtension extends AbstractExtension + { + private $commentHelper; + ++ private $userStatuses = []; ++ + public function __construct(CommentHelper $commentHelper) + { + $this->commentHelper = $commentHelper; +@@ -25,6 +27,15 @@ class AppExtension extends AbstractExtension + } + + public function getUserActivityText(User $user): string ++ { ++ if (!isset($this->userStatuses[$user->getId()])) { ++ $this->userStatuses[$user->getId()] = $this->calculateUserActivityText($user); ++ } ++ ++ return $this->userStatuses[$user->getId()]; ++ } ++ ++ private function calculateUserActivityText(User $user): string + { + $commentCount = $this->commentHelper->countRecentCommentsForUser($user); + diff --git a/_tuts/first-profile-revert-prop-caching.diff b/_tuts/first-profile-revert-prop-caching.diff new file mode 100644 index 0000000..7717179 --- /dev/null +++ b/_tuts/first-profile-revert-prop-caching.diff @@ -0,0 +1,26 @@ +diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php +index 7be2947..4a18185 100644 +--- a/src/Twig/AppExtension.php ++++ b/src/Twig/AppExtension.php +@@ -12,8 +12,6 @@ class AppExtension extends AbstractExtension + { + private $commentHelper; + +- private $userStatuses = []; +- + public function __construct(CommentHelper $commentHelper) + { + $this->commentHelper = $commentHelper; +@@ -28,11 +26,7 @@ class AppExtension extends AbstractExtension + + public function getUserActivityText(User $user): string + { +- if (!isset($this->userStatuses[$user->getId()])) { +- $this->userStatuses[$user->getId()] = $this->calculateUserActivityText($user); +- } +- +- return $this->userStatuses[$user->getId()]; ++ return $this->calculateUserActivityText($user); + } + + private function calculateUserActivityText(User $user): string diff --git a/_tuts/first-profile-switch-to-prod.diff b/_tuts/first-profile-switch-to-prod.diff new file mode 100644 index 0000000..0c5a9fb --- /dev/null +++ b/_tuts/first-profile-switch-to-prod.diff @@ -0,0 +1,13 @@ +diff --git a/.env b/.env +index 1f68332..ac88cd0 100644 +--- a/.env ++++ b/.env +@@ -14,7 +14,7 @@ + # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration + + ###> symfony/framework-bundle ### +-APP_ENV=dev ++APP_ENV=prod + APP_SECRET=77edcf2d21c9bb9eb67233d29c618543 + #TRUSTED_PROXIES=127.0.0.1,127.0.0.2 + #TRUSTED_HOSTS='^localhost|example\.com$' diff --git a/_tuts/first-profile-use-proper-caching.diff b/_tuts/first-profile-use-proper-caching.diff new file mode 100644 index 0000000..e2554e7 --- /dev/null +++ b/_tuts/first-profile-use-proper-caching.diff @@ -0,0 +1,42 @@ +diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php +index 4a18185..eb73e33 100644 +--- a/src/Twig/AppExtension.php ++++ b/src/Twig/AppExtension.php +@@ -4,6 +4,8 @@ namespace App\Twig; + + use App\Entity\User; + use App\Service\CommentHelper; ++use Psr\Cache\CacheItemInterface; ++use Symfony\Contracts\Cache\CacheInterface; + use Twig\Extension\AbstractExtension; + use Twig\TwigFilter; + use Twig\TwigFunction; +@@ -11,10 +13,12 @@ use Twig\TwigFunction; + class AppExtension extends AbstractExtension + { + private $commentHelper; ++ private $cache; + +- public function __construct(CommentHelper $commentHelper) ++ public function __construct(CommentHelper $commentHelper, CacheInterface $cache) + { + $this->commentHelper = $commentHelper; ++ $this->cache = $cache; + } + + public function getFilters(): array +@@ -26,7 +30,13 @@ class AppExtension extends AbstractExtension + + public function getUserActivityText(User $user): string + { +- return $this->calculateUserActivityText($user); ++ $key = sprintf('user_activity_text_'.$user->getId()); ++ ++ return $this->cache->get($key, function(CacheItemInterface $item) use ($user) { ++ $item->expiresAfter(3600); ++ ++ return $this->calculateUserActivityText($user); ++ }); + } + + private function calculateUserActivityText(User $user): string diff --git a/_tuts/metrics-blackfire-yaml-with-1-test.diff b/_tuts/metrics-blackfire-yaml-with-1-test.diff new file mode 100644 index 0000000..614ee6b --- /dev/null +++ b/_tuts/metrics-blackfire-yaml-with-1-test.diff @@ -0,0 +1,11 @@ +diff --git a/.blackfire.yaml b/.blackfire.yaml +new file mode 100644 +index 0000000..8a2bd73 +--- /dev/null ++++ b/.blackfire.yaml +@@ -0,0 +1,5 @@ ++"tests": ++ "HTTP Requests should be limited to 1 per page": ++ path: "/.*" ++ assertions: ++ - "metrics.http.requests.count <= 1" diff --git a/_tuts/n-1-extra-lazy.diff b/_tuts/n-1-extra-lazy.diff new file mode 100644 index 0000000..671cabd --- /dev/null +++ b/_tuts/n-1-extra-lazy.diff @@ -0,0 +1,13 @@ +diff --git a/src/Entity/BigFootSighting.php b/src/Entity/BigFootSighting.php +index 1e1f5eb..daf6cbf 100644 +--- a/src/Entity/BigFootSighting.php ++++ b/src/Entity/BigFootSighting.php +@@ -55,7 +55,7 @@ class BigFootSighting + private $createdAt; + + /** +- * @ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="bigFootSighting") ++ * @ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="bigFootSighting", fetch="EXTRA_LAZY") + * @ORM\OrderBy({"createdAt"="DESC"}) + */ + private $comments; diff --git a/_tuts/n-1-revert-join.diff b/_tuts/n-1-revert-join.diff new file mode 100644 index 0000000..5716f68 --- /dev/null +++ b/_tuts/n-1-revert-join.diff @@ -0,0 +1,13 @@ +diff --git a/src/Repository/BigFootSightingRepository.php b/src/Repository/BigFootSightingRepository.php +index 7aa4416..d0a075a 100644 +--- a/src/Repository/BigFootSightingRepository.php ++++ b/src/Repository/BigFootSightingRepository.php +@@ -23,8 +23,6 @@ class BigFootSightingRepository extends ServiceEntityRepository + public function findLatestQueryBuilder(int $maxResults): QueryBuilder + { + return $this->createQueryBuilder('big_foot_sighting') +- ->leftJoin('big_foot_sighting.comments', 'comments') +- ->addSelect('comments') + ->setMaxResults($maxResults) + ->orderBy('big_foot_sighting.createdAt', 'DESC'); + } diff --git a/_tuts/n-1-use-join.diff b/_tuts/n-1-use-join.diff new file mode 100644 index 0000000..4f67b1a --- /dev/null +++ b/_tuts/n-1-use-join.diff @@ -0,0 +1,13 @@ +diff --git a/src/Repository/BigFootSightingRepository.php b/src/Repository/BigFootSightingRepository.php +index d0a075a..7aa4416 100644 +--- a/src/Repository/BigFootSightingRepository.php ++++ b/src/Repository/BigFootSightingRepository.php +@@ -23,6 +23,8 @@ class BigFootSightingRepository extends ServiceEntityRepository + public function findLatestQueryBuilder(int $maxResults): QueryBuilder + { + return $this->createQueryBuilder('big_foot_sighting') ++ ->leftJoin('big_foot_sighting.comments', 'comments') ++ ->addSelect('comments') + ->setMaxResults($maxResults) + ->orderBy('big_foot_sighting.createdAt', 'DESC'); + } diff --git a/_tuts/player-add-1-expect.diff b/_tuts/player-add-1-expect.diff new file mode 100644 index 0000000..378a2e6 --- /dev/null +++ b/_tuts/player-add-1-expect.diff @@ -0,0 +1,14 @@ +diff --git a/scenario.bkf b/scenario.bkf +index d2a095c..68a9636 100644 +--- a/scenario.bkf ++++ b/scenario.bkf +@@ -8,6 +8,9 @@ scenario + + visit url("/") + name "Homepage" ++ expect status_code() == 200 + + click link("Log In") + name "Login page" ++ expect status_code() == 200 ++ diff --git a/_tuts/player-add-1-scenario-2-pages.diff b/_tuts/player-add-1-scenario-2-pages.diff new file mode 100644 index 0000000..5d97d33 --- /dev/null +++ b/_tuts/player-add-1-scenario-2-pages.diff @@ -0,0 +1,17 @@ +diff --git a/scenario.bkf b/scenario.bkf +index feca60f..d2a095c 100644 +--- a/scenario.bkf ++++ b/scenario.bkf +@@ -2,3 +2,12 @@ name "Various scenarios for the site" + + # override with --endpoint option + endpoint "https://localhost:8000" ++ ++scenario ++ name "Basic Visit" ++ ++ visit url("/") ++ name "Homepage" ++ ++ click link("Log In") ++ name "Login page" diff --git a/_tuts/player-add-failing-expect.diff b/_tuts/player-add-failing-expect.diff new file mode 100644 index 0000000..cb5dd3b --- /dev/null +++ b/_tuts/player-add-failing-expect.diff @@ -0,0 +1,12 @@ +diff --git a/scenario.bkf b/scenario.bkf +index 68a9636..5eb1447 100644 +--- a/scenario.bkf ++++ b/scenario.bkf +@@ -9,6 +9,7 @@ scenario + visit url("/") + name "Homepage" + expect status_code() == 200 ++ expect css("tbody.js-sightings-list tr").count() > 500 + + click link("Log In") + name "Login page" diff --git a/_tuts/player-bootstrap-scenario-bkf.diff b/_tuts/player-bootstrap-scenario-bkf.diff new file mode 100644 index 0000000..9df9d35 --- /dev/null +++ b/_tuts/player-bootstrap-scenario-bkf.diff @@ -0,0 +1,10 @@ +diff --git a/scenario.bkf b/scenario.bkf +new file mode 100644 +index 0000000..feca60f +--- /dev/null ++++ b/scenario.bkf +@@ -0,0 +1,4 @@ ++name "Various scenarios for the site" ++ ++# override with --endpoint option ++endpoint "https://localhost:8000" diff --git a/_tuts/player-example-assert.diff b/_tuts/player-example-assert.diff new file mode 100644 index 0000000..0f3816b --- /dev/null +++ b/_tuts/player-example-assert.diff @@ -0,0 +1,13 @@ +diff --git a/scenario.bkf b/scenario.bkf +index 92be148..8a58fb0 100644 +--- a/scenario.bkf ++++ b/scenario.bkf +@@ -10,6 +10,8 @@ scenario + name "Homepage" + expect status_code() == 200 + expect css("tbody.js-sightings-list tr").count() > 10 ++ # won't work until we're using Blackfire environment ++ assert metrics.sql.queries.count < 30 + + click link("Log In") + name "Login page" diff --git a/_tuts/player-fix-failing-expect.diff b/_tuts/player-fix-failing-expect.diff new file mode 100644 index 0000000..db1b0c9 --- /dev/null +++ b/_tuts/player-fix-failing-expect.diff @@ -0,0 +1,13 @@ +diff --git a/scenario.bkf b/scenario.bkf +index 5eb1447..92be148 100644 +--- a/scenario.bkf ++++ b/scenario.bkf +@@ -9,7 +9,7 @@ scenario + visit url("/") + name "Homepage" + expect status_code() == 200 +- expect css("tbody.js-sightings-list tr").count() > 500 ++ expect css("tbody.js-sightings-list tr").count() > 10 + + click link("Log In") + name "Login page" diff --git a/_tuts/player-fix-typo.diff b/_tuts/player-fix-typo.diff new file mode 100644 index 0000000..26aa4a3 --- /dev/null +++ b/_tuts/player-fix-typo.diff @@ -0,0 +1,13 @@ +diff --git a/scenario.bkf b/scenario.bkf +index 92c4244..92be148 100644 +--- a/scenario.bkf ++++ b/scenario.bkf +@@ -9,7 +9,7 @@ scenario + visit url("/") + name "Homepage" + expect status_code() == 200 +- expect css("tbody.js-sightings-list tr").ount() > 10 ++ expect css("tbody.js-sightings-list tr").count() > 10 + + click link("Log In") + name "Login page" diff --git a/_tuts/player-typo-a-method-name.diff b/_tuts/player-typo-a-method-name.diff new file mode 100644 index 0000000..bafebfe --- /dev/null +++ b/_tuts/player-typo-a-method-name.diff @@ -0,0 +1,13 @@ +diff --git a/scenario.bkf b/scenario.bkf +index 92be148..92c4244 100644 +--- a/scenario.bkf ++++ b/scenario.bkf +@@ -9,7 +9,7 @@ scenario + visit url("/") + name "Homepage" + expect status_code() == 200 +- expect css("tbody.js-sightings-list tr").count() > 10 ++ expect css("tbody.js-sightings-list tr").ount() > 10 + + click link("Log In") + name "Login page" diff --git a/_tuts/sdk-add-configuration.diff b/_tuts/sdk-add-configuration.diff new file mode 100644 index 0000000..b4b306a --- /dev/null +++ b/_tuts/sdk-add-configuration.diff @@ -0,0 +1,24 @@ +diff --git a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +index fb20634..ce68ff0 100644 +--- a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php ++++ b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +@@ -4,6 +4,7 @@ namespace App\EventSubscriber; + + use Blackfire\Client; + use Blackfire\Probe; ++use Blackfire\Profile\Configuration; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Symfony\Component\HttpKernel\Event\RequestEvent; + use Symfony\Component\HttpKernel\Event\TerminateEvent; +@@ -26,8 +27,10 @@ class BlackfireAutoProfileSubscriber implements EventSubscriberInterface + $shouldProfile = $request->getPathInfo() === '/api/github-organization'; + + if ($shouldProfile) { ++ $configuration = new Configuration(); ++ $configuration->setTitle('Automatic GitHub org profile'); + $blackfire = new Client(); +- $this->probe = $blackfire->createProbe(); ++ $this->probe = $blackfire->createProbe($configuration); + } + } + diff --git a/_tuts/sdk-add-priority.diff b/_tuts/sdk-add-priority.diff new file mode 100644 index 0000000..19316f8 --- /dev/null +++ b/_tuts/sdk-add-priority.diff @@ -0,0 +1,14 @@ +diff --git a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +index 4b731a5..fb20634 100644 +--- a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php ++++ b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +@@ -41,7 +41,8 @@ class BlackfireAutoProfileSubscriber implements EventSubscriberInterface + public static function getSubscribedEvents() + { + return [ +- RequestEvent::class => 'onRequestEvent', ++ // warning: adding a priority will run before routing & security ++ RequestEvent::class => ['onRequestEvent', 1000], + TerminateEvent::class => 'onTerminateEvent', + ]; + } diff --git a/_tuts/sdk-add-terminateevent.diff b/_tuts/sdk-add-terminateevent.diff new file mode 100644 index 0000000..5e6a13f --- /dev/null +++ b/_tuts/sdk-add-terminateevent.diff @@ -0,0 +1,47 @@ +diff --git a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +index 5b0078a..4b731a5 100644 +--- a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php ++++ b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +@@ -3,11 +3,18 @@ + namespace App\EventSubscriber; + + use Blackfire\Client; ++use Blackfire\Probe; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Symfony\Component\HttpKernel\Event\RequestEvent; ++use Symfony\Component\HttpKernel\Event\TerminateEvent; + + class BlackfireAutoProfileSubscriber implements EventSubscriberInterface + { ++ /** ++ * @var Probe|null ++ */ ++ private $probe; ++ + public function onRequestEvent(RequestEvent $event) + { + if (!$event->isMasterRequest()) { +@@ -20,7 +27,14 @@ class BlackfireAutoProfileSubscriber implements EventSubscriberInterface + + if ($shouldProfile) { + $blackfire = new Client(); +- $probe = $blackfire->createProbe(); ++ $this->probe = $blackfire->createProbe(); ++ } ++ } ++ ++ public function onTerminateEvent(TerminateEvent $event) ++ { ++ if ($this->probe) { ++ $this->probe->close(); + } + } + +@@ -28,6 +42,7 @@ class BlackfireAutoProfileSubscriber implements EventSubscriberInterface + { + return [ + RequestEvent::class => 'onRequestEvent', ++ TerminateEvent::class => 'onTerminateEvent', + ]; + } + } diff --git a/migrations/.gitignore b/_tuts/sdk-delete-me1.diff similarity index 100% rename from migrations/.gitignore rename to _tuts/sdk-delete-me1.diff diff --git a/_tuts/sdk-disable-auto-profiling.diff b/_tuts/sdk-disable-auto-profiling.diff new file mode 100644 index 0000000..7318a33 --- /dev/null +++ b/_tuts/sdk-disable-auto-profiling.diff @@ -0,0 +1,14 @@ +diff --git a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +index ce68ff0..043eb12 100644 +--- a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php ++++ b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +@@ -26,6 +26,9 @@ class BlackfireAutoProfileSubscriber implements EventSubscriberInterface + $request = $event->getRequest(); + $shouldProfile = $request->getPathInfo() === '/api/github-organization'; + ++ // stop our testing code from profiling ++ $shouldProfile = false; ++ + if ($shouldProfile) { + $configuration = new Configuration(); + $configuration->setTitle('Automatic GitHub org profile'); diff --git a/_tuts/sdk-install-it.diff b/_tuts/sdk-install-it.diff new file mode 100644 index 0000000..39afc75 --- /dev/null +++ b/_tuts/sdk-install-it.diff @@ -0,0 +1,178 @@ +diff --git a/composer.json b/composer.json +index 7519706..1130455 100644 +--- a/composer.json ++++ b/composer.json +@@ -5,6 +5,7 @@ + "php": "^7.1.3", + "ext-ctype": "*", + "ext-iconv": "*", ++ "blackfire/php-sdk": "^1.20", + "composer/package-versions-deprecated": "^1.11", + "sensio/framework-extra-bundle": "^5.4", + "symfony/console": "4.3.*", +diff --git a/composer.lock b/composer.lock +index a08121d..ff05bc9 100644 +--- a/composer.lock ++++ b/composer.lock +@@ -4,8 +4,145 @@ + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], +- "content-hash": "0d5cee3803219f1246f5b00b95e71ea0", ++ "content-hash": "76e41e6bf1b27db2c5e036b5fea394c7", + "packages": [ ++ { ++ "name": "blackfire/php-sdk", ++ "version": "v1.20.0", ++ "source": { ++ "type": "git", ++ "url": "https://github.com/blackfireio/php-sdk.git", ++ "reference": "1f8d72554fe714c73bed5117b177a0664d7b23fb" ++ }, ++ "dist": { ++ "type": "zip", ++ "url": "https://api.github.com/repos/blackfireio/php-sdk/zipball/1f8d72554fe714c73bed5117b177a0664d7b23fb", ++ "reference": "1f8d72554fe714c73bed5117b177a0664d7b23fb", ++ "shasum": "" ++ }, ++ "require": { ++ "composer/ca-bundle": "^1.0", ++ "php": ">=5.2.0" ++ }, ++ "require-dev": { ++ "symfony/http-client": "^4.3" ++ }, ++ "suggest": { ++ "ext-blackfire": "The C version of the Blackfire probe", ++ "ext-zlib": "To push config to remote profiling targets" ++ }, ++ "type": "library", ++ "extra": { ++ "branch-alias": { ++ "dev-master": "1.5.x-dev" ++ } ++ }, ++ "autoload": { ++ "files": [ ++ "src/autostart.php" ++ ], ++ "psr-4": { ++ "Blackfire\\": "src/Blackfire" ++ } ++ }, ++ "notification-url": "https://packagist.org/downloads/", ++ "license": [ ++ "MIT" ++ ], ++ "authors": [ ++ { ++ "name": "Blackfire.io", ++ "email": "support@blackfire.io" ++ } ++ ], ++ "description": "Blackfire.io PHP SDK", ++ "keywords": [ ++ "performance", ++ "profiler", ++ "uprofiler", ++ "xhprof" ++ ], ++ "support": { ++ "issues": "https://github.com/blackfireio/php-sdk/issues", ++ "source": "https://github.com/blackfireio/php-sdk/tree/master" ++ }, ++ "time": "2019-11-18T17:30:47+00:00" ++ }, ++ { ++ "name": "composer/ca-bundle", ++ "version": "1.2.8", ++ "source": { ++ "type": "git", ++ "url": "https://github.com/composer/ca-bundle.git", ++ "reference": "8a7ecad675253e4654ea05505233285377405215" ++ }, ++ "dist": { ++ "type": "zip", ++ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8a7ecad675253e4654ea05505233285377405215", ++ "reference": "8a7ecad675253e4654ea05505233285377405215", ++ "shasum": "" ++ }, ++ "require": { ++ "ext-openssl": "*", ++ "ext-pcre": "*", ++ "php": "^5.3.2 || ^7.0 || ^8.0" ++ }, ++ "require-dev": { ++ "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", ++ "psr/log": "^1.0", ++ "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" ++ }, ++ "type": "library", ++ "extra": { ++ "branch-alias": { ++ "dev-master": "1.x-dev" ++ } ++ }, ++ "autoload": { ++ "psr-4": { ++ "Composer\\CaBundle\\": "src" ++ } ++ }, ++ "notification-url": "https://packagist.org/downloads/", ++ "license": [ ++ "MIT" ++ ], ++ "authors": [ ++ { ++ "name": "Jordi Boggiano", ++ "email": "j.boggiano@seld.be", ++ "homepage": "http://seld.be" ++ } ++ ], ++ "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", ++ "keywords": [ ++ "cabundle", ++ "cacert", ++ "certificate", ++ "ssl", ++ "tls" ++ ], ++ "support": { ++ "irc": "irc://irc.freenode.org/composer", ++ "issues": "https://github.com/composer/ca-bundle/issues", ++ "source": "https://github.com/composer/ca-bundle/tree/1.2.8" ++ }, ++ "funding": [ ++ { ++ "url": "https://packagist.com", ++ "type": "custom" ++ }, ++ { ++ "url": "https://github.com/composer", ++ "type": "github" ++ }, ++ { ++ "url": "https://tidelift.com/funding/github/packagist/composer/composer", ++ "type": "tidelift" ++ } ++ ], ++ "time": "2020-08-23T12:54:47+00:00" ++ }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99", +diff --git a/symfony.lock b/symfony.lock +index 8c9f13d..e7d8321 100644 +--- a/symfony.lock ++++ b/symfony.lock +@@ -1,4 +1,10 @@ + { ++ "blackfire/php-sdk": { ++ "version": "v1.20.0" ++ }, ++ "composer/ca-bundle": { ++ "version": "1.2.4" ++ }, + "doctrine/annotations": { + "version": "1.0", + "recipe": { diff --git a/_tuts/sdk-make-subscriber.diff b/_tuts/sdk-make-subscriber.diff new file mode 100644 index 0000000..d92d4c4 --- /dev/null +++ b/_tuts/sdk-make-subscriber.diff @@ -0,0 +1,27 @@ +diff --git a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +new file mode 100644 +index 0000000..70ecaa0 +--- /dev/null ++++ b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +@@ -0,0 +1,21 @@ ++ 'onRequestEvent', ++ ]; ++ } ++} diff --git a/_tuts/sdk-manually-create-probe.diff b/_tuts/sdk-manually-create-probe.diff new file mode 100644 index 0000000..4306ac4 --- /dev/null +++ b/_tuts/sdk-manually-create-probe.diff @@ -0,0 +1,27 @@ +diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php +index da05ffd..4bcc5a1 100644 +--- a/src/Controller/MainController.php ++++ b/src/Controller/MainController.php +@@ -7,6 +7,7 @@ use App\Entity\User; + use App\Form\AgreeToUpdatedTermsFormType; + use App\GitHub\GitHubApiHelper; + use App\Repository\BigFootSightingRepository; ++use Blackfire\Client; + use Doctrine\ORM\EntityManagerInterface; + use Doctrine\ORM\Tools\Pagination\Paginator; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; +@@ -54,6 +55,14 @@ class MainController extends AbstractController + */ + public function gitHubOrganizationInfo(GitHubApiHelper $apiHelper) + { ++ // replace with some conditional logic ++ $shouldProfile = true; ++ ++ if ($shouldProfile) { ++ $blackfire = new Client(); ++ $probe = $blackfire->createProbe(); ++ } ++ + $organizationName = 'SymfonyCasts'; + $organization = $apiHelper->getOrganizationInfo($organizationName); + $repositories = $apiHelper->getOrganizationRepositories($organizationName); diff --git a/_tuts/sdk-manually-enable-disable-probe.diff b/_tuts/sdk-manually-enable-disable-probe.diff new file mode 100644 index 0000000..5da0ad3 --- /dev/null +++ b/_tuts/sdk-manually-enable-disable-probe.diff @@ -0,0 +1,23 @@ +diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php +index da05ffd..ea32e9e 100644 +--- a/src/Controller/MainController.php ++++ b/src/Controller/MainController.php +@@ -21,11 +21,17 @@ class MainController extends AbstractController + */ + public function homepage(BigFootSightingRepository $bigFootSightingRepository) + { ++ $probe = \BlackfireProbe::getMainInstance(); ++ $probe->enable(); + $sightings = $this->createSightingsPaginator(1, $bigFootSightingRepository); + +- return $this->render('main/homepage.html.twig', [ ++ $response = $this->render('main/homepage.html.twig', [ + 'sightings' => $sightings + ]); ++ ++ $probe->disable(); ++ ++ return $response; + } + + /** diff --git a/_tuts/sdk-only-on-master-requests.diff b/_tuts/sdk-only-on-master-requests.diff new file mode 100644 index 0000000..f01e02c --- /dev/null +++ b/_tuts/sdk-only-on-master-requests.diff @@ -0,0 +1,15 @@ +diff --git a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +index e6df6e7..5b0078a 100644 +--- a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php ++++ b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +@@ -10,6 +10,10 @@ class BlackfireAutoProfileSubscriber implements EventSubscriberInterface + { + public function onRequestEvent(RequestEvent $event) + { ++ if (!$event->isMasterRequest()) { ++ return; ++ } ++ + // replace with some conditional logic + $request = $event->getRequest(); + $shouldProfile = $request->getPathInfo() === '/api/github-organization'; diff --git a/_tuts/sdk-refactor-to-subscriber.diff b/_tuts/sdk-refactor-to-subscriber.diff new file mode 100644 index 0000000..54c79f9 --- /dev/null +++ b/_tuts/sdk-refactor-to-subscriber.diff @@ -0,0 +1,55 @@ +diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php +index 4bcc5a1..da05ffd 100644 +--- a/src/Controller/MainController.php ++++ b/src/Controller/MainController.php +@@ -7,7 +7,6 @@ use App\Entity\User; + use App\Form\AgreeToUpdatedTermsFormType; + use App\GitHub\GitHubApiHelper; + use App\Repository\BigFootSightingRepository; +-use Blackfire\Client; + use Doctrine\ORM\EntityManagerInterface; + use Doctrine\ORM\Tools\Pagination\Paginator; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; +@@ -55,14 +54,6 @@ class MainController extends AbstractController + */ + public function gitHubOrganizationInfo(GitHubApiHelper $apiHelper) + { +- // replace with some conditional logic +- $shouldProfile = true; +- +- if ($shouldProfile) { +- $blackfire = new Client(); +- $probe = $blackfire->createProbe(); +- } +- + $organizationName = 'SymfonyCasts'; + $organization = $apiHelper->getOrganizationInfo($organizationName); + $repositories = $apiHelper->getOrganizationRepositories($organizationName); +diff --git a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +index 70ecaa0..e6df6e7 100644 +--- a/src/EventSubscriber/BlackfireAutoProfileSubscriber.php ++++ b/src/EventSubscriber/BlackfireAutoProfileSubscriber.php +@@ -2,6 +2,7 @@ + + namespace App\EventSubscriber; + ++use Blackfire\Client; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Symfony\Component\HttpKernel\Event\RequestEvent; + +@@ -9,7 +10,14 @@ class BlackfireAutoProfileSubscriber implements EventSubscriberInterface + { + public function onRequestEvent(RequestEvent $event) + { +- // ... ++ // replace with some conditional logic ++ $request = $event->getRequest(); ++ $shouldProfile = $request->getPathInfo() === '/api/github-organization'; ++ ++ if ($shouldProfile) { ++ $blackfire = new Client(); ++ $probe = $blackfire->createProbe(); ++ } + } + + public static function getSubscribedEvents() diff --git a/_tuts/sdk-remove-enable-disable.diff b/_tuts/sdk-remove-enable-disable.diff new file mode 100644 index 0000000..24aa669 --- /dev/null +++ b/_tuts/sdk-remove-enable-disable.diff @@ -0,0 +1,24 @@ +diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php +index 262e994..da05ffd 100644 +--- a/src/Controller/MainController.php ++++ b/src/Controller/MainController.php +@@ -21,18 +21,11 @@ class MainController extends AbstractController + */ + public function homepage(BigFootSightingRepository $bigFootSightingRepository) + { +- $probe = \BlackfireProbe::getMainInstance(); +- $probe->enable(); + $sightings = $this->createSightingsPaginator(1, $bigFootSightingRepository); + +- $response = $this->render('main/homepage.html.twig', [ ++ return $this->render('main/homepage.html.twig', [ + 'sightings' => $sightings + ]); +- +- $probe->disable(); +- $probe->close(); // optional - will auto-close at end of script +- +- return $response; + } + + /** diff --git a/_tuts/sdk-showing-close.diff b/_tuts/sdk-showing-close.diff new file mode 100644 index 0000000..2941b5e --- /dev/null +++ b/_tuts/sdk-showing-close.diff @@ -0,0 +1,12 @@ +diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php +index ea32e9e..262e994 100644 +--- a/src/Controller/MainController.php ++++ b/src/Controller/MainController.php +@@ -30,6 +30,7 @@ class MainController extends AbstractController + ]); + + $probe->disable(); ++ $probe->close(); // optional - will auto-close at end of script + + return $response; + } diff --git a/_tuts/sfcloud-add-database.diff b/_tuts/sfcloud-add-database.diff new file mode 100644 index 0000000..4c3d7ae --- /dev/null +++ b/_tuts/sfcloud-add-database.diff @@ -0,0 +1,23 @@ +diff --git a/.symfony.cloud.yaml b/.symfony.cloud.yaml +index b1c05fe..68fd174 100644 +--- a/.symfony.cloud.yaml ++++ b/.symfony.cloud.yaml +@@ -22,6 +22,9 @@ web: + + disk: 512 + ++relationships: ++ database: "mydatabase:mysql" ++ + mounts: + "/var": { source: local, source_path: var } + +diff --git a/.symfony/services.yaml b/.symfony/services.yaml +index e69de29..3d76f52 100644 +--- a/.symfony/services.yaml ++++ b/.symfony/services.yaml +@@ -0,0 +1,4 @@ ++mydatabase: ++ # mariadb ++ type: mysql:10.2 ++ disk: 1024 diff --git a/_tuts/sfcloud-enable-bf-extension.diff b/_tuts/sfcloud-enable-bf-extension.diff new file mode 100644 index 0000000..d1b18f2 --- /dev/null +++ b/_tuts/sfcloud-enable-bf-extension.diff @@ -0,0 +1,13 @@ +diff --git a/.symfony.cloud.yaml b/.symfony.cloud.yaml +index 68fd174..fd3f292 100644 +--- a/.symfony.cloud.yaml ++++ b/.symfony.cloud.yaml +@@ -8,7 +8,7 @@ runtime: + - mbstring + - ctype + - iconv +- ++ - blackfire + + build: + flavor: none diff --git a/_tuts/sfcloud-project-init.diff b/_tuts/sfcloud-project-init.diff new file mode 100644 index 0000000..69a0384 --- /dev/null +++ b/_tuts/sfcloud-project-init.diff @@ -0,0 +1,69 @@ +diff --git a/.symfony.cloud.yaml b/.symfony.cloud.yaml +new file mode 100644 +index 0000000..81fdea3 +--- /dev/null ++++ b/.symfony.cloud.yaml +@@ -0,0 +1,38 @@ ++name: app ++ ++type: php:7.1 ++ ++runtime: ++ extensions: ++ - apcu ++ - mbstring ++ - ctype ++ - iconv ++ ++ ++build: ++ flavor: none ++ ++web: ++ locations: ++ "/": ++ root: "public" ++ expires: 1h ++ passthru: "/index.php" ++ ++disk: 512 ++ ++mounts: ++ "/var": { source: local, source_path: var } ++ ++hooks: ++ build: | ++ set -x -e ++ ++ curl -s https://get.symfony.com/cloud/configurator | (>&2 bash) ++ (>&2 symfony-build) ++ ++ deploy: | ++ set -x -e ++ ++ (>&2 symfony-deploy) +diff --git a/.symfony/routes.yaml b/.symfony/routes.yaml +new file mode 100644 +index 0000000..90c6a1e +--- /dev/null ++++ b/.symfony/routes.yaml +@@ -0,0 +1,2 @@ ++"https://{all}/": { type: upstream, upstream: "app:http" } ++"http://{all}/": { type: redirect, to: "https://{all}/" } +diff --git a/.symfony/services.yaml b/.symfony/services.yaml +new file mode 100644 +index 0000000..e69de29 +diff --git a/php.ini b/php.ini +new file mode 100644 +index 0000000..9b0ba5e +--- /dev/null ++++ b/php.ini +@@ -0,0 +1,8 @@ ++allow_url_include=off ++assert.active=off ++display_errors=off ++display_startup_errors=off ++max_execution_time=30 ++session.use_strict_mode=On ++realpath_cache_ttl=3600 ++zend.detect_unicode=Off diff --git a/_tuts/sfcloud-tweak-php-version.diff b/_tuts/sfcloud-tweak-php-version.diff new file mode 100644 index 0000000..3ce57ef --- /dev/null +++ b/_tuts/sfcloud-tweak-php-version.diff @@ -0,0 +1,12 @@ +diff --git a/.symfony.cloud.yaml b/.symfony.cloud.yaml +index 81fdea3..b1c05fe 100644 +--- a/.symfony.cloud.yaml ++++ b/.symfony.cloud.yaml +@@ -1,6 +1,6 @@ + name: app + +-type: php:7.1 ++type: php:7.3 + + runtime: + extensions: diff --git a/_tuts/steps.json b/_tuts/steps.json new file mode 100644 index 0000000..5801151 --- /dev/null +++ b/_tuts/steps.json @@ -0,0 +1,260 @@ +{ + "steps": [ + { + "id": "start", + "name": "start", + "description": null + }, + { + "id": "first-profile-make-direct-count-query", + "name": "First Profile: Make direct count query", + "description": null + }, + { + "id": "first-profile-switch-to-prod", + "name": "First Profile: switch to prod", + "description": null + }, + { + "id": "first-profile-property-caching", + "name": "First Profile: Property caching", + "description": null + }, + { + "id": "first-profile-revert-prop-caching", + "name": "First Profile: revert prop caching", + "description": null + }, + { + "id": "first-profile-use-proper-caching", + "name": "First Profile: use proper caching", + "description": null + }, + { + "id": "n-1-extra-lazy", + "name": "N+1: EXTRA_LAZY", + "description": null + }, + { + "id": "n-1-use-join", + "name": "N+1: use join", + "description": null + }, + { + "id": "n-1-revert-join", + "name": "N+1: revert join", + "description": null + }, + { + "id": "command-move-flush-out-of-loop", + "name": "Command: move flush out of loop", + "description": null + }, + { + "id": "timeline-go-back-to-dev-env", + "name": "Timeline: go back to dev env", + "description": null + }, + { + "id": "timeline-exit-earlier-in-subscriber", + "name": "Timeline: exit earlier in subscriber", + "description": null + }, + { + "id": "timeline-use-service-subscriber", + "name": "Timeline: use service subscriber", + "description": null + }, + { + "id": "sdk-install-it", + "name": "SDK: install it", + "description": null + }, + { + "id": "sdk-manually-enable-disable-probe", + "name": "SDK: Manually enable\/disable probe", + "description": null + }, + { + "id": "sdk-showing-close", + "name": "SDK: showing close()", + "description": null + }, + { + "id": "sdk-remove-enable-disable", + "name": "SDK: Remove enable\/disable", + "description": null + }, + { + "id": "sdk-manually-create-probe", + "name": "SDK: Manually create probe", + "description": null + }, + { + "id": "sdk-delete-me1", + "name": "SDK: delete me1", + "description": null + }, + { + "id": "sdk-make-subscriber", + "name": "SDK: make:subscriber", + "description": null + }, + { + "id": "sdk-refactor-to-subscriber", + "name": "SDK: refactor to subscriber", + "description": null + }, + { + "id": "sdk-only-on-master-requests", + "name": "SDK: only on master requests", + "description": null + }, + { + "id": "sdk-add-terminateevent", + "name": "SDK: Add TerminateEvent", + "description": null + }, + { + "id": "sdk-add-priority", + "name": "SDK: add priority", + "description": null + }, + { + "id": "sdk-add-configuration", + "name": "SDK: Add Configuration", + "description": null + }, + { + "id": "sdk-disable-auto-profiling", + "name": "SDK: disable auto-profiling", + "description": null + }, + { + "id": "test-add-test-assertion", + "name": "Test: add test assertion", + "description": null + }, + { + "id": "test-refactor-to-1-http-request", + "name": "Test: refactor to 1 http request", + "description": null + }, + { + "id": "test-typo-count", + "name": "Test: typo count", + "description": null + }, + { + "id": "test-fix-typo", + "name": "Test: fix typo", + "description": null + }, + { + "id": "test-refactor-into-separate-method", + "name": "Test: Refactor into separate method", + "description": null + }, + { + "id": "test-clean-up-original-method", + "name": "Test: clean up original method", + "description": null + }, + { + "id": "test-blackfire-group", + "name": "Test: blackfire group", + "description": null + }, + { + "id": "metrics-blackfire-yaml-with-1-test", + "name": "Metrics: .blackfire.yaml with 1 test", + "description": null + }, + { + "id": "player-bootstrap-scenario-bkf", + "name": "Player: bootstrap scenario.bkf", + "description": null + }, + { + "id": "player-add-1-scenario-2-pages", + "name": "Player: add 1 scenario, 2 pages", + "description": null + }, + { + "id": "player-add-1-expect", + "name": "Player: add 1 expect", + "description": null + }, + { + "id": "player-add-failing-expect", + "name": "Player: add failing expect", + "description": null + }, + { + "id": "player-fix-failing-expect", + "name": "Player: fix failing expect", + "description": null + }, + { + "id": "player-typo-a-method-name", + "name": "Player: typo a method name", + "description": null + }, + { + "id": "player-fix-typo", + "name": "Player: fix typo", + "description": null + }, + { + "id": "player-example-assert", + "name": "Player: example assert", + "description": null + }, + { + "id": "sfcloud-project-init", + "name": "SfCloud: project:init", + "description": null + }, + { + "id": "sfcloud-tweak-php-version", + "name": "SfCloud: tweak PHP version", + "description": null + }, + { + "id": "sfcloud-add-database", + "name": "SfCloud: Add database", + "description": null + }, + { + "id": "sfcloud-enable-bf-extension", + "name": "SfCloud: enable bf extension", + "description": null + }, + { + "id": "env-move-scenario-to-blackfire-yaml", + "name": "Env: move scenario to .blackfire.yaml", + "description": null + }, + { + "id": "env-remove-comment", + "name": "Env: remove comment", + "description": null + }, + { + "id": "env-homepage-wall-time-assert", + "name": "Env: homepage wall time assert", + "description": null + }, + { + "id": "env-trying-comparison", + "name": "Env: trying comparison", + "description": null + }, + { + "id": "env-using-variable", + "name": "Env: using variable", + "description": null + } + ], + "sha": "6b4305add68d3c1ca6405e8296c1630c3e561efe" +} \ No newline at end of file diff --git a/_tuts/test-add-test-assertion.diff b/_tuts/test-add-test-assertion.diff new file mode 100644 index 0000000..19ff6c4 --- /dev/null +++ b/_tuts/test-add-test-assertion.diff @@ -0,0 +1,36 @@ +diff --git a/tests/Controller/MainControllerTest.php b/tests/Controller/MainControllerTest.php +index ca0db9f..2108ad8 100644 +--- a/tests/Controller/MainControllerTest.php ++++ b/tests/Controller/MainControllerTest.php +@@ -2,17 +2,27 @@ + + namespace App\Tests\Controller; + ++use Blackfire\Bridge\PhpUnit\TestCaseTrait; ++use Blackfire\Profile\Configuration; + use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + + class MainControllerTest extends WebTestCase + { ++ use TestCaseTrait; ++ + public function testGetGitHubOrganization() + { + $client = static::createClient(); + +- $client->request('GET', '/api/github-organization'); +- $this->assertResponseIsSuccessful(); +- $data = json_decode($client->getResponse()->getContent(), true); +- $this->assertArrayHasKey('organization', $data); ++ $blackfireConfig = (new Configuration()) ++ ->assert('metrics.http.requests.count == 1'); ++ ++ $this->assertBlackfire($blackfireConfig, function() use ($client) { ++ $client->request('GET', '/api/github-organization'); ++ ++ $this->assertResponseIsSuccessful(); ++ $data = json_decode($client->getResponse()->getContent(), true); ++ $this->assertArrayHasKey('organization', $data); ++ }); + } + } diff --git a/_tuts/test-blackfire-group.diff b/_tuts/test-blackfire-group.diff new file mode 100644 index 0000000..dba0198 --- /dev/null +++ b/_tuts/test-blackfire-group.diff @@ -0,0 +1,15 @@ +diff --git a/tests/Controller/MainControllerTest.php b/tests/Controller/MainControllerTest.php +index b4c5427..e624e40 100644 +--- a/tests/Controller/MainControllerTest.php ++++ b/tests/Controller/MainControllerTest.php +@@ -21,6 +21,10 @@ class MainControllerTest extends WebTestCase + $this->assertArrayHasKey('organization', $data); + } + ++ /** ++ * @group blackfire ++ * @requires extension blackfire ++ */ + public function testGetGitHubOrganizationBlackfireHttpRequests() + { + $client = static::createClient(); diff --git a/_tuts/test-clean-up-original-method.diff b/_tuts/test-clean-up-original-method.diff new file mode 100644 index 0000000..71ea25b --- /dev/null +++ b/_tuts/test-clean-up-original-method.diff @@ -0,0 +1,25 @@ +diff --git a/tests/Controller/MainControllerTest.php b/tests/Controller/MainControllerTest.php +index 9c93001..b4c5427 100644 +--- a/tests/Controller/MainControllerTest.php ++++ b/tests/Controller/MainControllerTest.php +@@ -14,16 +14,11 @@ class MainControllerTest extends WebTestCase + { + $client = static::createClient(); + +- $blackfireConfig = (new Configuration()) +- ->assert('metrics.http.requests.count == 1'); +- +- $this->assertBlackfire($blackfireConfig, function() use ($client) { +- $client->request('GET', '/api/github-organization'); ++ $client->request('GET', '/api/github-organization'); + +- $this->assertResponseIsSuccessful(); +- $data = json_decode($client->getResponse()->getContent(), true); +- $this->assertArrayHasKey('organization', $data); +- }); ++ $this->assertResponseIsSuccessful(); ++ $data = json_decode($client->getResponse()->getContent(), true); ++ $this->assertArrayHasKey('organization', $data); + } + + public function testGetGitHubOrganizationBlackfireHttpRequests() diff --git a/_tuts/test-fix-typo.diff b/_tuts/test-fix-typo.diff new file mode 100644 index 0000000..042c632 --- /dev/null +++ b/_tuts/test-fix-typo.diff @@ -0,0 +1,13 @@ +diff --git a/tests/Controller/MainControllerTest.php b/tests/Controller/MainControllerTest.php +index b691079..2108ad8 100644 +--- a/tests/Controller/MainControllerTest.php ++++ b/tests/Controller/MainControllerTest.php +@@ -15,7 +15,7 @@ class MainControllerTest extends WebTestCase + $client = static::createClient(); + + $blackfireConfig = (new Configuration()) +- ->assert('metrics.http.requests.vount == 1'); ++ ->assert('metrics.http.requests.count == 1'); + + $this->assertBlackfire($blackfireConfig, function() use ($client) { + $client->request('GET', '/api/github-organization'); diff --git a/_tuts/test-refactor-into-separate-method.diff b/_tuts/test-refactor-into-separate-method.diff new file mode 100644 index 0000000..2c3d399 --- /dev/null +++ b/_tuts/test-refactor-into-separate-method.diff @@ -0,0 +1,21 @@ +diff --git a/tests/Controller/MainControllerTest.php b/tests/Controller/MainControllerTest.php +index 2108ad8..9c93001 100644 +--- a/tests/Controller/MainControllerTest.php ++++ b/tests/Controller/MainControllerTest.php +@@ -25,4 +25,16 @@ class MainControllerTest extends WebTestCase + $this->assertArrayHasKey('organization', $data); + }); + } ++ ++ public function testGetGitHubOrganizationBlackfireHttpRequests() ++ { ++ $client = static::createClient(); ++ ++ $blackfireConfig = (new Configuration()) ++ ->assert('metrics.http.requests.count == 1'); ++ ++ $this->assertBlackfire($blackfireConfig, function() use ($client) { ++ $client->request('GET', '/api/github-organization'); ++ }); ++ } + } diff --git a/_tuts/test-refactor-to-1-http-request.diff b/_tuts/test-refactor-to-1-http-request.diff new file mode 100644 index 0000000..d3b3d41 --- /dev/null +++ b/_tuts/test-refactor-to-1-http-request.diff @@ -0,0 +1,64 @@ +diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php +index da05ffd..628bc35 100644 +--- a/src/Controller/MainController.php ++++ b/src/Controller/MainController.php +@@ -55,8 +55,8 @@ class MainController extends AbstractController + public function gitHubOrganizationInfo(GitHubApiHelper $apiHelper) + { + $organizationName = 'SymfonyCasts'; +- $organization = $apiHelper->getOrganizationInfo($organizationName); + $repositories = $apiHelper->getOrganizationRepositories($organizationName); ++ $organization = $apiHelper->getOrganizationInfo($organizationName); + + return $this->json([ + 'organization' => $organization, +diff --git a/src/GitHub/GitHubApiHelper.php b/src/GitHub/GitHubApiHelper.php +index f33c217..ec93026 100644 +--- a/src/GitHub/GitHubApiHelper.php ++++ b/src/GitHub/GitHubApiHelper.php +@@ -8,6 +8,8 @@ class GitHubApiHelper + { + private $httpClient; + ++ private $githubOrganizations = []; ++ + public function __construct(HttpClientInterface $httpClient) + { + $this->httpClient = $httpClient; +@@ -15,6 +17,11 @@ class GitHubApiHelper + + public function getOrganizationInfo(string $organization): GitHubOrganization + { ++ // optimization in case getOrganizationRepositories is called first ++ if (isset($this->githubOrganizations[$organization])) { ++ return $this->githubOrganizations[$organization]; ++ } ++ + $response = $this->httpClient->request('GET', 'https://api.github.com/orgs/'.$organization); + + $data = $response->toArray(); +@@ -35,12 +42,24 @@ class GitHubApiHelper + $data = $response->toArray(); + + $repositories = []; ++ $publicRepoCount = 0; + foreach ($data as $repoData) { + $repositories[] = new GitHubRepository( + $repoData['name'], + $repoData['html_url'], + \DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s\Z', $repoData['updated_at']) + ); ++ ++ if ($repoData['private'] === false) { ++ ++$publicRepoCount; ++ } ++ } ++ ++ if (!isset($this->githubOrganizations[$organization])) { ++ $this->githubOrganizations[$organization] = new GitHubOrganization( ++ $data[0]['owner']['login'], ++ $publicRepoCount ++ ); + } + + return $repositories; diff --git a/_tuts/test-typo-count.diff b/_tuts/test-typo-count.diff new file mode 100644 index 0000000..61b47b7 --- /dev/null +++ b/_tuts/test-typo-count.diff @@ -0,0 +1,13 @@ +diff --git a/tests/Controller/MainControllerTest.php b/tests/Controller/MainControllerTest.php +index 2108ad8..b691079 100644 +--- a/tests/Controller/MainControllerTest.php ++++ b/tests/Controller/MainControllerTest.php +@@ -15,7 +15,7 @@ class MainControllerTest extends WebTestCase + $client = static::createClient(); + + $blackfireConfig = (new Configuration()) +- ->assert('metrics.http.requests.count == 1'); ++ ->assert('metrics.http.requests.vount == 1'); + + $this->assertBlackfire($blackfireConfig, function() use ($client) { + $client->request('GET', '/api/github-organization'); diff --git a/_tuts/timeline-exit-earlier-in-subscriber.diff b/_tuts/timeline-exit-earlier-in-subscriber.diff new file mode 100644 index 0000000..eab4e3d --- /dev/null +++ b/_tuts/timeline-exit-earlier-in-subscriber.diff @@ -0,0 +1,28 @@ +diff --git a/src/EventSubscriber/AgreeToTermsSubscriber.php b/src/EventSubscriber/AgreeToTermsSubscriber.php +index d634d4d..227a6a8 100644 +--- a/src/EventSubscriber/AgreeToTermsSubscriber.php ++++ b/src/EventSubscriber/AgreeToTermsSubscriber.php +@@ -43,6 +43,11 @@ class AgreeToTermsSubscriber implements EventSubscriberInterface + //$latestTermsDate = new \DateTimeImmutable('2019-10-15'); + $latestTermsDate = new \DateTimeImmutable('-1 year'); + ++ // user is up-to-date! ++ if ($user->getAgreedToTermsAt() >= $latestTermsDate) { ++ return; ++ } ++ + $form = $this->formFactory->create(AgreeToUpdatedTermsFormType::class); + + $html = $this->twig->render('main/agreeUpdatedTerms.html.twig', [ +@@ -54,11 +59,6 @@ class AgreeToTermsSubscriber implements EventSubscriberInterface + // we know the user doesn't need to see the form! + $this->entrypointLookup->reset(); + +- // user is up-to-date! +- if ($user->getAgreedToTermsAt() >= $latestTermsDate) { +- return; +- } +- + $response = new Response($html); + $event->setResponse($response); + } diff --git a/_tuts/timeline-go-back-to-dev-env.diff b/_tuts/timeline-go-back-to-dev-env.diff new file mode 100644 index 0000000..5874c72 --- /dev/null +++ b/_tuts/timeline-go-back-to-dev-env.diff @@ -0,0 +1,13 @@ +diff --git a/.env b/.env +index ac88cd0..1f68332 100644 +--- a/.env ++++ b/.env +@@ -14,7 +14,7 @@ + # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration + + ###> symfony/framework-bundle ### +-APP_ENV=prod ++APP_ENV=dev + APP_SECRET=77edcf2d21c9bb9eb67233d29c618543 + #TRUSTED_PROXIES=127.0.0.1,127.0.0.2 + #TRUSTED_HOSTS='^localhost|example\.com$' diff --git a/_tuts/timeline-use-service-subscriber.diff b/_tuts/timeline-use-service-subscriber.diff new file mode 100644 index 0000000..fb463dd --- /dev/null +++ b/_tuts/timeline-use-service-subscriber.diff @@ -0,0 +1,87 @@ +diff --git a/src/Security/LoginFormAuthenticator.php b/src/Security/LoginFormAuthenticator.php +index 17ade90..8485cfd 100644 +--- a/src/Security/LoginFormAuthenticator.php ++++ b/src/Security/LoginFormAuthenticator.php +@@ -4,6 +4,7 @@ namespace App\Security; + + use App\Entity\User; + use Doctrine\ORM\EntityManagerInterface; ++use Psr\Container\ContainerInterface; + use Symfony\Component\HttpFoundation\RedirectResponse; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +@@ -18,22 +19,17 @@ use Symfony\Component\Security\Csrf\CsrfToken; + use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; + use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; + use Symfony\Component\Security\Http\Util\TargetPathTrait; ++use Symfony\Contracts\Service\ServiceSubscriberInterface; + +-class LoginFormAuthenticator extends AbstractFormLoginAuthenticator ++class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements ServiceSubscriberInterface + { + use TargetPathTrait; + +- private $entityManager; +- private $urlGenerator; +- private $csrfTokenManager; +- private $passwordEncoder; ++ private $container; + +- public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder) ++ public function __construct(ContainerInterface $container) + { +- $this->entityManager = $entityManager; +- $this->urlGenerator = $urlGenerator; +- $this->csrfTokenManager = $csrfTokenManager; +- $this->passwordEncoder = $passwordEncoder; ++ $this->container = $container; + } + + public function supports(Request $request) +@@ -60,11 +56,11 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator + public function getUser($credentials, UserProviderInterface $userProvider) + { + $token = new CsrfToken('authenticate', $credentials['csrf_token']); +- if (!$this->csrfTokenManager->isTokenValid($token)) { ++ if (!$this->container->get(CsrfTokenManagerInterface::class)->isTokenValid($token)) { + throw new InvalidCsrfTokenException(); + } + +- $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]); ++ $user = $this->container->get(EntityManagerInterface::class)->getRepository(User::class)->findOneBy(['email' => $credentials['email']]); + + if (!$user) { + // fail authentication with a custom error +@@ -76,7 +72,7 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator + + public function checkCredentials($credentials, UserInterface $user) + { +- return $this->passwordEncoder->isPasswordValid($user, $credentials['password']); ++ return $this->container->get(UserPasswordEncoderInterface::class)->isPasswordValid($user, $credentials['password']); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) +@@ -85,11 +81,21 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator + return new RedirectResponse($targetPath); + } + +- return new RedirectResponse($this->urlGenerator->generate('app_homepage')); ++ return new RedirectResponse($this->container->get(UrlGeneratorInterface::class)->generate('app_homepage')); + } + + protected function getLoginUrl() + { +- return $this->urlGenerator->generate('app_login'); ++ return $this->container->get(UrlGeneratorInterface::class)->generate('app_login'); ++ } ++ ++ public static function getSubscribedServices() ++ { ++ return [ ++ EntityManagerInterface::class, ++ UrlGeneratorInterface::class, ++ CsrfTokenManagerInterface::class, ++ UserPasswordEncoderInterface::class, ++ ]; + } + } diff --git a/assets/app.js b/assets/app.js deleted file mode 100644 index bb0a6aa..0000000 --- a/assets/app.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Welcome to your app's main JavaScript file! - * - * We recommend including the built version of this JavaScript file - * (and its CSS file) in your base layout (base.html.twig). - */ - -// any CSS you import will output into a single css file (app.css in this case) -import './styles/app.css'; - -// start the Stimulus application -import './bootstrap'; diff --git a/assets/bootstrap.js b/assets/bootstrap.js deleted file mode 100644 index 9792e6a..0000000 --- a/assets/bootstrap.js +++ /dev/null @@ -1,5 +0,0 @@ -import { startStimulusApp } from '@symfony/stimulus-bridge'; -import '@symfony/autoimport'; - -// Registers Stimulus controllers from controllers.json and in the controllers/ directory -export const app = startStimulusApp(require.context('./controllers', true, /\.(j|t)sx?$/)); diff --git a/assets/controllers.json b/assets/controllers.json deleted file mode 100644 index a1c6e90..0000000 --- a/assets/controllers.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "controllers": [], - "entrypoints": [] -} diff --git a/assets/controllers/hello_controller.js b/assets/controllers/hello_controller.js deleted file mode 100644 index 8c79f65..0000000 --- a/assets/controllers/hello_controller.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Controller } from 'stimulus'; - -/* - * This is an example Stimulus controller! - * - * Any element with a data-controller="hello" attribute will cause - * this controller to be executed. The name "hello" comes from the filename: - * hello_controller.js -> "hello" - * - * Delete this file or adapt it for your use! - */ -export default class extends Controller { - connect() { - this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js'; - } -} diff --git a/assets/styles/app.css b/assets/styles/app.css deleted file mode 100644 index cb33b13..0000000 --- a/assets/styles/app.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - background-color: lightgray; -} diff --git a/bin/console b/bin/console index 8fe9d49..19c2f6c 100755 --- a/bin/console +++ b/bin/console @@ -4,19 +4,18 @@ use App\Kernel; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Dotenv\Dotenv; -use Symfony\Component\ErrorHandler\Debug; +use Symfony\Component\Debug\Debug; -if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { - echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL; +if (false === in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.\PHP_SAPI.' SAPI'.\PHP_EOL; } set_time_limit(0); require dirname(__DIR__).'/vendor/autoload.php'; -if (!class_exists(Application::class) || !class_exists(Dotenv::class)) { - throw new LogicException('You need to add "symfony/framework-bundle" and "symfony/dotenv" as Composer dependencies.'); +if (!class_exists(Application::class)) { + throw new RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.'); } $input = new ArgvInput(); @@ -28,7 +27,7 @@ if ($input->hasParameterOption('--no-debug', true)) { putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); } -(new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); +require dirname(__DIR__).'/config/bootstrap.php'; if ($_SERVER['APP_DEBUG']) { umask(0000); diff --git a/composer.json b/composer.json index a4bea49..7519706 100644 --- a/composer.json +++ b/composer.json @@ -2,24 +2,24 @@ "type": "project", "license": "proprietary", "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "ext-ctype": "*", "ext-iconv": "*", "composer/package-versions-deprecated": "^1.11", "sensio/framework-extra-bundle": "^5.4", - "symfony/console": "5.2.*", - "symfony/dotenv": "5.2.*", + "symfony/console": "4.3.*", + "symfony/dotenv": "4.3.*", "symfony/flex": "^1.9", - "symfony/form": "5.2.*", - "symfony/framework-bundle": "5.2.*", - "symfony/http-client": "5.2.*", + "symfony/form": "4.3.*", + "symfony/framework-bundle": "4.3.*", + "symfony/http-client": "4.3.*", "symfony/orm-pack": "^1.0", - "symfony/security-bundle": "5.2.*", + "symfony/security-bundle": "4.3.*", "symfony/serializer-pack": "^1.0", - "symfony/twig-bundle": "5.2.*", - "symfony/validator": "5.2.*", + "symfony/twig-bundle": "4.3.*", + "symfony/validator": "4.3.*", "symfony/webpack-encore-bundle": "^1.6", - "symfony/yaml": "5.2.*", + "symfony/yaml": "4.3.*", "twig/extensions": "^1.5" }, "require-dev": { @@ -35,7 +35,7 @@ }, "sort-packages": true, "platform": { - "php": "7.2.5" + "php": "7.1.3" } }, "autoload": { @@ -74,7 +74,7 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "5.2.*" + "require": "4.3.*" } } } diff --git a/composer.lock b/composer.lock index 1548cc0..a08121d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e48a133315e52bd5b60bf5bb8dd097db", + "content-hash": "0d5cee3803219f1246f5b00b95e71ea0", "packages": [ { "name": "composer/package-versions-deprecated", - "version": "1.11.99.1", + "version": "1.11.99", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + "reference": "c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855", + "reference": "c8c9aa8a14cc3d3bec86d0a8c3fa52ea79936855", "shasum": "" }, "require": { @@ -61,7 +61,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + "source": "https://github.com/composer/package-versions-deprecated/tree/master" }, "funding": [ { @@ -77,37 +77,34 @@ "type": "tidelift" } ], - "time": "2020-11-11T10:22:58+00:00" + "time": "2020-08-25T05:50:16+00:00" }, { "name": "doctrine/annotations", - "version": "1.11.1", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad" + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/ce77a7ba1770462cd705a91a151b6c3746f9c6ad", - "reference": "ce77a7ba1770462cd705a91a151b6c3746f9c6ad", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc", + "reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc", "shasum": "" }, "require": { "doctrine/lexer": "1.*", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0" + "php": "^7.1" }, "require-dev": { "doctrine/cache": "1.*", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^0.12.20", - "phpunit/phpunit": "^7.5 || ^9.1.5" + "phpunit/phpunit": "^7.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.7.x-dev" } }, "autoload": { @@ -142,41 +139,37 @@ } ], "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "homepage": "http://www.doctrine-project.org", "keywords": [ "annotations", "docblock", "parser" ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.11.1" - }, - "time": "2020-10-26T10:28:16+00:00" + "time": "2019-10-01T18:55:10+00:00" }, { "name": "doctrine/cache", - "version": "1.10.2", + "version": "v1.8.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "13e3381b25847283a91948d04640543941309727" + "reference": "d4374ae95b36062d02ef310100ed33d78738d76c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", - "reference": "13e3381b25847283a91948d04640543941309727", + "url": "https://api.github.com/repos/doctrine/cache/zipball/d4374ae95b36062d02ef310100ed33d78738d76c", + "reference": "d4374ae95b36062d02ef310100ed33d78738d76c", "shasum": "" }, "require": { - "php": "~7.1 || ^8.0" + "php": "~7.1" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^6.0", + "doctrine/coding-standard": "^4.0", "mongodb/mongodb": "^1.1", "phpunit/phpunit": "^7.0", "predis/predis": "~1.0" @@ -187,7 +180,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -221,63 +214,43 @@ "email": "schmittjoh@gmail.com" } ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "https://www.doctrine-project.org", "keywords": [ - "abstraction", - "apcu", "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.10.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } + "caching" ], - "time": "2020-07-07T18:54:01+00:00" + "time": "2019-10-28T09:31:32+00:00" }, { "name": "doctrine/collections", - "version": "1.6.7", + "version": "v1.6.2", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a" + "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/55f8b799269a1a472457bd1a41b4f379d4cfba4a", - "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "url": "https://api.github.com/repos/doctrine/collections/zipball/c5e0bc17b1620e97c968ac409acbff28b8b850be", + "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be", "shasum": "" }, "require": { - "php": "^7.1.3 || ^8.0" + "php": "^7.1.3" }, "require-dev": { "doctrine/coding-standard": "^6.0", "phpstan/phpstan-shim": "^0.9.2", "phpunit/phpunit": "^7.0", - "vimeo/psalm": "^3.8.1" + "vimeo/psalm": "^3.2.2" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, "autoload": { "psr-4": { "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" @@ -288,10 +261,6 @@ "MIT" ], "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -300,6 +269,10 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -317,39 +290,47 @@ "iterators", "php" ], - "support": { - "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.6.7" - }, - "time": "2020-07-27T17:53:49+00:00" + "time": "2019-06-09T13:48:14+00:00" }, { "name": "doctrine/common", - "version": "3.1.1", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/doctrine/common.git", - "reference": "2afde5a9844126bc311cd5f548b5475e75f800d3" + "reference": "b8ca1dcf6b0dc8a2af7a09baac8d0c48345df4ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/2afde5a9844126bc311cd5f548b5475e75f800d3", - "reference": "2afde5a9844126bc311cd5f548b5475e75f800d3", + "url": "https://api.github.com/repos/doctrine/common/zipball/b8ca1dcf6b0dc8a2af7a09baac8d0c48345df4ff", + "reference": "b8ca1dcf6b0dc8a2af7a09baac8d0c48345df4ff", "shasum": "" }, "require": { - "doctrine/persistence": "^2.0", - "php": "^7.1 || ^8.0" + "doctrine/annotations": "^1.0", + "doctrine/cache": "^1.0", + "doctrine/collections": "^1.0", + "doctrine/event-manager": "^1.0", + "doctrine/inflector": "^1.0", + "doctrine/lexer": "^1.0", + "doctrine/persistence": "^1.1", + "doctrine/reflection": "^1.0", + "php": "^7.1" }, "require-dev": { - "doctrine/coding-standard": "^6.0 || ^8.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "doctrine/coding-standard": "^1.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpunit/phpunit": "^7.0", "squizlabs/php_codesniffer": "^3.0", "symfony/phpunit-bridge": "^4.0.5" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev" + } + }, "autoload": { "psr-4": { "Doctrine\\Common\\": "lib/Doctrine/Common" @@ -385,62 +366,42 @@ "email": "ocramius@gmail.com" } ], - "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, persistence interfaces, proxies, event system and much more.", "homepage": "https://www.doctrine-project.org/projects/common.html", "keywords": [ "common", "doctrine", "php" ], - "support": { - "issues": "https://github.com/doctrine/common/issues", - "source": "https://github.com/doctrine/common/tree/3.1.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", - "type": "tidelift" - } - ], - "time": "2021-01-20T19:58:05+00:00" + "time": "2019-09-10T10:10:14+00:00" }, { "name": "doctrine/dbal", - "version": "2.10.4", + "version": "v2.9.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "47433196b6390d14409a33885ee42b6208160643" + "reference": "7345cd59edfa2036eb0fa4264b77ae2576842035" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/47433196b6390d14409a33885ee42b6208160643", - "reference": "47433196b6390d14409a33885ee42b6208160643", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/7345cd59edfa2036eb0fa4264b77ae2576842035", + "reference": "7345cd59edfa2036eb0fa4264b77ae2576842035", "shasum": "" }, "require": { "doctrine/cache": "^1.0", "doctrine/event-manager": "^1.0", "ext-pdo": "*", - "php": "^7.2" + "php": "^7.1" }, "require-dev": { - "doctrine/coding-standard": "^8.1", - "jetbrains/phpstorm-stubs": "^2019.1", - "nikic/php-parser": "^4.4", - "phpstan/phpstan": "^0.12.40", - "phpunit/phpunit": "^8.5.5", - "psalm/plugin-phpunit": "^0.10.0", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "^3.14.2" + "doctrine/coding-standard": "^5.0", + "jetbrains/phpstorm-stubs": "^2018.1.2", + "phpstan/phpstan": "^0.10.1", + "phpunit/phpunit": "^7.4", + "symfony/console": "^2.0.5|^3.0|^4.0", + "symfony/phpunit-bridge": "^3.4.5|^4.0.5" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -451,7 +412,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10.x-dev", + "dev-master": "2.9.x-dev", "dev-develop": "3.0.x-dev" } }, @@ -487,88 +448,56 @@ "keywords": [ "abstraction", "database", - "db2", "dbal", - "mariadb", - "mssql", "mysql", - "oci8", - "oracle", - "pdo", + "persistence", "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlanywhere", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/2.10.4" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", - "type": "tidelift" - } + "php", + "queryobject" ], - "time": "2020-09-12T21:20:41+00:00" + "time": "2019-11-02T22:19:34+00:00" }, { "name": "doctrine/doctrine-bundle", - "version": "2.2.3", + "version": "1.11.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "015fdd490074d4daa891e2d1df998dc35ba54924" + "reference": "28101e20776d8fa20a00b54947fbae2db0d09103" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/015fdd490074d4daa891e2d1df998dc35ba54924", - "reference": "015fdd490074d4daa891e2d1df998dc35ba54924", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/28101e20776d8fa20a00b54947fbae2db0d09103", + "reference": "28101e20776d8fa20a00b54947fbae2db0d09103", "shasum": "" }, "require": { - "doctrine/dbal": "^2.9.0|^3.0", - "doctrine/persistence": "^1.3.3|^2.0", - "doctrine/sql-formatter": "^1.0.1", - "php": "^7.1 || ^8.0", - "symfony/cache": "^4.3.3|^5.0", - "symfony/config": "^4.3.3|^5.0", - "symfony/console": "^3.4.30|^4.3.3|^5.0", - "symfony/dependency-injection": "^4.3.3|^5.0", - "symfony/doctrine-bridge": "^4.3.7|^5.0", - "symfony/framework-bundle": "^3.4.30|^4.3.3|^5.0", - "symfony/service-contracts": "^1.1.1|^2.0" + "doctrine/dbal": "^2.5.12", + "doctrine/doctrine-cache-bundle": "~1.2", + "jdorn/sql-formatter": "^1.2.16", + "php": "^7.1", + "symfony/config": "^3.4|^4.1", + "symfony/console": "^3.4|^4.1", + "symfony/dependency-injection": "^3.4|^4.1", + "symfony/doctrine-bridge": "^3.4|^4.1", + "symfony/framework-bundle": "^3.4|^4.1" }, "conflict": { "doctrine/orm": "<2.6", "twig/twig": "<1.34|>=2.0,<2.4" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^6.0", "doctrine/orm": "^2.6", - "friendsofphp/proxy-manager-lts": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.3", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "7.0", + "symfony/cache": "^3.4|^4.1", "symfony/phpunit-bridge": "^4.2", - "symfony/property-info": "^4.3.3|^5.0", - "symfony/proxy-manager-bridge": "^3.4|^4.3.3|^5.0", - "symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0", - "symfony/validator": "^3.4.30|^4.3.3|^5.0", - "symfony/web-profiler-bundle": "^3.4.30|^4.3.3|^5.0", - "symfony/yaml": "^3.4.30|^4.3.3|^5.0", - "twig/twig": "^1.34|^2.12|^3.0" + "symfony/property-info": "^3.4|^4.1", + "symfony/validator": "^3.4|^4.1", + "symfony/web-profiler-bundle": "^3.4|^4.1", + "symfony/yaml": "^3.4|^4.1", + "twig/twig": "^1.34|^2.4" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -577,7 +506,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "2.3.x-dev" + "dev-master": "1.11.x-dev" } }, "autoload": { @@ -591,20 +520,20 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" }, { "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, { "name": "Doctrine Project", "homepage": "http://www.doctrine-project.org/" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" } ], "description": "Symfony DoctrineBundle", @@ -615,66 +544,136 @@ "orm", "persistence" ], - "support": { - "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.2.3" + "time": "2019-06-04T07:35:05+00:00" + }, + { + "name": "doctrine/doctrine-cache-bundle", + "version": "1.3.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineCacheBundle.git", + "reference": "5514c90d9fb595e1095e6d66ebb98ce9ef049927" }, - "funding": [ + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/5514c90d9fb595e1095e6d66ebb98ce9ef049927", + "reference": "5514c90d9fb595e1095e6d66ebb98ce9ef049927", + "shasum": "" + }, + "require": { + "doctrine/cache": "^1.4.2", + "doctrine/inflector": "~1.0", + "php": ">=5.3.2", + "symfony/doctrine-bridge": "~2.7|~3.3|~4.0" + }, + "require-dev": { + "instaclick/coding-standard": "~1.1", + "instaclick/object-calisthenics-sniffs": "dev-master", + "instaclick/symfony2-coding-standard": "dev-remaster", + "phpunit/phpunit": "~4.8.36|~5.6|~6.5|~7.0", + "predis/predis": "~0.8", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "~1.5", + "symfony/console": "~2.7|~3.3|~4.0", + "symfony/finder": "~2.7|~3.3|~4.0", + "symfony/framework-bundle": "~2.7|~3.3|~4.0", + "symfony/phpunit-bridge": "~2.7|~3.3|~4.0", + "symfony/security-acl": "~2.7|~3.3", + "symfony/validator": "~2.7|~3.3|~4.0", + "symfony/yaml": "~2.7|~3.3|~4.0" + }, + "suggest": { + "symfony/security-acl": "For using this bundle to cache ACLs" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineCacheBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "name": "Fabio B. Silva", + "email": "fabio.bat.silva@gmail.com" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-bundle", - "type": "tidelift" + "name": "Guilherme Blanco", + "email": "guilhermeblanco@hotmail.com" + }, + { + "name": "Doctrine Project", + "homepage": "http://www.doctrine-project.org/" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" } ], - "time": "2021-01-19T20:29:53+00:00" + "description": "Symfony Bundle for Doctrine Cache", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2018-11-09T06:25:35+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", - "version": "2.2.2", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "85f0b847174daf243362c7da80efe1539be64f47" + "reference": "4c9579e0e43df1fb3f0ca29b9c20871c824fac71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/85f0b847174daf243362c7da80efe1539be64f47", - "reference": "85f0b847174daf243362c7da80efe1539be64f47", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/4c9579e0e43df1fb3f0ca29b9c20871c824fac71", + "reference": "4c9579e0e43df1fb3f0ca29b9c20871c824fac71", "shasum": "" }, "require": { - "doctrine/doctrine-bundle": "~1.0|~2.0", - "doctrine/migrations": "^2.2", - "php": "^7.1|^8.0", - "symfony/framework-bundle": "~3.4|~4.0|~5.0" + "doctrine/doctrine-bundle": "~1.0", + "doctrine/migrations": "^2.0", + "php": "^7.1", + "symfony/framework-bundle": "~3.4|~4.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^5.0", "mikey179/vfsstream": "^1.6", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "phpstan/phpstan": "^0.9.2", + "phpstan/phpstan-strict-rules": "^0.9", + "phpunit/phpunit": "^5.7|^6.4|^7.0" }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { "Doctrine\\Bundle\\MigrationsBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -682,16 +681,16 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" }, { "name": "Doctrine Project", "homepage": "http://www.doctrine-project.org" }, { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" } ], "description": "Symfony DoctrineMigrationsBundle", @@ -701,48 +700,30 @@ "migrations", "schema" ], - "support": { - "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", - "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/2.2.2" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-migrations-bundle", - "type": "tidelift" - } - ], - "time": "2020-12-23T15:06:17+00:00" + "time": "2019-01-09T18:49:50+00:00" }, { "name": "doctrine/event-manager", - "version": "1.1.1", + "version": "v1.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", - "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3", + "reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^7.1" }, "conflict": { "doctrine/common": "<2.9@dev" }, "require-dev": { - "doctrine/coding-standard": "^6.0", + "doctrine/coding-standard": "^4.0", "phpunit/phpunit": "^7.0" }, "type": "library", @@ -761,10 +742,6 @@ "MIT" ], "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -773,6 +750,10 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -786,68 +767,44 @@ "email": "ocramius@gmail.com" } ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "description": "Doctrine Event Manager component", "homepage": "https://www.doctrine-project.org/projects/event-manager.html", "keywords": [ "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.1.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } + "eventdispatcher", + "eventmanager" ], - "time": "2020-05-29T18:28:51+00:00" + "time": "2018-06-11T11:59:03+00:00" }, { "name": "doctrine/inflector", - "version": "2.0.3", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210" + "reference": "5527a48b7313d15261292c149e55e26eae771b0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/9cf661f4eb38f7c881cac67c75ea9b00bf97b210", - "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", + "reference": "5527a48b7313d15261292c149e55e26eae771b0a", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.1" }, "require-dev": { - "doctrine/coding-standard": "^7.0", - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", - "phpstan/phpstan-strict-rules": "^0.11", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.3.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -855,10 +812,6 @@ "MIT" ], "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -867,6 +820,10 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -876,67 +833,48 @@ "email": "schmittjoh@gmail.com" } ], - "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", - "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", "keywords": [ "inflection", - "inflector", - "lowercase", - "manipulation", - "php", - "plural", - "singular", - "strings", - "uppercase", - "words" - ], - "support": { - "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.x" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", - "type": "tidelift" - } + "pluralize", + "singularize", + "string" ], - "time": "2020-05-29T15:13:26+00:00" + "time": "2018-01-09T20:05:19+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^7.1" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" @@ -950,7 +888,7 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" + "homepage": "http://ocramius.github.com/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", @@ -959,52 +897,32 @@ "constructor", "instantiate" ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/1febd6c3ef84253d7c815bed85fc622ad207a9f8", + "reference": "1febd6c3ef84253d7c815bed85fc622ad207a9f8", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=5.3.2" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "phpunit/phpunit": "^4.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { @@ -1017,14 +935,14 @@ "MIT" ], "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Roman Borschel", "email": "roman@code-factory.org" }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" @@ -1039,61 +957,43 @@ "parser", "php" ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2019-06-08T11:03:04+00:00" }, { "name": "doctrine/migrations", - "version": "2.3.2", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "39520699043d9bfaaebeb81fa026bf2b02a8f735" + "reference": "a89fa87a192e90179163c1e863a145c13337f442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/39520699043d9bfaaebeb81fa026bf2b02a8f735", - "reference": "39520699043d9bfaaebeb81fa026bf2b02a8f735", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/a89fa87a192e90179163c1e863a145c13337f442", + "reference": "a89fa87a192e90179163c1e863a145c13337f442", "shasum": "" }, "require": { - "composer/package-versions-deprecated": "^1.8", "doctrine/dbal": "^2.9", - "friendsofphp/proxy-manager-lts": "^1.0", - "php": "^7.1 || ^8.0", - "symfony/console": "^3.4||^4.4.16||^5.0", - "symfony/stopwatch": "^3.4||^4.0||^5.0" + "ocramius/package-versions": "^1.3", + "ocramius/proxy-manager": "^2.0.2", + "php": "^7.1", + "symfony/console": "^3.4||^4.0", + "symfony/stopwatch": "^3.4||^4.0" }, "require-dev": { - "doctrine/coding-standard": "^8.2", + "doctrine/coding-standard": "^6.0", "doctrine/orm": "^2.6", "ext-pdo_sqlite": "*", "jdorn/sql-formatter": "^1.1", "mikey179/vfsstream": "^1.6", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", - "symfony/process": "^3.4||^4.0||^5.0", - "symfony/yaml": "^3.4||^4.0||^5.0" + "phpstan/phpstan": "^0.10", + "phpstan/phpstan-deprecation-rules": "^0.10", + "phpstan/phpstan-phpunit": "^0.10", + "phpstan/phpstan-strict-rules": "^0.10", + "phpunit/phpunit": "^7.0", + "symfony/process": "^3.4||^4.0", + "symfony/yaml": "^3.4||^4.0" }, "suggest": { "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command.", @@ -1105,7 +1005,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev" + "dev-master": "2.1.x-dev" } }, "autoload": { @@ -1139,62 +1039,37 @@ "migrations", "php" ], - "support": { - "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/2.3.2" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fmigrations", - "type": "tidelift" - } - ], - "time": "2020-12-23T14:06:04+00:00" + "time": "2019-07-30T18:51:47+00:00" }, { "name": "doctrine/orm", - "version": "2.8.1", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "242cf1a33df1b8bc5e1b86c3ebd01db07851c833" + "reference": "b52ef5a1002f99ab506a5a2d6dba5a2c236c5f43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/242cf1a33df1b8bc5e1b86c3ebd01db07851c833", - "reference": "242cf1a33df1b8bc5e1b86c3ebd01db07851c833", + "url": "https://api.github.com/repos/doctrine/orm/zipball/b52ef5a1002f99ab506a5a2d6dba5a2c236c5f43", + "reference": "b52ef5a1002f99ab506a5a2d6dba5a2c236c5f43", "shasum": "" }, "require": { - "composer/package-versions-deprecated": "^1.8", - "doctrine/annotations": "^1.11.1", - "doctrine/cache": "^1.9.1", - "doctrine/collections": "^1.5", - "doctrine/common": "^3.0", - "doctrine/dbal": "^2.10.0", - "doctrine/event-manager": "^1.1", - "doctrine/inflector": "^1.4|^2.0", - "doctrine/instantiator": "^1.3", - "doctrine/lexer": "^1.0", - "doctrine/persistence": "^2.0", + "doctrine/annotations": "~1.5", + "doctrine/cache": "~1.6", + "doctrine/collections": "^1.4", + "doctrine/common": "^2.7.1", + "doctrine/dbal": "^2.6", + "doctrine/instantiator": "~1.1", "ext-pdo": "*", - "php": "^7.2|^8.0", - "symfony/console": "^3.0|^4.0|^5.0" + "php": "^7.1", + "symfony/console": "~3.0|~4.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", - "phpstan/phpstan": "^0.12.18", - "phpunit/phpunit": "^8.5|^9.4", - "symfony/yaml": "^3.4|^4.0|^5.0", - "vimeo/psalm": "4.1.1" + "doctrine/coding-standard": "^5.0", + "phpunit/phpunit": "^7.5", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" @@ -1205,7 +1080,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7.x-dev" + "dev-master": "2.6.x-dev" } }, "autoload": { @@ -1240,29 +1115,25 @@ } ], "description": "Object-Relational-Mapper for PHP", - "homepage": "https://www.doctrine-project.org/projects/orm.html", + "homepage": "http://www.doctrine-project.org", "keywords": [ "database", "orm" ], - "support": { - "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.8.1" - }, - "time": "2020-12-04T19:53:07+00:00" + "time": "2019-09-20T14:30:26+00:00" }, { "name": "doctrine/persistence", - "version": "2.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/persistence.git", - "reference": "9899c16934053880876b920a3b8b02ed2337ac1d" + "reference": "3da7c9d125591ca83944f477e65ed3d7b4617c48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/persistence/zipball/9899c16934053880876b920a3b8b02ed2337ac1d", - "reference": "9899c16934053880876b920a3b8b02ed2337ac1d", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/3da7c9d125591ca83944f477e65ed3d7b4617c48", + "reference": "3da7c9d125591ca83944f477e65ed3d7b4617c48", "shasum": "" }, "require": { @@ -1270,24 +1141,26 @@ "doctrine/cache": "^1.0", "doctrine/collections": "^1.0", "doctrine/event-manager": "^1.0", - "php": "^7.1 || ^8.0" + "doctrine/reflection": "^1.0", + "php": "^7.1" }, "conflict": { "doctrine/common": "<2.10@dev" }, "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/coding-standard": "^6.0 || ^8.0", - "doctrine/common": "^3.0", - "phpstan/phpstan": "^0.12", - "phpunit/phpunit": "^7.5.20 || ^8.0 || ^9.0", - "vimeo/psalm": "^3.11" + "doctrine/coding-standard": "^5.0", + "phpstan/phpstan": "^0.8", + "phpunit/phpunit": "^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, "autoload": { "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common", - "Doctrine\\Persistence\\": "lib/Doctrine/Persistence" + "Doctrine\\Common\\": "lib/Doctrine/Common" } }, "notification-url": "https://packagist.org/downloads/", @@ -1295,10 +1168,6 @@ "MIT" ], "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -1307,6 +1176,10 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -1329,44 +1202,44 @@ "orm", "persistence" ], - "support": { - "issues": "https://github.com/doctrine/persistence/issues", - "source": "https://github.com/doctrine/persistence/tree/2.1.0" - }, - "time": "2020-10-24T22:13:54+00:00" + "time": "2019-04-23T08:28:24+00:00" }, { - "name": "doctrine/sql-formatter", - "version": "1.1.1", + "name": "doctrine/reflection", + "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/doctrine/sql-formatter.git", - "reference": "56070bebac6e77230ed7d306ad13528e60732871" + "url": "https://github.com/doctrine/reflection.git", + "reference": "02538d3f95e88eb397a5f86274deb2c6175c2ab6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/56070bebac6e77230ed7d306ad13528e60732871", - "reference": "56070bebac6e77230ed7d306ad13528e60732871", + "url": "https://api.github.com/repos/doctrine/reflection/zipball/02538d3f95e88eb397a5f86274deb2c6175c2ab6", + "reference": "02538d3f95e88eb397a5f86274deb2c6175c2ab6", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "doctrine/annotations": "^1.0", + "ext-tokenizer": "*", + "php": "^7.1" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4" + "doctrine/coding-standard": "^4.0", + "doctrine/common": "^2.8", + "phpstan/phpstan": "^0.9.2", + "phpstan/phpstan-phpunit": "^0.9.4", + "phpunit/phpunit": "^7.0", + "squizlabs/php_codesniffer": "^3.0" }, - "bin": [ - "bin/sql-formatter" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\SqlFormatter\\": "src" + "Doctrine\\Common\\": "lib/Doctrine/Common" } }, "notification-url": "https://packagist.org/downloads/", @@ -1375,64 +1248,67 @@ ], "authors": [ { - "name": "Jeremy Dorn", - "email": "jeremy@jeremydorn.com", - "homepage": "http://jeremydorn.com/" + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" } ], - "description": "a PHP SQL highlighting library", - "homepage": "https://github.com/doctrine/sql-formatter/", + "description": "Doctrine Reflection component", + "homepage": "https://www.doctrine-project.org/projects/reflection.html", "keywords": [ - "highlight", - "sql" + "reflection" ], - "support": { - "issues": "https://github.com/doctrine/sql-formatter/issues", - "source": "https://github.com/doctrine/sql-formatter/tree/1.1.x" - }, - "time": "2020-07-30T16:57:33+00:00" + "time": "2018-06-14T14:45:07+00:00" }, { - "name": "friendsofphp/proxy-manager-lts", - "version": "v1.0.3", + "name": "jdorn/sql-formatter", + "version": "v1.2.17", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/proxy-manager-lts.git", - "reference": "121af47c9aee9c03031bdeca3fac0540f59aa5c3" + "url": "https://github.com/jdorn/sql-formatter.git", + "reference": "64990d96e0959dff8e059dfcdc1af130728d92bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/proxy-manager-lts/zipball/121af47c9aee9c03031bdeca3fac0540f59aa5c3", - "reference": "121af47c9aee9c03031bdeca3fac0540f59aa5c3", + "url": "https://api.github.com/repos/jdorn/sql-formatter/zipball/64990d96e0959dff8e059dfcdc1af130728d92bc", + "reference": "64990d96e0959dff8e059dfcdc1af130728d92bc", "shasum": "" }, "require": { - "laminas/laminas-code": "~3.4.1|^4.0", - "php": ">=7.1", - "symfony/filesystem": "^4.4.17|^5.0" - }, - "conflict": { - "laminas/laminas-stdlib": "<3.2.1", - "zendframework/zend-stdlib": "<3.2.1" - }, - "replace": { - "ocramius/proxy-manager": "^2.1" + "php": ">=5.2.4" }, "require-dev": { - "ext-phar": "*", - "symfony/phpunit-bridge": "^5.2" + "phpunit/phpunit": "3.7.*" }, "type": "library", "extra": { - "thanks": { - "name": "ocramius/proxy-manager", - "url": "https://github.com/Ocramius/ProxyManager" + "branch-alias": { + "dev-master": "1.3.x-dev" } }, "autoload": { - "psr-4": { - "ProxyManager\\": "src/ProxyManager" - } + "classmap": [ + "lib" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1440,256 +1316,112 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.io/" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "http://jeremydorn.com/" } ], - "description": "Adding support for a wider range of PHP versions to ocramius/proxy-manager", - "homepage": "https://github.com/FriendsOfPHP/proxy-manager-lts", + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/jdorn/sql-formatter/", "keywords": [ - "aop", - "lazy loading", - "proxy", - "proxy pattern", - "service proxies" - ], - "support": { - "issues": "https://github.com/FriendsOfPHP/proxy-manager-lts/issues", - "source": "https://github.com/FriendsOfPHP/proxy-manager-lts/tree/v1.0.3" - }, - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", - "type": "tidelift" - } + "highlight", + "sql" ], - "time": "2021-01-14T21:52:44+00:00" + "time": "2014-01-12T16:20:24+00:00" }, { - "name": "laminas/laminas-code", - "version": "3.4.1", + "name": "ocramius/proxy-manager", + "version": "2.1.1", "source": { "type": "git", - "url": "https://github.com/laminas/laminas-code.git", - "reference": "1cb8f203389ab1482bf89c0e70a04849bacd7766" + "url": "https://github.com/Ocramius/ProxyManager.git", + "reference": "e18ac876b2e4819c76349de8f78ccc8ef1554cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/1cb8f203389ab1482bf89c0e70a04849bacd7766", - "reference": "1cb8f203389ab1482bf89c0e70a04849bacd7766", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/e18ac876b2e4819c76349de8f78ccc8ef1554cd7", + "reference": "e18ac876b2e4819c76349de8f78ccc8ef1554cd7", "shasum": "" }, "require": { - "laminas/laminas-eventmanager": "^2.6 || ^3.0", - "laminas/laminas-zendframework-bridge": "^1.0", - "php": "^7.1" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0" - }, - "replace": { - "zendframework/zend-code": "self.version" + "ocramius/package-versions": "^1.1.1", + "php": "^7.1.0", + "zendframework/zend-code": "^3.1.0" }, "require-dev": { - "doctrine/annotations": "^1.7", + "couscous/couscous": "^1.5.2", "ext-phar": "*", - "laminas/laminas-coding-standard": "^1.0", - "laminas/laminas-stdlib": "^2.7 || ^3.0", - "phpunit/phpunit": "^7.5.16 || ^8.4" - }, - "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "laminas/laminas-stdlib": "Laminas\\Stdlib component" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4.x-dev", - "dev-develop": "3.5.x-dev", - "dev-dev-4.0": "4.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laminas\\Code\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", - "homepage": "https://laminas.dev", - "keywords": [ - "code", - "laminas" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-code/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-code/issues", - "rss": "https://github.com/laminas/laminas-code/releases.atom", - "source": "https://github.com/laminas/laminas-code" - }, - "time": "2019-12-31T16:28:24+00:00" - }, - { - "name": "laminas/laminas-eventmanager", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-eventmanager.git", - "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", - "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", - "shasum": "" - }, - "require": { - "laminas/laminas-zendframework-bridge": "^1.0", - "php": "^5.6 || ^7.0" - }, - "replace": { - "zendframework/zend-eventmanager": "self.version" - }, - "require-dev": { - "athletic/athletic": "^0.1", - "container-interop/container-interop": "^1.1.0", - "laminas/laminas-coding-standard": "~1.0.0", - "laminas/laminas-stdlib": "^2.7.3 || ^3.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" + "humbug/humbug": "dev-master@DEV", + "nikic/php-parser": "^3.0.4", + "phpbench/phpbench": "^0.12.2", + "phpstan/phpstan": "^0.6.4", + "phpunit/phpunit": "^5.6.4", + "phpunit/phpunit-mock-objects": "^3.4.1", + "squizlabs/php_codesniffer": "^2.7.0" }, "suggest": { - "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", - "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", + "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", + "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)", + "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev", - "dev-develop": "3.3-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { - "psr-4": { - "Laminas\\EventManager\\": "src/" + "psr-0": { + "ProxyManager\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "description": "Trigger and listen to events within a PHP application", - "homepage": "https://laminas.dev", - "keywords": [ - "event", - "eventmanager", - "events", - "laminas" + "MIT" ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-eventmanager/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-eventmanager/issues", - "rss": "https://github.com/laminas/laminas-eventmanager/releases.atom", - "source": "https://github.com/laminas/laminas-eventmanager" - }, - "time": "2019-12-31T16:44:52+00:00" - }, - { - "name": "laminas/laminas-zendframework-bridge", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-zendframework-bridge.git", - "reference": "6ede70583e101030bcace4dcddd648f760ddf642" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/6ede70583e101030bcace4dcddd648f760ddf642", - "reference": "6ede70583e101030bcace4dcddd648f760ddf642", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1 || ^9.3", - "squizlabs/php_codesniffer": "^3.5" - }, - "type": "library", - "extra": { - "laminas": { - "module": "Laminas\\ZendFrameworkBridge" - } - }, - "autoload": { - "files": [ - "src/autoload.php" - ], - "psr-4": { - "Laminas\\ZendFrameworkBridge\\": "src//" + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.io/" } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" ], - "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "homepage": "https://github.com/Ocramius/ProxyManager", "keywords": [ - "ZendFramework", - "autoloading", - "laminas", - "zf" - ], - "support": { - "forum": "https://discourse.laminas.dev/", - "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", - "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", - "source": "https://github.com/laminas/laminas-zendframework-bridge" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" ], - "time": "2020-09-14T14:23:00+00:00" + "time": "2017-05-04T11:12:50+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "2.2.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" }, "type": "library", "extra": { "branch-alias": { - "dev-2.x": "2.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -1716,45 +1448,44 @@ "reflection", "static analysis" ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" + "time": "2018-08-07T13:53:10+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "4.3.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", "shasum": "" }, "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-master": "4.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": "src" + "phpDocumentor\\Reflection\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -1765,44 +1496,38 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" - }, - "time": "2020-09-03T19:13:55+00:00" + "time": "2019-09-12T14:27:41+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.4.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", - "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", + "php": "^7.1", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "*" + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -1821,11 +1546,7 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" - }, - "time": "2020-09-17T18:55:26+00:00" + "time": "2019-08-22T18:11:29+00:00" }, { "name": "psr/cache", @@ -1871,9 +1592,6 @@ "psr", "psr-6" ], - "support": { - "source": "https://github.com/php-fig/cache/tree/master" - }, "time": "2016-08-06T20:24:11+00:00" }, { @@ -1923,74 +1641,20 @@ "container-interop", "psr" ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/master" - }, "time": "2017-02-14T16:28:37+00:00" }, { - "name": "psr/event-dispatcher", - "version": "1.0.0", + "name": "psr/log", + "version": "1.1.2", "source": { "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/log", - "version": "1.1.3", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", "shasum": "" }, "require": { @@ -2024,60 +1688,59 @@ "psr", "psr-3" ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.3" - }, - "time": "2020-03-23T09:12:05+00:00" + "time": "2019-11-01T11:05:21+00:00" }, { "name": "sensio/framework-extra-bundle", - "version": "v5.6.1", + "version": "v5.5.1", "source": { "type": "git", "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git", - "reference": "430d14c01836b77c28092883d195a43ce413ee32" + "reference": "dfc2c4df9f7d465a65c770e9feb578fe071636f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/430d14c01836b77c28092883d195a43ce413ee32", - "reference": "430d14c01836b77c28092883d195a43ce413ee32", + "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/dfc2c4df9f7d465a65c770e9feb578fe071636f7", + "reference": "dfc2c4df9f7d465a65c770e9feb578fe071636f7", "shasum": "" }, "require": { "doctrine/annotations": "^1.0", - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/framework-bundle": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0" + "php": ">=7.1.3", + "symfony/config": "^4.3|^5.0", + "symfony/dependency-injection": "^4.3|^5.0", + "symfony/framework-bundle": "^4.3|^5.0", + "symfony/http-kernel": "^4.3|^5.0" }, "conflict": { - "doctrine/doctrine-cache-bundle": "<1.3.1", - "doctrine/persistence": "<1.3" + "doctrine/doctrine-cache-bundle": "<1.3.1" }, "require-dev": { - "doctrine/dbal": "^2.10|^3.0", "doctrine/doctrine-bundle": "^1.11|^2.0", "doctrine/orm": "^2.5", "nyholm/psr7": "^1.1", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/doctrine-bridge": "^4.4|^5.0", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", + "symfony/browser-kit": "^4.3|^5.0", + "symfony/dom-crawler": "^4.3|^5.0", + "symfony/expression-language": "^4.3|^5.0", + "symfony/finder": "^4.3|^5.0", "symfony/monolog-bridge": "^4.0|^5.0", "symfony/monolog-bundle": "^3.2", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9", + "symfony/phpunit-bridge": "^4.3.5|^5.0", "symfony/psr-http-message-bridge": "^1.1", - "symfony/security-bundle": "^4.4|^5.0", - "symfony/twig-bundle": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0", + "symfony/security-bundle": "^4.3|^5.0", + "symfony/twig-bundle": "^4.3|^5.0", + "symfony/yaml": "^4.3|^5.0", "twig/twig": "^1.34|^2.4|^3.0" }, + "suggest": { + "symfony/expression-language": "", + "symfony/psr-http-message-bridge": "To use the PSR-7 converters", + "symfony/security-bundle": "" + }, "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "5.6.x-dev" + "dev-master": "5.5.x-dev" } }, "autoload": { @@ -2103,38 +1766,38 @@ "annotations", "controllers" ], - "support": { - "issues": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/issues", - "source": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/tree/v5.6.1" - }, - "time": "2020-08-25T19:10:18+00:00" + "time": "2019-10-16T18:54:45+00:00" }, { "name": "symfony/asset", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "54a42aa50f9359d1184bf7e954521b45ca3d5828" + "reference": "5bdbd8878b69e3be16d036890ea3081172ea28c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/54a42aa50f9359d1184bf7e954521b45ca3d5828", - "reference": "54a42aa50f9359d1184bf7e954521b45ca3d5828", + "url": "https://api.github.com/repos/symfony/asset/zipball/5bdbd8878b69e3be16d036890ea3081172ea28c5", + "reference": "5bdbd8878b69e3be16d036890ea3081172ea28c5", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": "^7.1.3" }, "require-dev": { - "symfony/http-client": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0" + "symfony/http-foundation": "~3.4|~4.0", + "symfony/http-kernel": "~3.4|~4.0" }, "suggest": { "symfony/http-foundation": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Asset\\": "" @@ -2157,55 +1820,36 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", + "description": "Symfony Asset Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/asset/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/cache", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "d6aed6c1bbf6f59e521f46437475a0ff4878d388" + "reference": "44277074713f6959df50b857fcb43033cfc0c188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/d6aed6c1bbf6f59e521f46437475a0ff4878d388", - "reference": "d6aed6c1bbf6f59e521f46437475a0ff4878d388", + "url": "https://api.github.com/repos/symfony/cache/zipball/44277074713f6959df50b857fcb43033cfc0c188", + "reference": "44277074713f6959df50b857fcb43033cfc0c188", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "psr/cache": "~1.0", - "psr/log": "^1.1", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1", + "symfony/service-contracts": "^1.1", + "symfony/var-exporter": "^4.2" }, "conflict": { - "doctrine/dbal": "<2.10", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", + "symfony/var-dumper": "<3.4" }, "provide": { "psr/cache-implementation": "1.0", @@ -2214,18 +1858,20 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6", - "doctrine/dbal": "^2.10|^3.0", - "predis/predis": "^1.1", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", "psr/simple-cache": "^1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/filesystem": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/messenger": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.1", + "symfony/var-dumper": "^4.1.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Cache\\": "" @@ -2248,47 +1894,30 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", "homepage": "https://symfony.com", "keywords": [ "caching", "psr6" ], - "support": { - "source": "https://github.com/symfony/cache/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T11:24:50+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/cache-contracts", - "version": "v2.2.0", + "version": "v1.1.7", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "8034ca0b61d4dd967f3698aaa1da2507b631d0cb" + "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/8034ca0b61d4dd967f3698aaa1da2507b631d0cb", - "reference": "8034ca0b61d4dd967f3698aaa1da2507b631d0cb", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/af50d14ada9e4e82cfabfabdc502d144f89be0a1", + "reference": "af50d14ada9e4e82cfabfabdc502d144f89be0a1", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "psr/cache": "^1.0" }, "suggest": { @@ -2297,11 +1926,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "1.1-dev" } }, "autoload": { @@ -2333,60 +1958,46 @@ "interoperability", "standards" ], - "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2019-10-04T21:43:27+00:00" }, { "name": "symfony/config", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab" + "reference": "7b7d5d35a5ba5a62f2c6c69f574e36595e587d11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab", - "reference": "50e0e1314a3b2609d32b6a5a0d0fb5342494c4ab", + "url": "https://api.github.com/repos/symfony/config/zipball/7b7d5d35a5ba5a62f2c6c69f574e36595e587d11", + "reference": "7b7d5d35a5ba5a62f2c6c69f574e36595e587d11", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/filesystem": "^4.4|^5.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.15" + "php": "^7.1.3", + "symfony/filesystem": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<4.4" + "symfony/finder": "<3.4" }, "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/messenger": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/yaml": "^4.4|^5.0" + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/messenger": "~4.1", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Config\\": "" @@ -2409,67 +2020,46 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "description": "Symfony Config Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/config/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/console", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d62ec79478b55036f65e2602e282822b8eaaff0a" + "reference": "487f139d21506279eaf93d4469255daba3d8fb70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d62ec79478b55036f65e2602e282822b8eaaff0a", - "reference": "d62ec79478b55036f65e2602e282822b8eaaff0a", + "url": "https://api.github.com/repos/symfony/console/zipball/487f139d21506279eaf93d4469255daba3d8fb70", + "reference": "487f139d21506279eaf93d4469255daba3d8fb70", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2", - "symfony/string": "^5.1" + "symfony/service-contracts": "^1.1" }, "conflict": { - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", + "symfony/process": "<3.3" }, "provide": { "psr/log-implementation": "1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { "psr/log": "For using the console logger", @@ -2478,6 +2068,11 @@ "symfony/process": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" @@ -2500,80 +2095,43 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", + "description": "Symfony Console Component", "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-10T21:48:14+00:00" }, { - "name": "symfony/dependency-injection", - "version": "v5.2.2", + "name": "symfony/debug", + "version": "v4.3.10", "source": { "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "62f72187be689540385dce6c68a5d4c16f034139" + "url": "https://github.com/symfony/debug.git", + "reference": "1eff904a9596e65aba8992671d4652deb0a22bc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/62f72187be689540385dce6c68a5d4c16f034139", - "reference": "62f72187be689540385dce6c68a5d4c16f034139", + "url": "https://api.github.com/repos/symfony/debug/zipball/1eff904a9596e65aba8992671d4652deb0a22bc2", + "reference": "1eff904a9596e65aba8992671d4652deb0a22bc2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.0", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1.6|^2" + "php": "^7.1.3", + "psr/log": "~1.0" }, "conflict": { - "symfony/config": "<5.1", - "symfony/finder": "<4.4", - "symfony/proxy-manager-bridge": "<4.4", - "symfony/yaml": "<4.4" - }, - "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0" + "symfony/http-kernel": "<3.4" }, "require-dev": { - "symfony/config": "^5.1", - "symfony/expression-language": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "symfony/http-kernel": "~3.4|~4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" + "Symfony\\Component\\Debug\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2593,57 +2151,63 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T12:56:27+00:00" + "time": "2020-01-08T17:19:22+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v2.2.0", + "name": "symfony/dependency-injection", + "version": "v4.3.10", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "468bfb60a60b7caa03e4722c43f5359df47b4349" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", - "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/468bfb60a60b7caa03e4722c43f5359df47b4349", + "reference": "468bfb60a60b7caa03e4722c43f5359df47b4349", "shasum": "" }, "require": { - "php": ">=7.1" + "php": "^7.1.3", + "psr/container": "^1.0", + "symfony/service-contracts": "^1.1.6" + }, + "conflict": { + "symfony/config": "<4.3", + "symfony/finder": "<3.4", + "symfony/proxy-manager-bridge": "<3.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0" + }, + "require-dev": { + "symfony/config": "^4.3", + "symfony/expression-language": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "4.3-dev" } }, "autoload": { - "files": [ - "function.php" + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2652,96 +2216,68 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/master" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2020-01-14T16:43:06+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v5.2.2", + "version": "v4.3.9", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "793cfa617c55c68c492712b773e5e5262d1e97e0" + "reference": "81e2dff413f9d51e1ef3d8552ef7d773973d7b37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/793cfa617c55c68c492712b773e5e5262d1e97e0", - "reference": "793cfa617c55c68c492712b773e5e5262d1e97e0", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/81e2dff413f9d51e1ef3d8552ef7d773973d7b37", + "reference": "81e2dff413f9d51e1ef3d8552ef7d773973d7b37", "shasum": "" }, "require": { "doctrine/event-manager": "~1.0", - "doctrine/persistence": "^2", - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "doctrine/persistence": "~1.0", + "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1|^2" + "symfony/service-contracts": "^1.1" }, "conflict": { - "doctrine/dbal": "<2.10", - "phpunit/phpunit": "<5.4.3", - "symfony/dependency-injection": "<4.4", - "symfony/form": "<5.1", - "symfony/http-kernel": "<5", - "symfony/messenger": "<4.4", - "symfony/property-info": "<5", - "symfony/security-bundle": "<5", - "symfony/security-core": "<5", - "symfony/validator": "<5.2" + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.4", + "symfony/form": "<4.3", + "symfony/http-kernel": "<4.3.7", + "symfony/messenger": "<4.3" }, "require-dev": { - "composer/package-versions-deprecated": "^1.8", - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.6", "doctrine/collections": "~1.0", - "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^2.10|^3.0", - "doctrine/orm": "^2.7.3", - "symfony/cache": "^5.1", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/doctrine-messenger": "^5.1", - "symfony/expression-language": "^4.4|^5.0", - "symfony/form": "^5.1.3", - "symfony/http-kernel": "^5.0", - "symfony/messenger": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.0", - "symfony/property-info": "^5.0", - "symfony/proxy-manager-bridge": "^4.4|^5.0", - "symfony/security-core": "^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/uid": "^5.1", - "symfony/validator": "^5.2", - "symfony/var-dumper": "^4.4|^5.0" + "doctrine/data-fixtures": "1.0.*", + "doctrine/dbal": "~2.4", + "doctrine/orm": "^2.6.3", + "doctrine/reflection": "~1.0", + "symfony/config": "^4.2", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/form": "~4.3", + "symfony/http-kernel": "^4.3.7", + "symfony/messenger": "~4.3", + "symfony/property-access": "~3.4|~4.0", + "symfony/property-info": "~3.4|~4.0", + "symfony/proxy-manager-bridge": "~3.4|~4.0", + "symfony/security-core": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/translation": "~3.4|~4.0", + "symfony/validator": "^3.4.31|^4.3.4" }, "suggest": { "doctrine/data-fixtures": "", @@ -2752,6 +2288,11 @@ "symfony/validator": "" }, "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" @@ -2774,51 +2315,38 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides integration for Doctrine with various Symfony components", + "description": "Symfony Doctrine Bridge", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T11:24:50+00:00" + "time": "2019-12-01T08:34:52+00:00" }, { "name": "symfony/dotenv", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "783f12027c6b40ab0e93d6136d9f642d1d67cd6b" + "reference": "3e41dc2a3c517819b23cb4d1c95f5116182a8dd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/783f12027c6b40ab0e93d6136d9f642d1d67cd6b", - "reference": "783f12027c6b40ab0e93d6136d9f642d1d67cd6b", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/3e41dc2a3c517819b23cb4d1c95f5116182a8dd0", + "reference": "3e41dc2a3c517819b23cb4d1c95f5116182a8dd0", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1" + "php": "^7.1.3" }, "require-dev": { - "symfony/process": "^4.4|^5.0" + "symfony/process": "^3.4.2|^4.0" }, "type": "library", - "autoload": { - "psr-4": { + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Dotenv\\": "" }, "exclude-from-classmap": [ @@ -2846,136 +2374,52 @@ "env", "environment" ], - "support": { - "source": "https://github.com/symfony/dotenv/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:01:46+00:00" - }, - { - "name": "symfony/error-handler", - "version": "v5.2.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/error-handler.git", - "reference": "4fd4a377f7b7ec7c3f3b40346a1411e0a83f9d40" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/4fd4a377f7b7ec7c3f3b40346a1411e0a83f9d40", - "reference": "4fd4a377f7b7ec7c3f3b40346a1411e0a83f9d40", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/log": "^1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/var-dumper": "^4.4|^5.0" - }, - "require-dev": { - "symfony/deprecation-contracts": "^2.1", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\ErrorHandler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to manage errors and ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-08T17:19:22+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367" + "reference": "75f99d7489409207d09c6cd75a6c773ccbb516d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f9760f8074978ad82e2ce854dff79a71fe45367", - "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/75f99d7489409207d09c6cd75a6c773ccbb516d5", + "reference": "75f99d7489409207d09c6cd75a6c773ccbb516d5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^2", - "symfony/polyfill-php80": "^1.15" + "php": "^7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<3.4" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "1.1" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/error-handler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^4.4|^5.0" + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "^3.4|^4.0", + "symfony/service-contracts": "^1.1", + "symfony/stopwatch": "~3.4|~4.0" }, "suggest": { "symfony/dependency-injection": "", "symfony/http-kernel": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" @@ -2998,56 +2442,35 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:36:42+00:00" + "time": "2020-01-09T13:17:05+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.2.0", + "version": "v1.1.7", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2" + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ba7d54483095a198fa51781bc608d17e84dffa2", - "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/event-dispatcher": "^1" + "php": "^7.1.3" }, "suggest": { + "psr/event-dispatcher": "", "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "1.1-dev" } }, "autoload": { @@ -3079,44 +2502,32 @@ "interoperability", "standards" ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2019-09-17T09:54:03+00:00" }, { "name": "symfony/filesystem", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "262d033b57c73e8b59cd6e68a45c528318b15038" + "reference": "fdc0ac5e64f7555818411a17993bb24be4270769" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/262d033b57c73e8b59cd6e68a45c528318b15038", - "reference": "262d033b57c73e8b59cd6e68a45c528318b15038", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/fdc0ac5e64f7555818411a17993bb24be4270769", + "reference": "fdc0ac5e64f7555818411a17993bb24be4270769", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" @@ -3139,45 +2550,33 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides basic utilities for the filesystem", + "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2020-01-21T08:20:29+00:00" }, { "name": "symfony/finder", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "196f45723b5e618bf0e23b97e96d11652696ea9e" + "reference": "8174c13b932c71f10cdd8dfcd8f5e494f1e7003d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/196f45723b5e618bf0e23b97e96d11652696ea9e", - "reference": "196f45723b5e618bf0e23b97e96d11652696ea9e", + "url": "https://api.github.com/repos/symfony/finder/zipball/8174c13b932c71f10cdd8dfcd8f5e494f1e7003d", + "reference": "8174c13b932c71f10cdd8dfcd8f5e494f1e7003d", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": "^7.1.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" @@ -3200,39 +2599,22 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", + "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/flex", - "version": "v1.12.1", + "version": "v1.9.10", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "394f3e4dc03ea2a5448aeedc9658c8b596b1d39f" + "reference": "7335ec033995aa34133e621627333368f260b626" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/394f3e4dc03ea2a5448aeedc9658c8b596b1d39f", - "reference": "394f3e4dc03ea2a5448aeedc9658c8b596b1d39f", + "url": "https://api.github.com/repos/symfony/flex/zipball/7335ec033995aa34133e621627333368f260b626", + "reference": "7335ec033995aa34133e621627333368f260b626", "shasum": "" }, "require": { @@ -3242,7 +2624,6 @@ "require-dev": { "composer/composer": "^1.0.2|^2.0", "symfony/dotenv": "^4.4|^5.0", - "symfony/filesystem": "^4.4|^5.0", "symfony/phpunit-bridge": "^4.4|^5.0", "symfony/process": "^3.4|^4.4|^5.0" }, @@ -3271,7 +2652,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v1.12.1" + "source": "https://github.com/symfony/flex/tree/v1.9.10" }, "funding": [ { @@ -3287,59 +2668,54 @@ "type": "tidelift" } ], - "time": "2021-02-02T16:29:45+00:00" + "time": "2020-10-14T17:41:54+00:00" }, { "name": "symfony/form", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "b9fc4092f5c138ec89604ee5faa9cb0c12e2b601" + "reference": "05fac4992a100642806a50cc0c84fb4a8a326c14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/b9fc4092f5c138ec89604ee5faa9cb0c12e2b601", - "reference": "b9fc4092f5c138ec89604ee5faa9cb0c12e2b601", + "url": "https://api.github.com/repos/symfony/form/zipball/05fac4992a100642806a50cc0c84fb4a8a326c14", + "reference": "05fac4992a100642806a50cc0c84fb4a8a326c14", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/intl": "^4.4|^5.0", - "symfony/options-resolver": "^5.1", + "php": "^7.1.3", + "symfony/event-dispatcher": "^4.3", + "symfony/intl": "^4.3", + "symfony/options-resolver": "~4.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/property-access": "^5.0.8", - "symfony/service-contracts": "^1.1|^2" + "symfony/property-access": "~3.4|~4.0", + "symfony/service-contracts": "~1.1" }, "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<4.4", - "symfony/doctrine-bridge": "<4.4", - "symfony/error-handler": "<4.4.5", - "symfony/framework-bundle": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/intl": "<4.4", - "symfony/translation": "<4.4", - "symfony/translation-contracts": "<1.1.7", - "symfony/twig-bridge": "<4.4" + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<4.3", + "symfony/dependency-injection": "<3.4", + "symfony/doctrine-bridge": "<3.4", + "symfony/framework-bundle": "<3.4", + "symfony/http-kernel": "<4.3", + "symfony/intl": "<4.3", + "symfony/translation": "<4.2", + "symfony/twig-bridge": "<3.4.5|<4.0.5,>=4.0" }, "require-dev": { "doctrine/collections": "~1.0", - "symfony/config": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/security-csrf": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/validator": "^4.4.17|^5.1.9", - "symfony/var-dumper": "^4.4|^5.0" + "symfony/config": "~3.4|~4.0", + "symfony/console": "^4.3", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/http-kernel": "~4.3", + "symfony/security-csrf": "~3.4|~4.0", + "symfony/translation": "~4.2", + "symfony/validator": "^3.4.31|^4.3.4", + "symfony/var-dumper": "^4.3" }, "suggest": { "symfony/security-csrf": "For protecting forms against CSRF attacks.", @@ -3347,6 +2723,11 @@ "symfony/validator": "For form validation." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Form\\": "" @@ -3369,120 +2750,90 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Allows to easily create, process and reuse HTML forms", + "description": "Symfony Form Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/form/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T12:56:27+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/framework-bundle", - "version": "v5.2.2", + "version": "v4.3.9", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "ff455b2afd3f98237d4131ffebe190e59cc0f011" + "reference": "39fa21a555c5b451222b37d59925f5d4704ba0c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/ff455b2afd3f98237d4131ffebe190e59cc0f011", - "reference": "ff455b2afd3f98237d4131ffebe190e59cc0f011", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/39fa21a555c5b451222b37d59925f5d4704ba0c0", + "reference": "39fa21a555c5b451222b37d59925f5d4704ba0c0", "shasum": "" }, "require": { "ext-xml": "*", - "php": ">=7.2.5", - "symfony/cache": "^5.2", - "symfony/config": "^5.0", - "symfony/dependency-injection": "^5.2", - "symfony/deprecation-contracts": "^2.1", - "symfony/error-handler": "^4.4.1|^5.0.1", - "symfony/event-dispatcher": "^5.1", - "symfony/filesystem": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/http-foundation": "^5.2.1", - "symfony/http-kernel": "^5.2.1", + "php": "^7.1.3", + "symfony/cache": "^4.3.4", + "symfony/config": "^4.3.4", + "symfony/debug": "~4.0", + "symfony/dependency-injection": "^4.3", + "symfony/filesystem": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/http-foundation": "^4.3", + "symfony/http-kernel": "^4.3.4", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/routing": "^5.2" + "symfony/routing": "^4.3" }, "conflict": { - "doctrine/persistence": "<1.3", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "phpunit/phpunit": "<5.4.3", - "symfony/asset": "<5.1", - "symfony/browser-kit": "<4.4", - "symfony/console": "<5.2", - "symfony/dom-crawler": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/form": "<5.2", - "symfony/http-client": "<4.4", - "symfony/lock": "<4.4", - "symfony/mailer": "<5.2", - "symfony/messenger": "<4.4", - "symfony/mime": "<4.4", - "symfony/property-access": "<5.2", - "symfony/property-info": "<4.4", - "symfony/serializer": "<5.2", - "symfony/stopwatch": "<4.4", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<4.4", - "symfony/twig-bundle": "<4.4", - "symfony/validator": "<5.2", - "symfony/web-profiler-bundle": "<4.4", - "symfony/workflow": "<5.2" + "phpdocumentor/reflection-docblock": "<3.0", + "phpdocumentor/type-resolver": "<0.2.1", + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/asset": "<3.4", + "symfony/browser-kit": "<4.3", + "symfony/console": "<4.3", + "symfony/dom-crawler": "<4.3", + "symfony/dotenv": "<4.2", + "symfony/form": "<4.3.5", + "symfony/messenger": "<4.3.6", + "symfony/property-info": "<3.4", + "symfony/serializer": "<4.2", + "symfony/stopwatch": "<3.4", + "symfony/translation": "<4.3.6", + "symfony/twig-bridge": "<4.1.1", + "symfony/validator": "<4.1", + "symfony/workflow": "<4.3.6" }, "require-dev": { - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", - "doctrine/persistence": "^1.3|^2.0", - "paragonie/sodium_compat": "^1.8", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^5.1", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/console": "^5.2", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/dotenv": "^5.1", - "symfony/expression-language": "^4.4|^5.0", - "symfony/form": "^5.2", - "symfony/http-client": "^4.4|^5.0", - "symfony/lock": "^4.4|^5.0", - "symfony/mailer": "^5.2", - "symfony/messenger": "^5.2", - "symfony/mime": "^4.4|^5.0", + "fig/link-util": "^1.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "symfony/asset": "~3.4|~4.0", + "symfony/browser-kit": "^4.3", + "symfony/console": "^4.3.4", + "symfony/css-selector": "~3.4|~4.0", + "symfony/dom-crawler": "^4.3", + "symfony/expression-language": "~3.4|~4.0", + "symfony/form": "^4.3.5", + "symfony/http-client": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/mailer": "^4.3", + "symfony/messenger": "^4.3.6", + "symfony/mime": "^4.3", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^4.4|^5.0", - "symfony/property-info": "^4.4|^5.0", - "symfony/security-bundle": "^5.1", - "symfony/security-csrf": "^4.4|^5.0", - "symfony/security-http": "^4.4|^5.0", - "symfony/serializer": "^5.2", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/string": "^5.0", - "symfony/translation": "^5.0", - "symfony/twig-bundle": "^4.4|^5.0", - "symfony/validator": "^5.2", - "symfony/web-link": "^4.4|^5.0", - "symfony/workflow": "^5.2", - "symfony/yaml": "^4.4|^5.0", - "twig/twig": "^2.10|^3.0" + "symfony/process": "~3.4|~4.0", + "symfony/property-info": "~3.4|~4.0", + "symfony/security-csrf": "~3.4|~4.0", + "symfony/security-http": "~3.4|~4.0", + "symfony/serializer": "^4.3", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/templating": "~3.4|~4.0", + "symfony/translation": "^4.3.7", + "symfony/twig-bundle": "~2.8|~3.2|~4.0", + "symfony/validator": "^4.1", + "symfony/var-dumper": "^4.3", + "symfony/web-link": "~3.4|~4.0", + "symfony/workflow": "^4.3.6", + "symfony/yaml": "~3.4|~4.0", + "twig/twig": "~1.41|~2.10" }, "suggest": { "ext-apcu": "For best performance of the system caches", @@ -3495,6 +2846,11 @@ "symfony/yaml": "For using the debug:config and lint:yaml commands" }, "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" @@ -3517,70 +2873,47 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", + "description": "Symfony FrameworkBundle", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T11:19:04+00:00" + "time": "2019-11-28T11:39:15+00:00" }, { "name": "symfony/http-client", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "22cb1a7844fff206cc5186409776e78865405ea5" + "reference": "aa662bb26b8fe6f93620a32779fef3ab958ff840" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/22cb1a7844fff206cc5186409776e78865405ea5", - "reference": "22cb1a7844fff206cc5186409776e78865405ea5", + "url": "https://api.github.com/repos/symfony/http-client/zipball/aa662bb26b8fe6f93620a32779fef3ab958ff840", + "reference": "aa662bb26b8fe6f93620a32779fef3ab958ff840", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "psr/log": "^1.0", - "symfony/http-client-contracts": "^2.2", + "symfony/http-client-contracts": "^1.1.7", "symfony/polyfill-php73": "^1.11", - "symfony/polyfill-php80": "^1.15", "symfony/service-contracts": "^1.0|^2" }, "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", "psr/http-client-implementation": "1.0", "symfony/http-client-implementation": "1.1" }, "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", - "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4", "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/http-kernel": "^4.4.13|^5.1.5", - "symfony/process": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0" + "symfony/http-kernel": "^4.3", + "symfony/process": "^4.2" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\HttpClient\\": "" @@ -3603,56 +2936,34 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "description": "Symfony HttpClient component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-client/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-13T17:23:05+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v2.3.1", + "version": "v1.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "41db680a15018f9c1d4b23516059633ce280ca33" + "reference": "088bae75cfa2ec5eb6d33dce17dbd8613150ce6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41db680a15018f9c1d4b23516059633ce280ca33", - "reference": "41db680a15018f9c1d4b23516059633ce280ca33", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/088bae75cfa2ec5eb6d33dce17dbd8613150ce6e", + "reference": "088bae75cfa2ec5eb6d33dce17dbd8613150ce6e", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": "^7.1.3" }, "suggest": { "symfony/http-client-implementation": "" }, "type": "library", "extra": { - "branch-version": "2.3", "branch-alias": { - "dev-main": "2.3-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "1.1-dev" } }, "autoload": { @@ -3684,55 +2995,37 @@ "interoperability", "standards" ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.3.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-14T17:08:19+00:00" + "time": "2019-11-07T12:44:51+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "16dfa5acf8103f0394d447f8eea3ea49f9e50855" + "reference": "d7fde626946d8d1595bae553e2f5b6f451ed1966" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/16dfa5acf8103f0394d447f8eea3ea49f9e50855", - "reference": "16dfa5acf8103f0394d447f8eea3ea49f9e50855", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d7fde626946d8d1595bae553e2f5b6f451ed1966", + "reference": "d7fde626946d8d1595bae553e2f5b6f451ed1966", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.15" + "php": "^7.1.3", + "symfony/mime": "^4.3", + "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0" - }, - "suggest": { - "symfony/mime": "To use the file extension guesser" + "symfony/expression-language": "~3.4|~4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" @@ -3755,96 +3048,76 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Defines an object-oriented layer for the HTTP specification", + "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T11:19:04+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "831b51e9370ece0febd0950dd819c63f996721c7" + "reference": "fe7ab3c0abc04784a4f7ab511a6d876bc81cb4d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/831b51e9370ece0febd0950dd819c63f996721c7", - "reference": "831b51e9370ece0febd0950dd819c63f996721c7", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/fe7ab3c0abc04784a4f7ab511a6d876bc81cb4d4", + "reference": "fe7ab3c0abc04784a4f7ab511a6d876bc81cb4d4", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "psr/log": "~1.0", - "symfony/deprecation-contracts": "^2.1", - "symfony/error-handler": "^4.4|^5.0", - "symfony/event-dispatcher": "^5.0", - "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.15" + "symfony/debug": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/http-foundation": "^4.1.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php73": "^1.9" }, "conflict": { - "symfony/browser-kit": "<4.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.1.8", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", - "twig/twig": "<2.13" + "symfony/browser-kit": "<4.3", + "symfony/config": "<3.4", + "symfony/dependency-injection": "<4.3", + "symfony/translation": "<4.2", + "symfony/var-dumper": "<4.1.1", + "twig/twig": "<1.34|<2.4,>=2" }, "provide": { "psr/log-implementation": "1.0" }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/config": "^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dependency-injection": "^5.1.8", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "symfony/routing": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^2.13|^3.0.4" + "symfony/browser-kit": "^4.3", + "symfony/config": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/css-selector": "~3.4|~4.0", + "symfony/dependency-injection": "^4.3", + "symfony/dom-crawler": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/routing": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/templating": "~3.4|~4.0", + "symfony/translation": "~4.2", + "symfony/translation-contracts": "^1.1", + "symfony/var-dumper": "^4.1.1", + "twig/twig": "^1.34|^2.4" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "" + "symfony/dependency-injection": "", + "symfony/var-dumper": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\HttpKernel\\": "" @@ -3867,53 +3140,98 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a structured process for converting a Request into a Response", + "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.2.2" + "time": "2020-01-21T13:13:44+00:00" + }, + { + "name": "symfony/inflector", + "version": "v4.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/inflector.git", + "reference": "8c699257379098d26fa400edad29f703b380efcf" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/inflector/zipball/8c699257379098d26fa400edad29f703b380efcf", + "reference": "8c699257379098d26fa400edad29f703b380efcf", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Inflector\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://github.com/fabpot", - "type": "github" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "time": "2021-01-27T14:45:46+00:00" + "description": "Symfony Inflector Component", + "homepage": "https://symfony.com", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string", + "symfony", + "words" + ], + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/intl", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "930f17689729cc47d2ce18be21ed403bcbeeb6a9" + "reference": "2d139d02ddae582c382d30cccd2ee4c814043518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/930f17689729cc47d2ce18be21ed403bcbeeb6a9", - "reference": "930f17689729cc47d2ce18be21ed403bcbeeb6a9", + "url": "https://api.github.com/repos/symfony/intl/zipball/2d139d02ddae582c382d30cccd2ee4c814043518", + "reference": "2d139d02ddae582c382d30cccd2ee4c814043518", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/polyfill-php80": "^1.15" + "php": "^7.1.3", + "symfony/polyfill-intl-icu": "~1.0" }, "require-dev": { - "symfony/filesystem": "^4.4|^5.0" + "symfony/filesystem": "~3.4|~4.0" }, "suggest": { "ext-intl": "to use the component with locales other than \"en\"" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Intl\\": "" @@ -3947,7 +3265,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a PHP replacement layer for the C intl extension that includes additional data from the ICU library", + "description": "A PHP replacement layer for the C intl extension that includes additional data from the ICU library.", "homepage": "https://symfony.com", "keywords": [ "i18n", @@ -3957,46 +3275,90 @@ "l10n", "localization" ], - "support": { - "source": "https://github.com/symfony/intl/tree/v5.2.2" + "time": "2020-01-04T12:24:57+00:00" + }, + { + "name": "symfony/mime", + "version": "v4.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "50f65ca2a6c33702728024d33e4b9461f67623c4" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/50f65ca2a6c33702728024d33e4b9461f67623c4", + "reference": "50f65ca2a6c33702728024d33e4b9461f67623c4", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "symfony/dependency-injection": "~3.4|^4.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://github.com/fabpot", - "type": "github" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "time": "2021-01-27T10:01:46+00:00" + "description": "A library to manipulate MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "time": "2020-01-01T11:51:43+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce" + "reference": "3438c6fe65a9794b0e9f3686d0e3771412a2c47a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce", - "reference": "5d0f633f9bbfcf7ec642a2b5037268e61b0a62ce", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/3438c6fe65a9794b0e9f3686d0e3771412a2c47a", + "reference": "3438c6fe65a9794b0e9f3686d0e3771412a2c47a", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.15" + "php": "^7.1.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" @@ -4019,51 +3381,34 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an improved replacement for the array_replace PHP function", + "description": "Symfony OptionsResolver Component", "homepage": "https://symfony.com", "keywords": [ "config", "configuration", "options" ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T12:56:27+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/orm-pack", - "version": "v1.1.0", + "version": "v1.0.7", "source": { "type": "git", "url": "https://github.com/symfony/orm-pack.git", - "reference": "7dd2ed9ba6d7af79f90bdc77522605d40463e533" + "reference": "c57f5e05232ca40626eb9fa52a32bc8565e9231c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/orm-pack/zipball/7dd2ed9ba6d7af79f90bdc77522605d40463e533", - "reference": "7dd2ed9ba6d7af79f90bdc77522605d40463e533", + "url": "https://api.github.com/repos/symfony/orm-pack/zipball/c57f5e05232ca40626eb9fa52a32bc8565e9231c", + "reference": "c57f5e05232ca40626eb9fa52a32bc8565e9231c", "shasum": "" }, "require": { - "composer/package-versions-deprecated": "*", - "doctrine/doctrine-bundle": "^2", - "doctrine/doctrine-migrations-bundle": "^2", - "doctrine/orm": "^2" + "doctrine/doctrine-bundle": "^1.6.10|^2.0", + "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", + "doctrine/orm": "^2.5.11", + "php": "^7.0" }, "type": "symfony-pack", "notification-url": "https://packagist.org/downloads/", @@ -4071,42 +3416,25 @@ "MIT" ], "description": "A pack for the Doctrine ORM", - "support": { - "issues": "https://github.com/symfony/orm-pack/issues", - "source": "https://github.com/symfony/orm-pack/tree/master" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-07-08T14:31:54+00:00" + "time": "2019-10-18T05:41:09+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.22.0", + "name": "symfony/polyfill-intl-icu", + "version": "v1.13.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "267a9adeb8ecb8071040a740930e077cdfb987af" + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "b3dffd68afa61ca70f2327f2dd9bbeb6aa53d70b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/267a9adeb8ecb8071040a740930e077cdfb987af", - "reference": "267a9adeb8ecb8071040a740930e077cdfb987af", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/b3dffd68afa61ca70f2327f2dd9bbeb6aa53d70b", + "reference": "b3dffd68afa61ca70f2327f2dd9bbeb6aa53d70b", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=5.3.3", + "symfony/intl": "~2.3|~3.0|~4.0|~5.0" }, "suggest": { "ext-intl": "For best performance" @@ -4114,17 +3442,10 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "1.13-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, "files": [ "bootstrap.php" ] @@ -4143,77 +3464,52 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony polyfill for intl's ICU-related data and classes", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "grapheme", + "icu", "intl", "polyfill", "portable", "shim" ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { - "name": "symfony/polyfill-intl-icu", - "version": "v1.22.0", + "name": "symfony/polyfill-intl-idn", + "version": "v1.13.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "b2b1e732a6c039f1a3ea3414b3379a2433e183d6" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/b2b1e732a6c039f1a3ea3414b3379a2433e183d6", - "reference": "b2b1e732a6c039f1a3ea3414b3379a2433e183d6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" }, "suggest": { - "ext-intl": "For best performance and support of other locales than \"en\"" + "ext-intl": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "1.13-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Icu\\": "" + "Symfony\\Polyfill\\Intl\\Idn\\": "" }, - "classmap": [ - "Resources/stubs" - ], - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4222,82 +3518,58 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Laurent Bassin", + "email": "laurent@bassin.info" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's ICU-related data and classes", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "icu", + "idn", "intl", "polyfill", "portable", "shim" ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.22.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.13.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=5.3.3" }, "suggest": { - "ext-intl": "For best performance" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "1.13-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Polyfill\\Mbstring\\": "" }, "files": [ "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -4314,68 +3586,43 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", + "mbstring", "polyfill", "portable", "shim" ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T17:09:11+00:00" + "time": "2019-11-27T14:18:11+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.22.0", + "name": "symfony/polyfill-php72", + "version": "v1.13.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", - "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-mbstring": "For best performance" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "1.13-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Symfony\\Polyfill\\Php72\\": "" }, "files": [ "bootstrap.php" @@ -4395,59 +3642,37 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "1.13-dev" } }, "autoload": { @@ -4483,135 +3708,38 @@ "portable", "shim" ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.22.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2019-11-27T16:25:15+00:00" }, { "name": "symfony/property-access", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "3af8ed262bd3217512a13b023981fe68f36ad5f3" + "reference": "28ecead27bd17937b3f904396b026a8e3915d0cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/3af8ed262bd3217512a13b023981fe68f36ad5f3", - "reference": "3af8ed262bd3217512a13b023981fe68f36ad5f3", + "url": "https://api.github.com/repos/symfony/property-access/zipball/28ecead27bd17937b3f904396b026a8e3915d0cd", + "reference": "28ecead27bd17937b3f904396b026a8e3915d0cd", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15", - "symfony/property-info": "^5.2" + "php": "^7.1.3", + "symfony/inflector": "~3.4|~4.0" }, "require-dev": { - "symfony/cache": "^4.4|^5.0" + "symfony/cache": "~3.4|~4.0" }, "suggest": { "psr/cache-implementation": "To cache access methods." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\PropertyAccess\\": "" @@ -4634,7 +3762,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "description": "Symfony PropertyAccess Component", "homepage": "https://symfony.com", "keywords": [ "access", @@ -4647,56 +3775,37 @@ "property path", "reflection" ], - "support": { - "source": "https://github.com/symfony/property-access/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/property-info", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "4e4f368c3737b1c175d66f4fc0b99a5bcd161a77" + "reference": "169aafe8f2a01ec50fb324f5d24bbd61a5799df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/4e4f368c3737b1c175d66f4fc0b99a5bcd161a77", - "reference": "4e4f368c3737b1c175d66f4fc0b99a5bcd161a77", + "url": "https://api.github.com/repos/symfony/property-info/zipball/169aafe8f2a01ec50fb324f5d24bbd61a5799df1", + "reference": "169aafe8f2a01ec50fb324f5d24bbd61a5799df1", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15", - "symfony/string": "^5.1" + "php": "^7.1.3", + "symfony/inflector": "~3.4|~4.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4" + "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", + "phpdocumentor/type-resolver": "<0.3.0", + "symfony/dependency-injection": "<3.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/cache": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" + "doctrine/annotations": "~1.7", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "symfony/cache": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/serializer": "~3.4|~4.0" }, "suggest": { "phpdocumentor/reflection-docblock": "To use the PHPDoc", @@ -4705,6 +3814,11 @@ "symfony/serializer": "To use Serializer metadata" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\PropertyInfo\\": "" @@ -4727,7 +3841,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Extracts information about PHP class' properties using metadata of popular sources", + "description": "Symfony Property Info Component", "homepage": "https://symfony.com", "keywords": [ "doctrine", @@ -4737,57 +3851,38 @@ "type", "validator" ], - "support": { - "source": "https://github.com/symfony/property-info/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/routing", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "348b5917e56546c6d96adbf21d7f92c9ef563661" + "reference": "6cc4b6a92e3c623b2c7e56180728839b0caf8564" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/348b5917e56546c6d96adbf21d7f92c9ef563661", - "reference": "348b5917e56546c6d96adbf21d7f92c9ef563661", + "url": "https://api.github.com/repos/symfony/routing/zipball/6cc4b6a92e3c623b2c7e56180728839b0caf8564", + "reference": "6cc4b6a92e3c623b2c7e56180728839b0caf8564", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15" + "php": "^7.1.3" }, "conflict": { - "symfony/config": "<5.0", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" + "symfony/config": "<4.2", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "~1.2", "psr/log": "~1.0", - "symfony/config": "^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", @@ -4797,6 +3892,11 @@ "symfony/yaml": "For using the YAML loader" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Routing\\": "" @@ -4819,7 +3919,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Maps an HTTP request to a set of configuration variables", + "description": "Symfony Routing Component", "homepage": "https://symfony.com", "keywords": [ "router", @@ -4827,81 +3927,66 @@ "uri", "url" ], - "support": { - "source": "https://github.com/symfony/routing/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-08T14:00:15+00:00" }, { "name": "symfony/security-bundle", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "51854aa28585d196e60519271338aecad86f95f5" + "reference": "1cc02bd8e44eef0bd4ecfd53a8b4d7b26f675d85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/51854aa28585d196e60519271338aecad86f95f5", - "reference": "51854aa28585d196e60519271338aecad86f95f5", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/1cc02bd8e44eef0bd4ecfd53a8b4d7b26f675d85", + "reference": "1cc02bd8e44eef0bd4ecfd53a8b4d7b26f675d85", "shasum": "" }, "require": { "ext-xml": "*", - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^5.2", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher": "^5.1", - "symfony/http-kernel": "^5.0", - "symfony/polyfill-php80": "^1.15", - "symfony/security-core": "^5.2", - "symfony/security-csrf": "^4.4|^5.0", - "symfony/security-guard": "^5.2", - "symfony/security-http": "^5.2" + "php": "^7.1.3", + "symfony/config": "^4.2", + "symfony/dependency-injection": "^4.2", + "symfony/http-kernel": "^4.3", + "symfony/security-core": "~4.3", + "symfony/security-csrf": "~4.2", + "symfony/security-guard": "~4.2", + "symfony/security-http": "~4.3.10|^4.4.3" }, "conflict": { - "symfony/browser-kit": "<4.4", - "symfony/console": "<4.4", - "symfony/framework-bundle": "<4.4", - "symfony/ldap": "<4.4", - "symfony/twig-bundle": "<4.4" + "symfony/browser-kit": "<4.2", + "symfony/console": "<3.4", + "symfony/framework-bundle": "<4.3.4", + "symfony/twig-bundle": "<4.2", + "symfony/var-dumper": "<3.4" }, "require-dev": { - "doctrine/doctrine-bundle": "^2.0", - "symfony/asset": "^4.4|^5.0", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/form": "^4.4|^5.0", - "symfony/framework-bundle": "^5.2", - "symfony/process": "^4.4|^5.0", - "symfony/rate-limiter": "^5.2", - "symfony/serializer": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/twig-bridge": "^4.4|^5.0", - "symfony/twig-bundle": "^4.4|^5.0", - "symfony/validator": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0", - "twig/twig": "^2.13|^3.0.4" + "doctrine/doctrine-bundle": "~1.5", + "symfony/asset": "~3.4|~4.0", + "symfony/browser-kit": "~4.2", + "symfony/console": "~3.4|~4.0", + "symfony/css-selector": "~3.4|~4.0", + "symfony/dom-crawler": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/form": "~3.4|~4.0", + "symfony/framework-bundle": "^4.3.4", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "symfony/translation": "~3.4|~4.0", + "symfony/twig-bridge": "~3.4|~4.0", + "symfony/twig-bundle": "~4.2", + "symfony/validator": "~3.4|~4.0", + "symfony/var-dumper": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0", + "twig/twig": "~1.41|~2.10" }, "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Bundle\\SecurityBundle\\": "" @@ -4924,63 +4009,41 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", + "description": "Symfony SecurityBundle", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/security-bundle/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-08T17:19:22+00:00" }, { "name": "symfony/security-core", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "6c7314eac7c4870e6316fa9c277ebf4d393063ca" + "reference": "b22f9e7626c2d48b1431bd3db4bc9d7fed3bd0a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/6c7314eac7c4870e6316fa9c277ebf4d393063ca", - "reference": "6c7314eac7c4870e6316fa9c277ebf4d393063ca", + "url": "https://api.github.com/repos/symfony/security-core/zipball/b22f9e7626c2d48b1431bd3db4bc9d7fed3bd0a7", + "reference": "b22f9e7626c2d48b1431bd3db4bc9d7fed3bd0a7", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/event-dispatcher-contracts": "^1.1|^2", - "symfony/polyfill-php80": "^1.15", - "symfony/service-contracts": "^1.1.6|^2" + "php": "^7.1.3", + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/service-contracts": "^1.1" }, "conflict": { - "symfony/event-dispatcher": "<4.4", - "symfony/ldap": "<4.4", - "symfony/security-guard": "<4.4", - "symfony/validator": "<5.2" + "symfony/event-dispatcher": "<4.3", + "symfony/security-guard": "<4.3" }, "require-dev": { "psr/container": "^1.0", "psr/log": "~1.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/ldap": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/validator": "^5.2" + "symfony/event-dispatcher": "^4.3", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/ldap": "~3.4|~4.0", + "symfony/validator": "^3.4.31|^4.3.4" }, "suggest": { "psr/container-implementation": "To instantiate the Security class", @@ -4991,6 +4054,11 @@ "symfony/validator": "For using the user password constraint" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Security\\Core\\": "" @@ -5015,53 +4083,41 @@ ], "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/security-core/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T12:56:27+00:00" + "time": "2020-01-21T11:08:18+00:00" }, { "name": "symfony/security-csrf", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "e22ef49d5d3773014942f3dfe301b168a4a833dc" + "reference": "9e435026ab45f073880d1fbe0e1b17e7df6bf642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/e22ef49d5d3773014942f3dfe301b168a4a833dc", - "reference": "e22ef49d5d3773014942f3dfe301b168a4a833dc", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/9e435026ab45f073880d1fbe0e1b17e7df6bf642", + "reference": "9e435026ab45f073880d1fbe0e1b17e7df6bf642", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/security-core": "^4.4|^5.0" + "php": "^7.1.3", + "symfony/security-core": "~3.4|~4.0" }, "conflict": { - "symfony/http-foundation": "<4.4" + "symfony/http-foundation": "<3.4" }, "require-dev": { - "symfony/http-foundation": "^4.4|^5.0" + "symfony/http-foundation": "~3.4|~4.0" }, "suggest": { "symfony/http-foundation": "For using the class SessionTokenStorage." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Security\\Csrf\\": "" @@ -5086,49 +4142,36 @@ ], "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/security-csrf/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/security-guard", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/security-guard.git", - "reference": "a191352047f2ea0d927c62e1a2f261cf906d1bde" + "reference": "5d87ee4ffa5aa6e594008fa3cc65bc8f86ad6438" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-guard/zipball/a191352047f2ea0d927c62e1a2f261cf906d1bde", - "reference": "a191352047f2ea0d927c62e1a2f261cf906d1bde", + "url": "https://api.github.com/repos/symfony/security-guard/zipball/5d87ee4ffa5aa6e594008fa3cc65bc8f86ad6438", + "reference": "5d87ee4ffa5aa6e594008fa3cc65bc8f86ad6438", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15", - "symfony/security-core": "^5.0", - "symfony/security-http": "^4.4.1|^5.0.1" + "php": "^7.1.3", + "symfony/security-core": "~3.4.22|^4.2.3", + "symfony/security-http": "^4.3" }, "require-dev": { "psr/log": "~1.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Security\\Guard\\": "" @@ -5153,65 +4196,47 @@ ], "description": "Symfony Security Component - Guard", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/security-guard/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-04T18:57:41+00:00" }, { "name": "symfony/security-http", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "b2289c9c6837d627df12508bda91d74d6fe0e03e" + "reference": "2ea4960f4c402e528a855f43b32eda412b0e2077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/b2289c9c6837d627df12508bda91d74d6fe0e03e", - "reference": "b2289c9c6837d627df12508bda91d74d6fe0e03e", + "url": "https://api.github.com/repos/symfony/security-http/zipball/2ea4960f4c402e528a855f43b32eda412b0e2077", + "reference": "2ea4960f4c402e528a855f43b32eda412b0e2077", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/http-foundation": "^5.2", - "symfony/http-kernel": "^5.2", - "symfony/polyfill-php80": "^1.15", - "symfony/property-access": "^4.4|^5.0", - "symfony/security-core": "^5.2" + "php": "^7.1.3", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/http-kernel": "^4.3", + "symfony/property-access": "~3.4|~4.0", + "symfony/security-core": "^4.3" }, "conflict": { - "symfony/event-dispatcher": "<4.3", - "symfony/security-csrf": "<4.4" + "symfony/security-csrf": "<3.4.11|~4.0,<4.0.11" }, "require-dev": { "psr/log": "~1.0", - "symfony/cache": "^4.4|^5.0", - "symfony/rate-limiter": "^5.2", - "symfony/routing": "^4.4|^5.0", - "symfony/security-csrf": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0" + "symfony/routing": "~3.4|~4.0", + "symfony/security-csrf": "^3.4.11|^4.0.11" }, "suggest": { "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs", "symfony/security-csrf": "For using tokens to protect authentication/logout attempts" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Security\\Http\\": "" @@ -5236,84 +4261,62 @@ ], "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/security-http/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T11:24:50+00:00" + "time": "2020-01-21T11:08:18+00:00" }, { "name": "symfony/serializer", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "4218dd0902543dc454b2eac0db37044f187283d7" + "reference": "cd4f545209e1f3d408b5adda729c59bfd714a1a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/4218dd0902543dc454b2eac0db37044f187283d7", - "reference": "4218dd0902543dc454b2eac0db37044f187283d7", + "url": "https://api.github.com/repos/symfony/serializer/zipball/cd4f545209e1f3d408b5adda729c59bfd714a1a5", + "reference": "cd4f545209e1f3d408b5adda729c59bfd714a1a5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.15" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4", - "symfony/property-access": "<4.4", - "symfony/property-info": "<4.4", - "symfony/yaml": "<4.4" + "phpdocumentor/type-resolver": "<0.2.1", + "symfony/dependency-injection": "<3.4", + "symfony/property-access": "<3.4", + "symfony/property-info": "<3.4", + "symfony/yaml": "<3.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "~1.0", "doctrine/cache": "~1.0", - "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^4.4|^5.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/error-handler": "^4.4|^5.0", - "symfony/filesystem": "^4.4|^5.0", - "symfony/form": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/property-access": "^4.4.9|^5.0.9", - "symfony/property-info": "^4.4|^5.0", - "symfony/uid": "^5.1", - "symfony/validator": "^4.4|^5.0", - "symfony/var-exporter": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" + "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "symfony/cache": "~3.4|~4.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/property-access": "~3.4|~4.0", + "symfony/property-info": "^3.4.13|~4.0", + "symfony/validator": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", "doctrine/cache": "For using the default cached annotation reader and metadata cache.", "psr/cache-implementation": "For using the metadata cache.", "symfony/config": "For using the XML mapping loader.", - "symfony/mime": "For using a MIME type guesser within the DataUriNormalizer.", + "symfony/http-foundation": "For using a MIME type guesser within the DataUriNormalizer.", "symfony/property-access": "For using the ObjectNormalizer.", "symfony/property-info": "To deserialize relations.", - "symfony/var-exporter": "For using the metadata compiler.", "symfony/yaml": "For using the default YAML mapping loader." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Serializer\\": "" @@ -5336,44 +4339,28 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "description": "Symfony Serializer Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/serializer/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T11:32:03+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/serializer-pack", - "version": "v1.0.4", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/symfony/serializer-pack.git", - "reference": "61173947057d5e1bf1c79e2a6ab6a8430be0602e" + "reference": "c5f18ba4ff989a42d7d140b7f85406e77cd8c4b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer-pack/zipball/61173947057d5e1bf1c79e2a6ab6a8430be0602e", - "reference": "61173947057d5e1bf1c79e2a6ab6a8430be0602e", + "url": "https://api.github.com/repos/symfony/serializer-pack/zipball/c5f18ba4ff989a42d7d140b7f85406e77cd8c4b2", + "reference": "c5f18ba4ff989a42d7d140b7f85406e77cd8c4b2", "shasum": "" }, "require": { "doctrine/annotations": "^1.0", - "phpdocumentor/reflection-docblock": "*", + "php": "^7.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", "symfony/property-access": "*", "symfony/property-info": "*", "symfony/serializer": "*" @@ -5384,42 +4371,24 @@ "MIT" ], "description": "A pack for the Symfony serializer", - "support": { - "issues": "https://github.com/symfony/serializer-pack/issues", - "source": "https://github.com/symfony/serializer-pack/tree/v1.0.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-19T08:52:16+00:00" + "time": "2018-12-10T12:14:14+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.2.0", + "version": "v1.1.8", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/ffc7f5692092df31515df2a5ecf3b7302b3ddacf", + "reference": "ffc7f5692092df31515df2a5ecf3b7302b3ddacf", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "psr/container": "^1.0" }, "suggest": { @@ -5428,11 +4397,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "1.1-dev" } }, "autoload": { @@ -5464,44 +4429,32 @@ "interoperability", "standards" ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/master" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2019-10-14T12:27:06+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c" + "reference": "4aff3715c98706ee25bdb4aced6591a9dffc3d9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b12274acfab9d9850c52583d136a24398cdf1a0c", - "reference": "b12274acfab9d9850c52583d136a24398cdf1a0c", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4aff3715c98706ee25bdb4aced6591a9dffc3d9b", + "reference": "4aff3715c98706ee25bdb4aced6591a9dffc3d9b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1.0|^2" + "php": "^7.1.3", + "symfony/service-contracts": "^1.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" @@ -5524,126 +4477,26 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a way to profile code", + "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" - }, - { - "name": "symfony/string", - "version": "v5.2.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "c95468897f408dd0aca2ff582074423dd0455122" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", - "reference": "c95468897f408dd0aca2ff582074423dd0455122", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" - }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "files": [ - "Resources/functions.php" - ], - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-25T15:14:59+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.3.0", + "version": "v1.1.7", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105" + "reference": "364518c132c95642e530d9b2d217acbc2ccac3e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e2eaa60b558f26a4b0354e1bbb25636efaaad105", - "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/364518c132c95642e530d9b2d217acbc2ccac3e6", + "reference": "364518c132c95642e530d9b2d217acbc2ccac3e6", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": "^7.1.3" }, "suggest": { "symfony/translation-implementation": "" @@ -5651,11 +4504,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "1.1-dev" } }, "autoload": { @@ -5687,83 +4536,59 @@ "interoperability", "standards" ], - "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-09-28T13:05:58+00:00" + "time": "2019-09-17T11:12:18+00:00" }, { "name": "symfony/twig-bridge", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "5618cadebf28dff5c375f6c3c8e6f1d52df397e1" + "reference": "9574613b74ed066f775eaf94bb15476ef58609de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/5618cadebf28dff5c375f6c3c8e6f1d52df397e1", - "reference": "5618cadebf28dff5c375f6c3c8e6f1d52df397e1", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9574613b74ed066f775eaf94bb15476ef58609de", + "reference": "9574613b74ed066f775eaf94bb15476ef58609de", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^2.13|^3.0.4" + "php": "^7.1.3", + "symfony/translation-contracts": "^1.1", + "twig/twig": "^1.41|^2.10" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<4.4", - "symfony/form": "<5.1", - "symfony/http-foundation": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<5.2", - "symfony/workflow": "<5.2" + "symfony/console": "<3.4", + "symfony/form": "<4.3.5", + "symfony/http-foundation": "<4.3", + "symfony/translation": "<4.2", + "symfony/workflow": "<4.3" }, "require-dev": { "egulias/email-validator": "^2.1.10", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/form": "^5.1.9", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/mime": "^5.2", + "fig/link-util": "^1.0", + "symfony/asset": "~3.4|~4.0", + "symfony/console": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/form": "^4.3.5", + "symfony/http-foundation": "~4.3", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/mime": "~4.3", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^4.4|^5.1", - "symfony/routing": "^4.4|^5.0", - "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^4.4|^5.0", - "symfony/security-csrf": "^4.4|^5.0", - "symfony/security-http": "^4.4|^5.0", - "symfony/serializer": "^5.2", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^5.2", - "symfony/web-link": "^4.4|^5.0", - "symfony/workflow": "^5.2", - "symfony/yaml": "^4.4|^5.0", - "twig/cssinliner-extra": "^2.12", - "twig/inky-extra": "^2.12", - "twig/markdown-extra": "^2.12" + "symfony/routing": "~3.4|~4.0", + "symfony/security-acl": "~2.8|~3.0", + "symfony/security-core": "~3.0|~4.0", + "symfony/security-csrf": "~3.4|~4.0", + "symfony/security-http": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/templating": "~3.4|~4.0", + "symfony/translation": "^4.2.1", + "symfony/var-dumper": "~3.4|~4.0", + "symfony/web-link": "~3.4|~4.0", + "symfony/workflow": "~4.3", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "symfony/asset": "For using the AssetExtension", @@ -5776,12 +4601,18 @@ "symfony/security-csrf": "For using the CsrfExtension", "symfony/security-http": "For using the LogoutUrlExtension", "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/templating": "For using the TwigEngine", "symfony/translation": "For using the TranslationExtension", "symfony/var-dumper": "For using the DumpExtension", "symfony/web-link": "For using the WebLinkExtension", "symfony/yaml": "For using the YamlExtension" }, "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Bridge\\Twig\\": "" @@ -5804,71 +4635,61 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides integration for Twig with various Symfony components", + "description": "Symfony Twig Bridge", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-08T17:19:22+00:00" }, { "name": "symfony/twig-bundle", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "5ebbb5f0e8bfaa0b4b37cb25ff97f83b18caf221" + "reference": "0471344717bfb081f10209ad6b8fd520ca8ebd9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/5ebbb5f0e8bfaa0b4b37cb25ff97f83b18caf221", - "reference": "5ebbb5f0e8bfaa0b4b37cb25ff97f83b18caf221", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/0471344717bfb081f10209ad6b8fd520ca8ebd9d", + "reference": "0471344717bfb081f10209ad6b8fd520ca8ebd9d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^5.0", + "php": "^7.1.3", + "symfony/config": "~4.2", + "symfony/debug": "~4.0", + "symfony/http-foundation": "~4.3", + "symfony/http-kernel": "~4.1", "symfony/polyfill-ctype": "~1.8", - "symfony/twig-bridge": "^5.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/twig-bridge": "^4.3", + "twig/twig": "~1.41|~2.10" }, "conflict": { - "symfony/dependency-injection": "<5.2", - "symfony/framework-bundle": "<5.0", - "symfony/translation": "<5.0" + "symfony/dependency-injection": "<4.1", + "symfony/framework-bundle": "<4.3", + "symfony/translation": "<4.2" }, "require-dev": { - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", - "symfony/asset": "^4.4|^5.0", - "symfony/dependency-injection": "^5.2", - "symfony/expression-language": "^4.4|^5.0", - "symfony/finder": "^4.4|^5.0", - "symfony/form": "^4.4|^5.0", - "symfony/framework-bundle": "^5.0", - "symfony/routing": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0", - "symfony/translation": "^5.0", - "symfony/web-link": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" + "symfony/asset": "~3.4|~4.0", + "symfony/dependency-injection": "^4.2.5", + "symfony/expression-language": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/form": "~3.4|~4.0", + "symfony/framework-bundle": "~4.3", + "symfony/routing": "~3.4|~4.0", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/templating": "~3.4|~4.0", + "symfony/translation": "^4.2", + "symfony/web-link": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Bundle\\TwigBundle\\": "" @@ -5891,87 +4712,64 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a tight integration of Twig into the Symfony full-stack framework", + "description": "Symfony TwigBundle", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/validator", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "c2c234d80dad3925247b0a3fbbcecfe676e2b551" + "reference": "c380b4ab2a6d4bbc38f12b1b8f9da86aa2712a17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/c2c234d80dad3925247b0a3fbbcecfe676e2b551", - "reference": "c2c234d80dad3925247b0a3fbbcecfe676e2b551", + "url": "https://api.github.com/repos/symfony/validator/zipball/c380b4ab2a6d4bbc38f12b1b8f9da86aa2712a17", + "reference": "c380b4ab2a6d4bbc38f12b1b8f9da86aa2712a17", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.15", - "symfony/translation-contracts": "^1.1|^2" + "symfony/translation-contracts": "^1.1" }, "conflict": { "doctrine/lexer": "<1.0.2", - "phpunit/phpunit": "<5.4.3", - "symfony/dependency-injection": "<4.4", - "symfony/expression-language": "<5.1", - "symfony/http-kernel": "<4.4", - "symfony/intl": "<4.4", - "symfony/translation": "<4.4", - "symfony/yaml": "<4.4" + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<3.4", + "symfony/intl": "<4.3", + "symfony/translation": "<4.2", + "symfony/yaml": "<3.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", "egulias/email-validator": "^2.1.10", - "symfony/cache": "^4.4|^5.0", - "symfony/config": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/expression-language": "^5.1", - "symfony/finder": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/intl": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.0", - "symfony/property-info": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" + "symfony/cache": "~3.4|~4.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-client": "^4.3", + "symfony/http-foundation": "~4.1", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/intl": "^4.3", + "symfony/property-access": "~3.4|~4.0", + "symfony/property-info": "~3.4|~4.0", + "symfony/translation": "~4.2", + "symfony/var-dumper": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" }, "suggest": { "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader.", + "doctrine/cache": "For using the default cached annotation reader and metadata cache.", "egulias/email-validator": "Strict (RFC compliant) email validation", - "psr/cache-implementation": "For using the mapping cache.", + "psr/cache-implementation": "For using the metadata cache.", "symfony/config": "", - "symfony/expression-language": "For using the Expression validator and the ExpressionLanguageSyntax constraints", + "symfony/expression-language": "For using the Expression validator", "symfony/http-foundation": "", "symfony/intl": "", "symfony/property-access": "For accessing properties within comparison constraints", @@ -5980,6 +4778,11 @@ "symfony/yaml": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Validator\\": "" @@ -6002,137 +4805,36 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools to validate values", + "description": "Symfony Validator Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/validator/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T12:56:27+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v5.2.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "72ca213014a92223a5d18651ce79ef441c12b694" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72ca213014a92223a5d18651ce79ef441c12b694", - "reference": "72ca213014a92223a5d18651ce79ef441c12b694", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15" - }, - "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<4.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-21T08:20:29+00:00" }, { "name": "symfony/var-exporter", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "5aed4875ab514c8cb9b6ff4772baa25fa4c10307" + "reference": "563f728784ea09c8154ea57cf8192ae5d6f0d277" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/5aed4875ab514c8cb9b6ff4772baa25fa4c10307", - "reference": "5aed4875ab514c8cb9b6ff4772baa25fa4c10307", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/563f728784ea09c8154ea57cf8192ae5d6f0d277", + "reference": "563f728784ea09c8154ea57cf8192ae5d6f0d277", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": "^7.1.3" }, "require-dev": { - "symfony/var-dumper": "^4.4.9|^5.0.9" + "symfony/var-dumper": "^4.1.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\VarExporter\\": "" @@ -6151,66 +4853,49 @@ "email": "p@tchwork.com" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "time": "2021-01-27T10:01:46+00:00" + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "time": "2020-01-01T11:51:43+00:00" }, { "name": "symfony/webpack-encore-bundle", - "version": "v1.9.0", + "version": "v1.7.2", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "ea80d29e82da32942dc796c02b48e83b98665aaa" + "reference": "787c2fdedde57788013339f05719c82ce07b6058" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/ea80d29e82da32942dc796c02b48e83b98665aaa", - "reference": "ea80d29e82da32942dc796c02b48e83b98665aaa", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/787c2fdedde57788013339f05719c82ce07b6058", + "reference": "787c2fdedde57788013339f05719c82ce07b6058", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/asset": "^4.4 || ^5.0", - "symfony/config": "^4.4 || ^5.0", - "symfony/dependency-injection": "^4.4 || ^5.0", - "symfony/http-kernel": "^4.4 || ^5.0", + "php": "^7.1.3", + "symfony/asset": "^3.4 || ^4.0 || ^5.0", + "symfony/config": "^3.4 || ^4.0 || ^5.0", + "symfony/dependency-injection": "^3.4 || ^4.0 || ^5.0", + "symfony/http-kernel": "^3.4 || ^4.0 || ^5.0", "symfony/service-contracts": "^1.0 || ^2.0" }, "require-dev": { - "symfony/framework-bundle": "^4.4 || ^5.0", - "symfony/phpunit-bridge": "^4.4 || ^5.0", - "symfony/twig-bundle": "^4.4 || ^5.0", - "symfony/web-link": "^4.4 || ^5.0" + "symfony/framework-bundle": "^3.4 || ^4.0 || ^5.0", + "symfony/phpunit-bridge": "^4.3.5 || ^5.0", + "symfony/twig-bundle": "^3.4 || ^4.0 || ^5.0", + "symfony/web-link": "^3.4 || ^4.0 || ^5.0" }, "type": "symfony-bundle", "extra": { @@ -6235,58 +4920,41 @@ } ], "description": "Integration with your Symfony app & Webpack Encore!", - "support": { - "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v1.9.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-15T16:57:00+00:00" + "time": "2019-11-26T14:48:41+00:00" }, { "name": "symfony/yaml", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6bb8b36c6dea8100268512bf46e858c8eb5c545e" + "reference": "8e0a95493b734ca8195acf3e1f3d89e88b957db5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6bb8b36c6dea8100268512bf46e858c8eb5c545e", - "reference": "6bb8b36c6dea8100268512bf46e858c8eb5c545e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8e0a95493b734ca8195acf3e1f3d89e88b957db5", + "reference": "8e0a95493b734ca8195acf3e1f3d89e88b957db5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/console": "<4.4" + "symfony/console": "<3.4" }, "require-dev": { - "symfony/console": "^4.4|^5.0" + "symfony/console": "~3.4|~4.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" }, - "bin": [ - "Resources/bin/yaml-lint" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" @@ -6309,26 +4977,9 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Loads and dumps YAML files", + "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2020-01-21T11:09:03+00:00" }, { "name": "twig/extensions", @@ -6383,40 +5034,36 @@ "i18n", "text" ], - "support": { - "issues": "https://github.com/twigphp/Twig-extensions/issues", - "source": "https://github.com/twigphp/Twig-extensions/tree/master" - }, - "abandoned": true, "time": "2018-12-05T18:34:18+00:00" }, { "name": "twig/twig", - "version": "v2.14.3", + "version": "v2.12.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "8bc568d460d88b25c00c046256ec14a787ea60d9" + "reference": "ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/8bc568d460d88b25c00c046256ec14a787ea60d9", - "reference": "8bc568d460d88b25c00c046256ec14a787ea60d9", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5", + "reference": "ddd4134af9bfc6dba4eff7c8447444ecc45b9ee5", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.0", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + "symfony/debug": "^3.4|^4.2", + "symfony/phpunit-bridge": "^4.4@dev|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.14-dev" + "dev-master": "2.12-dev" } }, "autoload": { @@ -6440,6 +5087,7 @@ }, { "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", "role": "Contributors" }, { @@ -6453,48 +5101,35 @@ "keywords": [ "templating" ], - "support": { - "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.14.3" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2021-01-05T15:34:33+00:00" + "time": "2019-10-17T07:34:53+00:00" }, { "name": "webmozart/assert", - "version": "1.9.1", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "url": "https://github.com/webmozart/assert.git", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", + "php": "^5.3.3 || ^7.0", "symfony/polyfill-ctype": "^1.8" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" - }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -6516,51 +5151,157 @@ "check", "validate" ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.9.1" + "time": "2019-08-24T08:43:50+00:00" + }, + { + "name": "zendframework/zend-code", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "46feaeecea14161734b56c1ace74f28cb329f194" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/46feaeecea14161734b56c1ace74f28cb329f194", + "reference": "46feaeecea14161734b56c1ace74f28cb329f194", + "shasum": "" + }, + "require": { + "php": "^7.1", + "zendframework/zend-eventmanager": "^2.6 || ^3.0" + }, + "require-dev": { + "doctrine/annotations": "^1.0", + "ext-phar": "*", + "phpunit/phpunit": "^7.5.16 || ^8.4", + "zendframework/zend-coding-standard": "^1.0", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev", + "dev-develop": "3.5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", + "keywords": [ + "ZendFramework", + "code", + "zf" + ], + "abandoned": "laminas/laminas-code", + "time": "2019-10-05T23:18:22+00:00" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "a5e2583a211f73604691586b8406ff7296a946dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/a5e2583a211f73604691586b8406ff7296a946dd", + "reference": "a5e2583a211f73604691586b8406ff7296a946dd", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "athletic/athletic": "^0.1", + "container-interop/container-interop": "^1.1.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0" }, - "time": "2020-07-08T17:02:28+00:00" + "suggest": { + "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", + "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev", + "dev-develop": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://github.com/zendframework/zend-eventmanager", + "keywords": [ + "event", + "eventmanager", + "events", + "zf2" + ], + "abandoned": "laminas/laminas-eventmanager", + "time": "2018-04-25T15:33:34+00:00" } ], "packages-dev": [ { "name": "doctrine/data-fixtures", - "version": "1.5.0", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "51d3d4880d28951fff42a635a2389f8c63baddc5" + "reference": "09b16943b27f3d80d63988d100ff256148c2f78b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/51d3d4880d28951fff42a635a2389f8c63baddc5", - "reference": "51d3d4880d28951fff42a635a2389f8c63baddc5", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/09b16943b27f3d80d63988d100ff256148c2f78b", + "reference": "09b16943b27f3d80d63988d100ff256148c2f78b", "shasum": "" }, "require": { - "doctrine/common": "^2.13|^3.0", - "doctrine/persistence": "^1.3.3|^2.0", - "php": "^7.2 || ^8.0" + "doctrine/common": "~2.2", + "php": "^7.1" }, "conflict": { "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { - "doctrine/coding-standard": "^8.2", "doctrine/dbal": "^2.5.4", - "doctrine/mongodb-odm": "^1.3.0 || ^2.0.0", - "doctrine/orm": "^2.7.0", - "ext-sqlite3": "*", - "phpunit/phpunit": "^8.0" + "doctrine/orm": "^2.5.4", + "phpunit/phpunit": "^7.0" }, "suggest": { - "alcaeus/mongo-php-adapter": "For using MongoDB ODM 1.3 with PHP 7 (deprecated)", + "alcaeus/mongo-php-adapter": "For using MongoDB ODM with PHP 7", "doctrine/mongodb-odm": "For loading MongoDB ODM fixtures", "doctrine/orm": "For loading ORM fixtures", "doctrine/phpcr-odm": "For loading PHPCR ODM fixtures" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, "autoload": { "psr-4": { "Doctrine\\Common\\DataFixtures\\": "lib/Doctrine/Common/DataFixtures" @@ -6581,58 +5322,41 @@ "keywords": [ "database" ], - "support": { - "issues": "https://github.com/doctrine/data-fixtures/issues", - "source": "https://github.com/doctrine/data-fixtures/tree/1.5.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdata-fixtures", - "type": "tidelift" - } - ], - "time": "2021-01-23T10:20:43+00:00" + "time": "2019-07-10T18:30:35+00:00" }, { "name": "doctrine/doctrine-fixtures-bundle", - "version": "3.4.0", + "version": "3.2.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineFixturesBundle.git", - "reference": "870189619a7770f468ffb0b80925302e065a3b34" + "reference": "90e4a4f968b2dae40e290a6ee516957af043f16c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/870189619a7770f468ffb0b80925302e065a3b34", - "reference": "870189619a7770f468ffb0b80925302e065a3b34", + "url": "https://api.github.com/repos/doctrine/DoctrineFixturesBundle/zipball/90e4a4f968b2dae40e290a6ee516957af043f16c", + "reference": "90e4a4f968b2dae40e290a6ee516957af043f16c", "shasum": "" }, "require": { "doctrine/data-fixtures": "^1.3", - "doctrine/doctrine-bundle": "^1.11|^2.0", + "doctrine/doctrine-bundle": "^1.6", "doctrine/orm": "^2.6.0", - "doctrine/persistence": "^1.3.7|^2.0", - "php": "^7.1 || ^8.0", - "symfony/config": "^3.4|^4.3|^5.0", - "symfony/console": "^3.4|^4.3|^5.0", - "symfony/dependency-injection": "^3.4|^4.3|^5.0", - "symfony/doctrine-bridge": "^3.4|^4.1|^5.0", - "symfony/http-kernel": "^3.4|^4.3|^5.0" + "php": "^7.1", + "symfony/doctrine-bridge": "~3.4|^4.1", + "symfony/framework-bundle": "^3.4|^4.1" }, "require-dev": { "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.4 || ^8.0 || ^9.2", - "symfony/phpunit-bridge": "^4.1|^5.0" + "phpunit/phpunit": "^7.4", + "symfony/phpunit-bridge": "^4.1" }, "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, "autoload": { "psr-4": { "Doctrine\\Bundle\\FixturesBundle\\": "" @@ -6644,16 +5368,16 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" }, { "name": "Doctrine Project", "homepage": "http://www.doctrine-project.org" }, { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" } ], "description": "Symfony DoctrineFixturesBundle", @@ -6662,38 +5386,70 @@ "Fixture", "persistence" ], - "support": { - "issues": "https://github.com/doctrine/DoctrineFixturesBundle/issues", - "source": "https://github.com/doctrine/DoctrineFixturesBundle/tree/3.4.0" + "time": "2019-06-12T12:03:37+00:00" + }, + { + "name": "easycorp/easy-log-handler", + "version": "v1.0.9", + "source": { + "type": "git", + "url": "https://github.com/EasyCorp/easy-log-handler.git", + "reference": "224e1dfcf9455aceee89cd0af306ac097167fac1" }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyCorp/easy-log-handler/zipball/224e1dfcf9455aceee89cd0af306ac097167fac1", + "reference": "224e1dfcf9455aceee89cd0af306ac097167fac1", + "shasum": "" + }, + "require": { + "monolog/monolog": "~1.6|~2.0", + "php": ">=7.1", + "symfony/yaml": "^3.4|^4.0|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "EasyCorp\\EasyLog\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "name": "Javier Eguiluz", + "email": "javiereguiluz@gmail.com" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-fixtures-bundle", - "type": "tidelift" + "name": "Project Contributors", + "homepage": "https://github.com/EasyCorp/easy-log-handler" } ], - "time": "2020-11-14T09:36:49+00:00" + "description": "A handler for Monolog that optimizes log messages to be processed by humans instead of software. Improve your productivity with logs that are easy to understand.", + "homepage": "https://github.com/EasyCorp/easy-log-handler", + "keywords": [ + "easy", + "log", + "logging", + "monolog", + "productivity" + ], + "time": "2019-10-24T07:13:31+00:00" }, { "name": "fzaninotto/faker", - "version": "v1.9.2", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/fzaninotto/Faker.git", - "reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e" + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/848d8125239d7dbf8ab25cb7f054f1a630e68c2e", - "reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", "shasum": "" }, "require": { @@ -6702,12 +5458,12 @@ "require-dev": { "ext-intl": "*", "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^2.9.2" + "squizlabs/php_codesniffer": "^1.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -6730,30 +5486,25 @@ "faker", "fixtures" ], - "support": { - "issues": "https://github.com/fzaninotto/Faker/issues", - "source": "https://github.com/fzaninotto/Faker/tree/v1.9.2" - }, - "abandoned": true, - "time": "2020-12-11T09:56:16+00:00" + "time": "2018-07-12T10:23:15+00:00" }, { "name": "monolog/monolog", - "version": "2.2.0", + "version": "1.25.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/70e65a5470a42cfec1a7da00d30edb6e617e8dcf", + "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf", "shasum": "" }, "require": { - "php": ">=7.2", - "psr/log": "^1.0.1" + "php": ">=5.3.0", + "psr/log": "~1.0" }, "provide": { "psr/log-implementation": "1.0.0" @@ -6761,37 +5512,33 @@ "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^7", - "graylog2/gelf-php": "^1.4.2", - "mongodb/mongodb": "^1.8", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", - "phpspec/prophecy": "^1.6.1", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "^8.5", - "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", - "ruflin/elastica": ">=0.90 <7.0.1", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mbstring": "Allow to work properly with unicode symbols", - "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-mongo": "Allow sending log messages to a MongoDB server", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -6807,44 +5554,30 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" + "homepage": "http://seld.be" } ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "https://github.com/Seldaek/monolog", + "homepage": "http://github.com/Seldaek/monolog", "keywords": [ "log", "logging", "psr-3" ], - "support": { - "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.2.0" - }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", - "type": "tidelift" - } - ], - "time": "2020-12-14T13:15:25+00:00" + "time": "2019-09-06T13:49:17+00:00" }, { "name": "nikic/php-parser", - "version": "v4.10.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + "reference": "b76bbc3c51f22c570648de48e8c2d941ed5e2cf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/b76bbc3c51f22c570648de48e8c2d941ed5e2cf2", + "reference": "b76bbc3c51f22c570648de48e8c2d941ed5e2cf2", "shasum": "" }, "require": { @@ -6852,8 +5585,8 @@ "php": ">=7.0" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "ircmaxell/php-yacc": "0.0.4", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" }, "bin": [ "bin/php-parse" @@ -6861,7 +5594,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -6883,40 +5616,41 @@ "parser", "php" ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" - }, - "time": "2020-12-20T10:01:03+00:00" + "time": "2019-10-25T18:33:07+00:00" }, { "name": "symfony/browser-kit", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "b03b2057ed53ee4eab2e8f372084d7722b7b8ffd" + "reference": "66d301ce3458b522e3b1f2a76ecfccd1834dcf90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/b03b2057ed53ee4eab2e8f372084d7722b7b8ffd", - "reference": "b03b2057ed53ee4eab2e8f372084d7722b7b8ffd", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/66d301ce3458b522e3b1f2a76ecfccd1834dcf90", + "reference": "66d301ce3458b522e3b1f2a76ecfccd1834dcf90", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/dom-crawler": "^4.4|^5.0" + "php": "^7.1.3", + "symfony/dom-crawler": "~3.4|~4.0" }, "require-dev": { - "symfony/css-selector": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/process": "^4.4|^5.0" + "symfony/css-selector": "~3.4|~4.0", + "symfony/http-client": "^4.3", + "symfony/mime": "^4.3", + "symfony/process": "~3.4|~4.0" }, "suggest": { "symfony/process": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\BrowserKit\\": "" @@ -6936,48 +5670,36 @@ }, { "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/browser-kit/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "homepage": "https://symfony.com/contributors" } ], - "time": "2021-01-27T12:56:27+00:00" + "description": "Symfony BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/css-selector", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f" + "reference": "32203e7cc318dcfd1d5fb12ab35e595fc6016206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/f65f217b3314504a1ec99c2d6ef69016bb13490f", - "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/32203e7cc318dcfd1d5fb12ab35e595fc6016206", + "reference": "32203e7cc318dcfd1d5fb12ab35e595fc6016206", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": "^7.1.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\CssSelector\\": "" @@ -7004,62 +5726,50 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Converts CSS selectors to XPath expressions", + "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/debug-bundle", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", - "reference": "ec21bd26d24dab02ac40e4bec362b3f4032486e8" + "reference": "37f558ddd74933f0254bb5e3b6b758e1ee7ff699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/ec21bd26d24dab02ac40e4bec362b3f4032486e8", - "reference": "ec21bd26d24dab02ac40e4bec362b3f4032486e8", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/37f558ddd74933f0254bb5e3b6b758e1ee7ff699", + "reference": "37f558ddd74933f0254bb5e3b6b758e1ee7ff699", "shasum": "" }, "require": { "ext-xml": "*", - "php": ">=7.2.5", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/twig-bridge": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "php": "^7.1.3", + "symfony/http-kernel": "~3.4|~4.0", + "symfony/twig-bridge": "~3.4|~4.0", + "symfony/var-dumper": "^4.1.1" }, "conflict": { - "symfony/config": "<4.4", - "symfony/dependency-injection": "<5.2" + "symfony/config": "<4.2", + "symfony/dependency-injection": "<3.4" }, "require-dev": { - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/web-profiler-bundle": "^4.4|^5.0" + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/web-profiler-bundle": "~3.4|~4.0" }, "suggest": { "symfony/config": "For service container configuration", "symfony/dependency-injection": "For using as a service from the container" }, "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Bundle\\DebugBundle\\": "" @@ -7082,42 +5792,27 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a tight integration of the Symfony Debug component into the Symfony full-stack framework", + "description": "Symfony DebugBundle", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-10T16:30:10+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/debug-pack", - "version": "v1.0.9", + "version": "v1.0.7", "source": { "type": "git", "url": "https://github.com/symfony/debug-pack.git", - "reference": "cfd5093378e9cafe500f05c777a22fe8a64a9342" + "reference": "09a4a1e9bf2465987d4f79db0ad6c11cc632bc79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-pack/zipball/cfd5093378e9cafe500f05c777a22fe8a64a9342", - "reference": "cfd5093378e9cafe500f05c777a22fe8a64a9342", + "url": "https://api.github.com/repos/symfony/debug-pack/zipball/09a4a1e9bf2465987d4f79db0ad6c11cc632bc79", + "reference": "09a4a1e9bf2465987d4f79db0ad6c11cc632bc79", "shasum": "" }, "require": { + "easycorp/easy-log-handler": "^1.0.7", + "php": "^7.0", "symfony/debug-bundle": "*", "symfony/monolog-bundle": "^3.0", "symfony/profiler-pack": "*", @@ -7129,57 +5824,43 @@ "MIT" ], "description": "A debug pack for Symfony projects", - "support": { - "issues": "https://github.com/symfony/debug-pack/issues", - "source": "https://github.com/symfony/debug-pack/tree/v1.0.9" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-19T08:51:51+00:00" + "time": "2018-12-10T12:11:11+00:00" }, { "name": "symfony/dom-crawler", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "5d89ceb53ec65e1973a555072fac8ed5ecad3384" + "reference": "ccf895f6f3ed9430f53ae1ce34566e9bb6c58446" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5d89ceb53ec65e1973a555072fac8ed5ecad3384", - "reference": "5d89ceb53ec65e1973a555072fac8ed5ecad3384", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ccf895f6f3ed9430f53ae1ce34566e9bb6c58446", + "reference": "ccf895f6f3ed9430f53ae1ce34566e9bb6c58446", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { "masterminds/html5": "<2.6" }, "require-dev": { "masterminds/html5": "^2.6", - "symfony/css-selector": "^4.4|^5.0" + "symfony/css-selector": "~3.4|~4.0" }, "suggest": { "symfony/css-selector": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Component\\DomCrawler\\": "" @@ -7202,56 +5883,37 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Eases DOM navigation for HTML and XML documents", + "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.28.0", + "version": "v1.14.3", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "6f4d27a68c92179c124f5331a27e32d197c9bd59" + "reference": "c864e7f9b8d1e1f5f60acc3beda11299f637aded" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/6f4d27a68c92179c124f5331a27e32d197c9bd59", - "reference": "6f4d27a68c92179c124f5331a27e32d197c9bd59", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/c864e7f9b8d1e1f5f60acc3beda11299f637aded", + "reference": "c864e7f9b8d1e1f5f60acc3beda11299f637aded", "shasum": "" }, "require": { - "doctrine/inflector": "^1.2|^2.0", + "doctrine/inflector": "^1.2", "nikic/php-parser": "^4.0", - "php": ">=7.1.3", + "php": "^7.0.8", "symfony/config": "^3.4|^4.0|^5.0", "symfony/console": "^3.4|^4.0|^5.0", "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/deprecation-contracts": "^2.2", "symfony/filesystem": "^3.4|^4.0|^5.0", "symfony/finder": "^3.4|^4.0|^5.0", "symfony/framework-bundle": "^3.4|^4.0|^5.0", "symfony/http-kernel": "^3.4|^4.0|^5.0" }, "require-dev": { - "composer/semver": "^3.0@dev", "doctrine/doctrine-bundle": "^1.8|^2.0", "doctrine/orm": "^2.3", "friendsofphp/php-cs-fixer": "^2.8", @@ -7265,7 +5927,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-main": "1.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -7291,58 +5953,36 @@ "scaffold", "scaffolding" ], - "support": { - "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.28.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-15T18:19:20+00:00" + "time": "2019-11-07T00:56:03+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "aca99c4135001224b917eed17cc846e8c0ba981c" + "reference": "8c6a51c55add464d9e6ef7000f1877e4c75f9fbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/aca99c4135001224b917eed17cc846e8c0ba981c", - "reference": "aca99c4135001224b917eed17cc846e8c0ba981c", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/8c6a51c55add464d9e6ef7000f1877e4c75f9fbf", + "reference": "8c6a51c55add464d9e6ef7000f1877e4c75f9fbf", "shasum": "" }, "require": { - "monolog/monolog": "^1.25.1|^2", - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/service-contracts": "^1.1|^2" + "monolog/monolog": "~1.19", + "php": "^7.1.3", + "symfony/http-kernel": "^4.3", + "symfony/service-contracts": "^1.1" }, "conflict": { - "symfony/console": "<4.4", - "symfony/http-foundation": "<4.4" + "symfony/console": "<3.4", + "symfony/http-foundation": "<3.4" }, "require-dev": { - "symfony/console": "^4.4|^5.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/mailer": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/security-core": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "symfony/console": "~3.4|~4.0", + "symfony/security-core": "~3.4|~4.0", + "symfony/var-dumper": "~3.4|~4.0" }, "suggest": { "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings.", @@ -7350,6 +5990,11 @@ "symfony/var-dumper": "For using the debugging handlers like the console handler or the log server handler." }, "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Bridge\\Monolog\\": "" @@ -7372,39 +6017,22 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides integration for Monolog with various Symfony components", + "description": "Symfony Monolog Bridge", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T11:24:50+00:00" + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/monolog-bundle", - "version": "v3.6.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bundle.git", - "reference": "e495f5c7e4e672ffef4357d4a4d85f010802f940" + "reference": "dd80460fcfe1fa2050a7103ad818e9d0686ce6fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/e495f5c7e4e672ffef4357d4a4d85f010802f940", - "reference": "e495f5c7e4e672ffef4357d4a4d85f010802f940", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/dd80460fcfe1fa2050a7103ad818e9d0686ce6fd", + "reference": "dd80460fcfe1fa2050a7103ad818e9d0686ce6fd", "shasum": "" }, "require": { @@ -7417,7 +6045,7 @@ }, "require-dev": { "symfony/console": "~3.4 || ~4.0 || ^5.0", - "symfony/phpunit-bridge": "^4.4 || ^5.0", + "symfony/phpunit-bridge": "^3.4.19 || ^4.0 || ^5.0", "symfony/yaml": "~3.4 || ~4.0 || ^5.0" }, "type": "symfony-bundle", @@ -7454,49 +6082,27 @@ "log", "logging" ], - "support": { - "issues": "https://github.com/symfony/monolog-bundle/issues", - "source": "https://github.com/symfony/monolog-bundle/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-06T15:12:11+00:00" + "time": "2019-11-13T13:11:14+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v5.2.2", + "version": "v5.0.3", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "587f2b6bbcda8c473b91c18165958ffbb8af3c4c" + "reference": "7f835941a01bea2d03776451c9e42c2a2da6c34c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/587f2b6bbcda8c473b91c18165958ffbb8af3c4c", - "reference": "587f2b6bbcda8c473b91c18165958ffbb8af3c4c", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/7f835941a01bea2d03776451c9e42c2a2da6c34c", + "reference": "7f835941a01bea2d03776451c9e42c2a2da6c34c", "shasum": "" }, "require": { "php": ">=5.5.9" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0|9.1.2" - }, - "require-dev": { - "symfony/deprecation-contracts": "^2.1", - "symfony/error-handler": "^4.4|^5.0" + "phpunit/phpunit": "<5.4.3" }, "suggest": { "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" @@ -7506,6 +6112,9 @@ ], "type": "symfony-bridge", "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + }, "thanks": { "name": "phpunit/phpunit", "url": "https://github.com/sebastianbergmann/phpunit" @@ -7536,42 +6145,26 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides utilities for PHPUnit, especially user deprecation notices management", + "description": "Symfony PHPUnit Bridge", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-25T13:54:05+00:00" + "time": "2020-01-21T08:40:24+00:00" }, { "name": "symfony/profiler-pack", - "version": "v1.0.5", + "version": "v1.0.4", "source": { "type": "git", "url": "https://github.com/symfony/profiler-pack.git", - "reference": "29ec66471082b4eb068db11eb4f0a48c277653f7" + "reference": "99c4370632c2a59bb0444852f92140074ef02209" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/profiler-pack/zipball/29ec66471082b4eb068db11eb4f0a48c277653f7", - "reference": "29ec66471082b4eb068db11eb4f0a48c277653f7", + "url": "https://api.github.com/repos/symfony/profiler-pack/zipball/99c4370632c2a59bb0444852f92140074ef02209", + "reference": "99c4370632c2a59bb0444852f92140074ef02209", "shasum": "" }, "require": { + "php": "^7.0", "symfony/stopwatch": "*", "symfony/twig-bundle": "*", "symfony/web-profiler-bundle": "*" @@ -7582,41 +6175,24 @@ "MIT" ], "description": "A pack for the Symfony web profiler", - "support": { - "issues": "https://github.com/symfony/profiler-pack/issues", - "source": "https://github.com/symfony/profiler-pack/tree/v1.0.5" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-08-12T06:50:46+00:00" + "time": "2018-12-10T12:11:44+00:00" }, { "name": "symfony/test-pack", - "version": "v1.0.7", + "version": "v1.0.6", "source": { "type": "git", "url": "https://github.com/symfony/test-pack.git", - "reference": "e61756c97cbedae00b7cf43b87abcfadfeb2746c" + "reference": "ff87e800a67d06c423389f77b8209bc9dc469def" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/test-pack/zipball/e61756c97cbedae00b7cf43b87abcfadfeb2746c", - "reference": "e61756c97cbedae00b7cf43b87abcfadfeb2746c", + "url": "https://api.github.com/repos/symfony/test-pack/zipball/ff87e800a67d06c423389f77b8209bc9dc469def", + "reference": "ff87e800a67d06c423389f77b8209bc9dc469def", "shasum": "" }, "require": { + "php": "^7.0", "symfony/browser-kit": "*", "symfony/css-selector": "*", "symfony/phpunit-bridge": "*" @@ -7627,61 +6203,126 @@ "MIT" ], "description": "A pack for functional and end-to-end testing within a Symfony app", - "support": { - "issues": "https://github.com/symfony/test-pack/issues", - "source": "https://github.com/symfony/test-pack/tree/v1.0.7" + "time": "2019-06-21T06:27:32+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "c688afb42ae9f1c5ba45318ecfabc0d5e4fcc9ce" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c688afb42ae9f1c5ba45318ecfabc0d5e4fcc9ce", + "reference": "c688afb42ae9f1c5ba45318ecfabc0d5e4fcc9ce", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://github.com/fabpot", - "type": "github" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "time": "2020-10-19T08:52:28+00:00" + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2020-01-04T12:24:57+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v5.2.2", + "version": "v4.3.10", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "d9ce6aa8abdb84fc0db8a6f47962a949e1c652c2" + "reference": "ef55f4aac938cdf8c2051ead22e18ad80ed4e4f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/d9ce6aa8abdb84fc0db8a6f47962a949e1c652c2", - "reference": "d9ce6aa8abdb84fc0db8a6f47962a949e1c652c2", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/ef55f4aac938cdf8c2051ead22e18ad80ed4e4f8", + "reference": "ef55f4aac938cdf8c2051ead22e18ad80ed4e4f8", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0", - "symfony/framework-bundle": "^5.1", - "symfony/http-kernel": "^5.2", - "symfony/routing": "^4.4|^5.0", - "symfony/twig-bundle": "^4.4|^5.0", - "twig/twig": "^2.13|^3.0.4" + "php": "^7.1.3", + "symfony/config": "^4.2", + "symfony/http-kernel": "^4.3", + "symfony/routing": "^4.3", + "symfony/twig-bundle": "~4.2", + "symfony/var-dumper": "^4.3", + "twig/twig": "^1.41|^2.10" }, "conflict": { - "symfony/dependency-injection": "<5.2", - "symfony/form": "<4.4", - "symfony/messenger": "<4.4" + "symfony/dependency-injection": "<3.4", + "symfony/form": "<4.3", + "symfony/messenger": "<4.2", + "symfony/var-dumper": "<3.4" }, "require-dev": { - "symfony/browser-kit": "^4.4|^5.0", - "symfony/console": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/stopwatch": "^4.4|^5.0" + "symfony/browser-kit": "^4.3", + "symfony/console": "^4.3", + "symfony/css-selector": "~3.4|~4.0", + "symfony/framework-bundle": "^4.3", + "symfony/stopwatch": "~3.4|~4.0" }, "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, "autoload": { "psr-4": { "Symfony\\Bundle\\WebProfilerBundle\\": "" @@ -7704,26 +6345,9 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a development tool that gives detailed information about the execution of any request", + "description": "Symfony WebProfilerBundle", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v5.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2020-01-09T12:29:25+00:00" } ], "aliases": [], @@ -7732,13 +6356,13 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.2.5", + "php": "^7.1.3", "ext-ctype": "*", "ext-iconv": "*" }, "platform-dev": [], "platform-overrides": { - "php": "7.2.5" + "php": "7.1.3" }, "plugin-api-version": "2.0.0" } diff --git a/config/bundles.php b/config/bundles.php index a022c0e..baca5b9 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -2,6 +2,7 @@ return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], + Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], diff --git a/config/packages/cache.yaml b/config/packages/cache.yaml index 6899b72..4028c9b 100644 --- a/config/packages/cache.yaml +++ b/config/packages/cache.yaml @@ -1,10 +1,10 @@ framework: cache: - # Unique name of your app: used to compute stable namespaces for cache keys. + # Put the unique name of your app here: the prefix seed + # is used to compute stable namespaces for cache keys. #prefix_seed: your_vendor_name/app_name - # The "app" cache stores to the filesystem by default. - # The data in this cache should persist between deploys. + # The app cache caches to the filesystem by default. # Other options include: # Redis diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index c319176..849e3f4 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -1,13 +1,17 @@ doctrine: dbal: - url: '%env(resolve:DATABASE_URL)%' + # configure these for your database server + driver: 'pdo_mysql' + server_version: '5.7' + charset: utf8mb4 + default_table_options: + charset: utf8mb4 + collate: utf8mb4_unicode_ci - # IMPORTANT: You MUST configure your server version, - # either here or in the DATABASE_URL env var (see .env file) - #server_version: '13' + url: '%env(resolve:DATABASE_URL)%' orm: auto_generate_proxy_classes: true - naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + naming_strategy: doctrine.orm.naming_strategy.underscore auto_mapping: true mappings: App: diff --git a/config/packages/doctrine_migrations.yaml b/config/packages/doctrine_migrations.yaml index 61e6612..3bf0fbc 100644 --- a/config/packages/doctrine_migrations.yaml +++ b/config/packages/doctrine_migrations.yaml @@ -1,5 +1,5 @@ doctrine_migrations: - migrations_paths: - # namespace is arbitrary but should be different from App\Migrations - # as migrations classes should NOT be autoloaded - 'DoctrineMigrations': '%kernel.project_dir%/migrations' + dir_name: '%kernel.project_dir%/src/Migrations' + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + namespace: DoctrineMigrations diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index cad7f78..6089f4b 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -1,4 +1,3 @@ -# see https://symfony.com/doc/current/reference/configuration/framework.html framework: secret: '%env(APP_SECRET)%' #csrf_protection: true diff --git a/config/packages/prod/deprecations.yaml b/config/packages/prod/deprecations.yaml deleted file mode 100644 index 920a061..0000000 --- a/config/packages/prod/deprecations.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists -#monolog: -# channels: [deprecation] -# handlers: -# deprecation: -# type: stream -# channels: [deprecation] -# path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log" diff --git a/config/packages/prod/doctrine.yaml b/config/packages/prod/doctrine.yaml index 084f59a..0a7c53b 100644 --- a/config/packages/prod/doctrine.yaml +++ b/config/packages/prod/doctrine.yaml @@ -2,14 +2,26 @@ doctrine: orm: auto_generate_proxy_classes: false metadata_cache_driver: - type: pool - pool: doctrine.system_cache_pool + type: service + id: doctrine.system_cache_provider query_cache_driver: - type: pool - pool: doctrine.system_cache_pool + type: service + id: doctrine.system_cache_provider result_cache_driver: - type: pool - pool: doctrine.result_cache_pool + type: service + id: doctrine.result_cache_provider + +services: + doctrine.result_cache_provider: + class: Symfony\Component\Cache\DoctrineProvider + public: false + arguments: + - '@doctrine.result_cache_pool' + doctrine.system_cache_provider: + class: Symfony\Component\Cache\DoctrineProvider + public: false + arguments: + - '@doctrine.system_cache_pool' framework: cache: diff --git a/config/packages/prod/monolog.yaml b/config/packages/prod/monolog.yaml index bfe69c0..5bcdf06 100644 --- a/config/packages/prod/monolog.yaml +++ b/config/packages/prod/monolog.yaml @@ -5,7 +5,6 @@ monolog: action_level: error handler: nested excluded_http_codes: [404, 405] - buffer_size: 50 # How many messages should be saved? Prevent memory leaks nested: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" @@ -14,3 +13,11 @@ monolog: type: console process_psr_3_messages: false channels: ["!event", "!doctrine"] + deprecation: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log" + deprecation_filter: + type: filter + handler: deprecation + max_level: info + channels: ["php"] diff --git a/config/packages/prod/routing.yaml b/config/packages/prod/routing.yaml deleted file mode 100644 index b3e6a0a..0000000 --- a/config/packages/prod/routing.yaml +++ /dev/null @@ -1,3 +0,0 @@ -framework: - router: - strict_requirements: null diff --git a/config/packages/routing.yaml b/config/packages/routing.yaml index b45c1ce..3d69e1e 100644 --- a/config/packages/routing.yaml +++ b/config/packages/routing.yaml @@ -1,7 +1,4 @@ framework: router: + strict_requirements: null utf8: true - - # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. - # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands - #default_uri: http://localhost diff --git a/config/packages/security.yaml b/config/packages/security.yaml index c34defb..bfdb653 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,4 +1,8 @@ security: + encoders: + App\Entity\User: + algorithm: auto + # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers providers: # used to reload user from session & other features (e.g. switch_user) @@ -12,7 +16,6 @@ security: security: false main: anonymous: true - lazy: true guard: authenticators: - App\Security\LoginFormAuthenticator diff --git a/config/packages/test/monolog.yaml b/config/packages/test/monolog.yaml index fc40641..2762653 100644 --- a/config/packages/test/monolog.yaml +++ b/config/packages/test/monolog.yaml @@ -1,12 +1,7 @@ monolog: handlers: main: - type: fingers_crossed - action_level: error - handler: nested - excluded_http_codes: [404, 405] - channels: ["!event"] - nested: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug + channels: ["!event"] diff --git a/config/packages/test/twig.yaml b/config/packages/test/twig.yaml deleted file mode 100644 index 8c6e0b4..0000000 --- a/config/packages/test/twig.yaml +++ /dev/null @@ -1,2 +0,0 @@ -twig: - strict_variables: true diff --git a/config/packages/test/webpack_encore.yaml b/config/packages/test/webpack_encore.yaml deleted file mode 100644 index 02a7651..0000000 --- a/config/packages/test/webpack_encore.yaml +++ /dev/null @@ -1,2 +0,0 @@ -#webpack_encore: -# strict_mode: false diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index b3cdf30..343cac6 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -1,2 +1,6 @@ twig: default_path: '%kernel.project_dir%/templates' + debug: '%kernel.debug%' + strict_variables: '%kernel.debug%' + form_themes: + - bootstrap_4_layout.html.twig \ No newline at end of file diff --git a/config/packages/webpack_encore.yaml b/config/packages/webpack_encore.yaml index 90f1a1d..ce02f6b 100644 --- a/config/packages/webpack_encore.yaml +++ b/config/packages/webpack_encore.yaml @@ -1,30 +1,14 @@ webpack_encore: - # The path where Encore is building the assets - i.e. Encore.setOutputPath() + # The path where Encore is building the assets. + # This should match Encore.setOutputPath() in webpack.config.js. output_path: '%kernel.project_dir%/public/build' # If multiple builds are defined (as shown below), you can disable the default build: # output_path: false - # Set attributes that will be rendered on all script and link tags - script_attributes: - defer: true - # link_attributes: - - # If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials') + # if using Encore.enableIntegrityHashes() specify the crossorigin attribute value (default: false, or use 'anonymous' or 'use-credentials') # crossorigin: 'anonymous' - # Preload all rendered script and link tags automatically via the HTTP/2 Link header - # preload: true - - # Throw an exception if the entrypoints.json file is missing or an entry is missing from the data - # strict_mode: false - - # If you have multiple builds: - # builds: - # pass "frontend" as the 3rg arg to the Twig functions - # {{ encore_entry_script_tags('entry1', null, 'frontend') }} - - # frontend: '%kernel.project_dir%/public/frontend/build' - - # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes) - # Put in config/packages/prod/webpack_encore.yaml - # cache: true + # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes). + # To enable caching for the production environment, creating a webpack_encore.yaml in the config/packages/prod directory with this value set to true + # Available in version 1.2 + #cache: false diff --git a/config/preload.php b/config/preload.php deleted file mode 100644 index 5ebcdb2..0000000 --- a/config/preload.php +++ /dev/null @@ -1,5 +0,0 @@ - - + @@ -22,8 +22,8 @@ - - src + + src diff --git a/public/css/bigfoot.css b/public/css/bigfoot.css deleted file mode 100644 index 189b0df..0000000 --- a/public/css/bigfoot.css +++ /dev/null @@ -1,20 +0,0 @@ -body { - background-color: rgb(21, 32, 43); - color: #fff; -} - -.top-nav { - border-bottom: 1px solid #48535E; -} - -.footer-nav { - border-top: 1px solid #48535E; -} - -.big-foot-img { - width:300px; - height:300px; - object-fit: cover; - object-position: top; - border-radius: 5px; -} \ No newline at end of file diff --git a/public/img/bigfoot.png b/public/img/bigfoot.png deleted file mode 100644 index 343faa94ef50c1a4e7716eb4700dc731214c25ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29863 zcmZsDcRUn+{6E)So!Lo9nF(b!$jFK)qsZQ5WJjdT<1(WV8QCh?&Mv~8kZcWSWoE?L z+Zp$JU!T+C_x=8U|J0-I-tYJ8{hH6$c;7RvJ2&a5Pg7G+P|&H~x}i-$0WqYYI6@CU z3jXKw7q6$_FR0!1+t(>5N~3A^?!hQ16ev`0T-WuYTo|7H#MT_Y
    M5E`x;u1c%e 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 Tw5b#x0{?EPYF{h7Y8m`LPNqrG diff --git a/public/index.php b/public/index.php index 3bcee0b..e30f90c 100644 --- a/public/index.php +++ b/public/index.php @@ -1,13 +1,10 @@ bootEnv(dirname(__DIR__).'/.env'); +require dirname(__DIR__).'/config/bootstrap.php'; if ($_SERVER['APP_DEBUG']) { umask(0000); @@ -15,6 +12,14 @@ Debug::enable(); } +if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) { + Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST); +} + +if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) { + Request::setTrustedHosts([$trustedHosts]); +} + $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); $request = Request::createFromGlobals(); $response = $kernel->handle($request); diff --git a/sfcasts/assertions.md b/sfcasts/assertions.md new file mode 100644 index 0000000..5bb3921 --- /dev/null +++ b/sfcasts/assertions.md @@ -0,0 +1,72 @@ +# Assertions / Profile "Tests" + +Adding specific assertions inside a test is really cool: + +[[[ code('621f575434') ]]] + +But you can *also* add assertions *globally*. What I mean is, whenever you trigger +a *real* Blackfire profile - like through your browser - you can set up *assertions* +that you want to run against that profile. + +## Recommendations Versus Assertions + +Actually, we've *already* seen a system that's *similar* to this. Click into +one of the profiles. Every profile has a "Recommendations" tab on the left, which +tells us changes that we should *probably* make. In reality, recommendations are +*assertions* in disguise! For example, the "Symfony debug mode should be disabled +in production" is displayed here because the *assertion* that +`metrics.symfony.kernel.debug.count` equals zero, failed. Yep, metrics are +*everywhere*! + +I *love* that Blackfire gives us so many of these recommendations for free. But +we can *also* define our own. When we do, they'll show up under the assertions tab. + +## Hello .blackfire.yaml + +How do we do that? Just send an email to `assertion-requests@blackfire.io`, pay +$19.95 for shipping and handling, and wait 6-8 weeks for delivery. If you order +now, we'll *double* your order and include a signed-copy of the `blackfire-player` +source code printed as a book. + +*Or* you can configure global assertions with a special Blackfire config file. +At the root of your project, create a new file called `.blackfire.yaml`. A few +different things will eventually go here - the first is `tests:`. + +Honestly, the *trickiest* thing about writing assertions is trying to figure out... +a good assertion to use! Writing *time-based* assertions is the easiest... but +because they're fragile, we want to avoid those. + +## Adding your first "Test" + +Let's start with one we've already done. Say: +`"HTTP requests should be limited to 1 per page":`. Below this, add `path` set +to the regular expression `/.*`: + +[[[ code('7380b18a4c') ]]] + +This means that this assertion will be executed against *any* profile for *any* +page. Only want the assertion to run against a single page or section? Use this +option. + +Now add `assertions:` with one item below. Go steal the metrics expression from +our test... and paste it here. Change this to be *less than* or equal to 1: + +[[[ code('10d8b56e89') ]]] + +That's it! Let's try it out! Back in your browser... go back to our site, refresh, +and create a new profile. I'll call it: `[Recording] Added first assertion`. + +Click into the call graph. Actually, go back. See this little green check mark? +That *already* tells us that this profile passed *all* our "tests". We can see +that on the "Assertions" tab: `metrics.http.requests.count` was 0, which *is* less +than or equal to 1. + +So at this point, these "tests" are basically a nice way to create your *own* +custom recommendations. These will become *more* interesting later when we talk +about environments and builds. + +Next, let's talk about a tool from the Blackfire ecosystem called the Blackfire +player. It's a command line utility that allows us to write simple files and +execute them as functional tests... *completely* independent of the Blackfire +profiling system. What we learn from it will form the foundation for the rest +of the tutorial. diff --git a/sfcasts/auto-profile.md b/sfcasts/auto-profile.md new file mode 100644 index 0000000..3fc42b9 --- /dev/null +++ b/sfcasts/auto-profile.md @@ -0,0 +1,77 @@ +# SDK: Automatically Create a Profile + +Imagine you have a performance "problem" on production. No worries! Except... +the issue is only caused in some edge-case situation... and you're having a +hard time repeating the *exact* condition... which means that you can't create +a meaningful Blackfire profile by using the browser extension. + +For example, imagine we want to profile the AJAX request that loads the GitHub +repository info... but we think that the performance problem only happens for +certain *types* of users - maybe users that have *many* comments. I'm just making +this up. + +To do that, *instead* of triggering a new profile by clicking the browser +extension button - which maybe is hard because we can't seem to replicate the +correct situation - let's trigger a new profile automatically from *inside* +our code. We can do this using the PHP SDK. + +Spin over, go back to `MainController` and scroll down to +`loadSightingsPartial()`... actually to the `gitHubOrganizationInfo()` method: + +[[[ code('3a2d9ed726') ]]] + +This is the controller that returns the content on the right side of the page. + +Start by creating a fake variable `$shouldProfile = true`: + +[[[ code('44e2fee779') ]]] + +In a real app, you would replace this with logic to determine whether or not +this is one of those requests that you think might have a performance problem: +maybe you check to see if the user has *many* comments or something. + +## Creating & Starting the Profile + +Then, if `$shouldProfile`, it means that we *want* Blackfire to profile this request. +To do that, say `$blackfire = new Client()` - the one from `Blackfire`. This +is an object that helps communicate with the Blackfire servers. Next, *create* +a probe - basically create a new "profile" - with +`$probe = $blackfire->createProbe()`: + +[[[ code('83848bc699') ]]] + +Earlier, when we used `BlackfireProbe::getMainInstance()`, we were, kind of *asking* +for a "probe" if there *was* a profile happening. But this time, we're *creating* +a probe: creating a new profile and telling it to start "instrumenting" - collecting +data - right now. + +In fact, the second argument to `createProbe()` is `$enabled=true`: whether or not +we want the probe to *immediately* start instrumentation or if we will enable +it later with `$probe->enable()`. + +Now, *because* this will *create* a new profile, you need to make sure you do +this only *rarely* on production. Why? Because creating profiles is heavy and this +slow request will be *felt* by whichever user triggered it. So, choose your logic +for `$shouldProfile` carefully. + +Anyways, let's try it! Move over and refresh your list of Blackfire profiles. +The most recent one is the "Only instrumenting some code" profile. Now refresh the +homepage. This triggers the AJAX call... but notice it's slower. And when we +refresh Blackfire... boom! We have a brand new profile! Open that up and... +let's give it a name: `[Recording] First automatic profile`: +http://bit.ly/f-bf-1st-auto-profile. I'm so proud. + +## This only Profiles the Controller + +You can now create *new* profiles from your code... *whenever* you want to. But... +there's a small problem: this only profiled a *tiny* part of our code. And, +that makes sense: when our PHP code started executing, the PHP extension didn't +yet know that we wanted to profile this request. And so, it couldn't start collecting +data until we told it to, which happened in the controller. To make matters *worse*, +as *soon* as PHP garbage collected the `$probe` variable... which happened once +the variable isn't used anymore... so at the end of the controller, internally, +the probe called `close()` on itself. That means that we just collected data +on *nothing* more than the code in our controller. + +How can we fix that? By starting the probe *super* early and closing it manually +as late as we can. Let's do that next. diff --git a/sfcasts/blackfire-cli.md b/sfcasts/blackfire-cli.md new file mode 100644 index 0000000..241be83 --- /dev/null +++ b/sfcasts/blackfire-cli.md @@ -0,0 +1,81 @@ +# The Blackfire CLI Tool for AJAX Requests + +We know that the probe - that's the Blackfire PHP extension - doesn't +run on every single request: it only runs when it detects that our browser extension +is *telling* it to run. + +There's actually a *second* way that you can tell the probe to do its work. It's +with a *super* handy command-line tool. + +## Installing the Blackfire CLI Tool + +Go back to the Blackfire site, click on their docs... and once again find the +[installation page](https://blackfire.io/docs/up-and-running/installation). +When we went through this earlier, we purposely skipped one step: installing +the Blackfire CLI tool. Actually, Blackfire recently updated this page... and I +like the newer version a lot better. In both versions of the docs - the new one +and the old one you see here - if you followed the commands to install the "agent" +then you've already *also* installed the CLI tool. Nice! + +To make sure, find your terminal and try running: + +```terminal +blackfire version +``` + +## Blackfire CLI Confiug: Client ID & Token + +Got it! Before using this, we *do* need to add a little bit of configuration +by running a `blackfire config` command. On the old version of the docs, I'll +copy the "client ID": I'll need that in a second. On the newer version of the +docs, you'll be able to copy a `blackfire config` command that already includes +the client id and client token. For me, I'll run + +```terminal +blackfire config +``` + +If your version of the command has the `--client-id` and `--client-token` options +already, you're done! If not, like me, paste in the Client Id... then also copy and +paste in the token. + +The client id and token work... almost like a username and password to your Blackfire +account. When we use the browser extension, we're logged into Blackfire in the +browser. When we click profile, the Blackfire API is able to give the extension +some credentials that it passes to the probe to prove that we're allowed to profile +this page. + +When you use the Blackfire command line tool to profile something... the +client id and client token are used to talk to the Blackfire API and get those +*same* credentials that it then passes to the probe to prove we're authorized to +profile. They basically identify & prove which *user* we are on Blackfire. + +## Profiling AJAX Requests + +The Blackfire CLI tool has two superpowers. The first is that you can run +`blackfire curl` and then pass a URL to *any* page on your site that you want +to create a profile for. Now... that *might* seem *totally* worthless. After all... +if we want to profile a page... isn't it easier just to *go* to that page in +our browser and use the extension to profile it? + +Yep! Unless... you *can't* easily "go" to that page - like if you want to profile +an AJAX request or an API endpoint. Check this out: I'll open up the dev tools, +go to the "Network" section and refresh. Notice I'm already filtered to XHR +requests - so the `/api/github-organization` AJAX request pops up. Want to easily +profile *just* that request? Right click on it and select "Copy as cURL". + +Now head *back* to your terminal and paste. Cool, right? It creates a *full* +curl command that you can use to make that same request... *including* any session +cookies, which means this request will be authenticated as the same user you're +logged in as in the browser. We can use this with Blackfire: say `blackfire` +then paste! + +Try it! It's profiling and using the same process as the browser: making 10 requests +and profiling each one. This is my favorite way to profile AJAX requests. When +it finishes, it gives us the URL to the call graph and some basic stats below. +Go open that profile: http://bit.ly/sf-bf-curl! + +It works! Use that to easily profile *any* AJAX requests you want to. + +So what is the *second* superpower of the CLI tool? It's actually its *main* +superpower: the ability to profile command-line scripts. Let's do that next. diff --git a/sfcasts/blackfire-player-expects.md b/sfcasts/blackfire-player-expects.md new file mode 100644 index 0000000..13e9aab --- /dev/null +++ b/sfcasts/blackfire-player-expects.md @@ -0,0 +1,149 @@ +# Expectations/Tests with Blackfire Player + +We just used `blackfire-player` to execute our first "scenario". It's pretty +simple: it goes to the homepage then clicks the "Log In" link: + +[[[ code('e3eaee2e22') ]]] + +It works... but... we're not *doing* anything after we visit these pages. The +*true* power of `blackfire-player` is that you can add *tests* to your scenario - +or even scrape pages and save that data somewhere. + +## Adding an Expectation/Test to a Page + +To add a "test" - or "assertion", or "expectation"... I *love* when things have 5 +names... - say `expect` followed by - you guessed it! - an *expression*! +`status_code() == 200`. Copy that and add it to the login page as well: + +[[[ code('9ea20a81d4') ]]] + +Ok, try `blackfire-player` again! + +```terminal-silent +blackfire-player run scenario.bkf --ssl-no-verify -v +``` + +Woo! It still passes and *now* it's starting to be useful! + +## What's Possible in the expect Expression? + +Let's break this down. First, *just* like we saw with the `metrics` stuff: + +[[[ code('03970f47cc') ]]] + +This is an *expression* - it's Symfony's ExpressionLanguage once again - basically +JavaScript. And second... this expression has a *ton* of built-in functions. + +Search the `blackfire-player` docs for "status_code"... and keep searching until +you find a big function list. Here it is. Yep, we can use `current_url()`, +`header()` to get a header value and many others. The `css()` function is +especially useful: it allows us to dig into the HTML on the page. We'll use that +in a minute. The docs also have good examples of how to do more complex things. +But we're not going to become Blackfire player experts right now... I just want +you to get comfortable with writing scenarios. + +## Asserting HTML Elements with css() + +Let's try to write a *failing* expectation to see what it looks like. Let's see... +we could find this table and assert that it has more than 500 rows... which it +definitely does *not*. Let's find a CSS selector we can use... hmm. Ok, we could +look for a `` with this `js-sightings-list` class and then count its +`` elements. + +Back inside the scenario file, add another expect. This time use the `css()` +function and pass it a CSS selector: `tbody.js-sightings-list tr`: + +[[[ code('b1b3369bed') ]]] + +Internally, The `blackfire-player` uses Symfony's `Crawler` object from the `DomCrawler` +component, which has a `count()` method on it. Assert that this is `> 500`. + +Let's see what happens! + +```terminal-silent +blackfire-player run scenario.bkf --ssl-no-verify -v +``` + +And... yes! It fails - with a nice error: + +> The `count()` of that CSS element is 25, which is not greater than 500. + +Go back and change this to 10: + +[[[ code('9c2788b557') ]]] + +The data is dynamic data... so we don't *really* know how many rows it will have. +But since our fixtures add more than 10 sightings... and because there will probably +be at least 10 sightings if we ever ran this against production, this is probably +a safe value. + +Try it now: + +```terminal-silent +blackfire-player run scenario.bkf --ssl-no-verify -v +``` + +All better! + +## Typos in Expressions + +Another thing that `blackfire-player` does well is its *errors* when I... do +something silly. Make a typo: change `count()` to `ount()`: + +[[[ code('83aab1d706') ]]] + +And rerun the scenario: + +```terminal-silent +blackfire-player run scenario.bkf --ssl-no-verify -v +``` + +> Unable to call method `ount` of object `Crawler`. + +That's a *huge* hint to tell you what object you're working with so you can figure +out what methods it *does* have. Change that back to `count()`: + +[[[ code('9a09765c16') ]]] + +## Performance Assertions in the Scenarios? + +So... `blackfire-player` has *nothing* to do with the Blackfire profiler. It's +just a useful tool for visiting pages, clicking on links and adding expectations. +But... if it *truly* had nothing to do with the profiler, I probably wouldn't +have talked about it. In reality, the concept of "scenarios" is *about* to become +*very* important - it's a fundamental part of a topic we'll talk about soon: +Blackfire "builds". + +And actually, there is one *little* integration between `blackfire-player` and +the profiler: you can add *performance* assertions to your scenario. To do that, +instead of `expect`, say `assert` and then use any performance expression you want: the +same strings that you can use inside a test. For example: +`metrics.sql.queries.count < 30`: + +[[[ code('a8ebecf563') ]]] + +When we execute this: + +```terminal-silent +blackfire-player run scenario.bkf --ssl-no-verify -v +``` + +It *does* still pass. But if you played with this value - like set it to `< 1` +and re-ran the scenario: + +```terminal-silent +blackfire-player run scenario.bkf --ssl-no-verify -v +``` + +Hmm, it *still* passes... even though this page is *definitely* making more than +one query. The reason is that the `assert` functionality *won't* work inside +a scenario until we introduce Blackfire "environments" - which we will soon. +They are one of my absolute *favorite* parts of Blackfire. + +For now, I'll leave a comment that this *won't* work until then: + +[[[ code('93686b00f1') ]]] + +Next, let's deploy to production! Because once our site is deployed, we can +*finally* talk about cool things like "environments" and "builds". You can use +anything to deploy, of course, but *we* will use SymfonyCloud. diff --git a/sfcasts/blackfire-player.md b/sfcasts/blackfire-player.md new file mode 100644 index 0000000..484c387 --- /dev/null +++ b/sfcasts/blackfire-player.md @@ -0,0 +1,115 @@ +# Blackfire Player + +Pretend for a few minutes that the Blackfire profiler that we've been learning +*so* much about... doesn't exist... at all. Why? Because we're *now* going to talk +about something that has the word "Blackfire" in it... but has absolutely +*nothing* to do with the Blackfire profiler. At least, not yet. + +## Hello Blackfire Player + +Google for "Blackfire player". The Blackfire Player is an open source library +that makes it *really* easy to write a few lines of code that will then be +executed to *crawl* a site: clicking on links, filling out forms, and doing +things with the result. It's basically a simple language for surfing the web +and a tool that's able to *read* that language and... actually do it! + +To install it, copy the `curl` command, find your terminal, and paste: + +```terminal-silent +curl -OLsS https://get.blackfire.io/blackfire-player.phar +``` + +If you're on Windows, you can just download the `blackfire-player.phar` file from +that URL and put it into your project. + +Now go back and copy the other two commands. + +```terminal-silent +chmod +x blackfire-player.phar +mv blackfire-player.phar /usr/local/bin/blackfire-player +``` + +Paste and... that's it! For Windows users, skip this step. Let's see if it works. +Run: + +```terminal +blackfire-player +``` + +Nice! + +***TIP +For Windows, run `php blackfire-player.php` from inside your project. +*** + +So here's the idea: we create a file that contains one or more *scenarios*. +Inside each scenario, we write code that says: go visit this URL, expect a 200 +status code, then click on this link, and so on. It can get fancier, but that's +the gist of it. + +## Creating our First Scenario & .bkf File + +Let's create a our first Blackfire player file at the root of the project, though +it could live anywhere. Call it, how about, `scenario.bkf`. That's *pure* creativity. + +At the top, I'll put a `name` - though it's not very important - then +`endpoint` set to our server's URL. So `https://localhost:8000`: + +[[[ code('75ca2808a3') ]]] + +You can override this when you *execute* this file by passing a `--endpoint` +option. + +Notice that this *kind* of looks like YAML, but it's *not*: there is no `:` +between the key and value. This is a custom Blackfire player language, which +is friendly, but takes some getting used to. + +At the bottom, add our first scenario - call it "Basic Visit". Inside, let's do +two things: first, `visit url("/")`. We can *also* give this page a name - it +helps debugging: + +[[[ code('0fd3c06259') ]]] + +And second... once we're on the homepage, let's "click" this "Log In" link. Do +that with `click link()` and then use that exact text: `Log In`. Give this page a +name too: + +[[[ code('d13414e820') ]]] + +## Executing blackfire-player + +That's enough to start. We *should* be able to use the `blackfire-player` tool +to... actually *do* this stuff!. Let's try it: + +```terminal +blackfire-player run scenario.bkf +``` + +And... it fails: + +> Curl error 60... + +If you Google'd this, you find out that this is an SSL problem - it's caused because +or Symfony dev server uses a, sort of, self-signed certificate that blackfire-player +doesn't like. The simplest solution, which is ok since we're just testing +locally - is to pass `--ssl-no-verify` + +```terminal-silent +blackfire-player run scenario.bkf --ssl-no-verify +``` + +And... hey! It worked! Scenarios 1, steps 2. It *truly* made a request to +the homepage then clicked on that link! By the way, the requests aren't using a +*real* browser. And so, any JavaScript code on your page *won't* run. +That *might* change in the future - but I'm not sure. + +Anyways, to see more fun output, use the `-v` flag: + +```terminal-silent +blackfire-player run scenario.bkf --ssl-no-verify -v +``` + +Very cool! Blackfire player *is* now making two real HTTP requests to our site... +but it's not *doing* anything with that data. Next, let's add some *tests* to +our scenario - like expecting that the status code is 200 and checking for elements +in the DOM. diff --git a/sfcasts/build-basics.md b/sfcasts/build-basics.md new file mode 100644 index 0000000..e3a9389 --- /dev/null +++ b/sfcasts/build-basics.md @@ -0,0 +1,112 @@ +# Automatic Performance Checks: Builds + +Head back to https://blackfire.io, click "Environments" and click into our +"Sasquatch Sightings Production" environment. + +Interesting. By default, it takes us *not* to the profiles tab... but to a tab +called "Builds". And, look on the right: "Periodic Builds": "Builds are started +every 6 hours"... which we could change to a different interval. + +Further below, there are a bunch of "notification channels" where you can tell +Blackfire that you want to be *notified* - like via Slack - of the results of this +"build" thingy. + +## Hello Builds + +Ok, what the *heck* is a build anyways? To find out, let's trigger one manually, +then stand back and see what happens. Click "Start a Build". The form pre-fills +the URL to our site... cool... and we can apparently give it a title if we want. +Let's... just start the build. + +This takes us to a new page where.... interesting: it's running an +"Untitled Scenario"... then it looks like it went to the homepage... and created +a profile? + +Let's... back up: there are a *lot* of interesting things going on. And I *love* +interesting things! + +First, we've *seen* this word "scenario" before! Earlier, we used the +`blackfire-player`: a command-line tool that's *made* by the Blackfire +people... but can be used totally outside of the profiling tool. We created a +`scenario.bkf` file where we defined a *scenario* and used the special +`blackfire-player` language to tell it to go to the homepage, assert a few things, +then click on the "Log In" link and check something else: + +[[[ code('a351172e14') ]]] + +At that time, this was a nice way to "crawl" a site and test some things on it. +The "build" used the same "scenario" word. That's not an accident. More on +that soon. + +## Build "URLs to Test" + +The *second* important thing is that this profiled the *homepage* because, when +we created our environment, we configured one "URL to test": the homepage. That's +what the build is doing: "testing" - meaning *profiling* - that page. + +Let's add a second URL. One other page we've been working on a lot is +`/api/github-organization`: this JSON endpoint. Copy that URL and add it as a +*second* "URL to test". Click save... then manually create a *second* build. + +Like before, it creates this "Untitled Scenario" thing. Ah! But *this* time it +profiled *both* pages! The build *also* shows up as green: the build "passed". + +This is a *critical* thing about builds. It's not *simply* that a build is an +automated way to create a profile for a few pages. That would be pretty worthless. +The *real* value is that you can write performance *tests* that cause a build to +pass or fail. + +Check it out "1 successful constraint" - which is that "HTTP Requests should +be limited to 1 per page". Hey! That's the "test" that *we* set up inside +`.blackfire.yaml`! + +[[[ code('45ce3ea1a3') ]]] + +The *real* beauty of `tests` is *not* that the "Assertions" tab will look red when +you're looking inside a profile. The *real* beauty is that you can configure +performance *constraints* that should pass *whenever* these builds happen. If a +build *fails* - maybe because you introduced some slow code - you can be notified. + +## Build Log: blackfire-player + +But there's even *more* cool stuff going on. Near the bottom, click to see the +"Player output". Woh! It shows us how builds work behind-the-scenes: the Blackfire +server *uses* the `blackfire-player`! + +Look closer: it's running a *scenario*: `visit url()`, `method 'GET'`, then +`visit url()` of `/api/github-organization`. It's a bit hard to read, but this +*converted* our 2 "URLs to test" into a scenario - using the same format as the +`scenario.bkf` file - then *passed* that to `blackfire-player`. You can even see +it *reloading* both pages multiple times to get 10 samples. That's one of the +options it added in the scenario. + +So with just a *tiny* bit of configuration, Blackfire is now creating a build +every 6 hours. Each time, it profiles these 2 pages and, thanks to our one test, +if either page makes more than one HTTP request, the build will fail. By setting +up a notification, we'll know about it. + +The fact that the build system uses `blackfire-player` makes me wonder: instead +of configuring these URLs, could we *instead* have the build system run our custom +scenario file? I mean, it's a *lot* more powerful: we can visit pages, but also +click links and fill out forms. We can *also* add *specific* assertions to *each* +page... in addition to our one "global" test about HTTP requests. + +The answer to this question is... of course! And it's where the build system +*really* starts to shine. We'll talk about that next. + +## History & Graphs from Automated Builds + +But before we do, I want you to see what the build page looks like once it's +had enough time to execute a few automated builds. Let's check out the +SymfonyCasts environment. Woh! It's graph time! Because this environment has +a *history* of automated builds, Blackfire creates some super cool graphs: +like our cache hit percentage and our cache levels. You can see that my +`OPcache Interned Strings Buffer` cache is full. I really need to tweak some +config to increase that. + +I can also see how the different URLs are performing over time for wall time, +I/O, CPU, Memory & network as well as other stuff. We can click to see more +details about any build... and even look at any of its profiles. + +*Anyways*, next: let's make the build system smarter by executing our custom +scenario. diff --git a/sfcasts/build-scenarios.md b/sfcasts/build-scenarios.md new file mode 100644 index 0000000..3161502 --- /dev/null +++ b/sfcasts/build-scenarios.md @@ -0,0 +1,122 @@ +# Builds with Custom Scenarios + +A few chapters ago, we created this `scenario.bkf` file: + +[[[ code('7f280e7891') ]]] + +It's written in a special `blackfire-player` *language* where we write one or more +"scenarios" that, sort of, "crawl" a web page, asserting things, clicking on +links and even submitting forms. This a simple scenario: the tool can do a lot more. + +On the surface, apart from its name, this has *nothing* to do with the Blackfire +profiler system: `blackfire-player` is just a tool that can read these scenarios +and do what they say. At your terminal, run this file: + +```terminal +blackfire-player run scenario.bkf --ssl-no-verify +``` + +That last flag avoids an SSL problem with our local web server. When we hit enter... +it goes to the homepage, clicks the "Log In" link and... it passes. + +## Scenarios in .blackfire.yaml + +This is cool... but we can do something *way* more interesting. Copy the entire +scenario from this file, close it, and open `.blackfire.yaml`. Add a new key +called `scenarios` set to a `|`: + +[[[ code('38685f15f5') ]]] + +That's a YAML way of saying that we will use multiple lines to set this. + +Below, indent, then say `#!blackfire-player`: + +[[[ code('493676085a') ]]] + +That tells Blackfire that we're about to use the `blackfire-player` syntax... +which is the *only* format supported here... but it's needed anyways. Below, +paste the scenario. Make sure it's indented 4 spaces: + +[[[ code('b9ae201ea0') ]]] + +The *cool* thing is that we can *still* execute the scenario locally: just replace +`scenario.bkf` with `.blackfire.yaml`. The player is smart enough to know that it +can look under the `scenarios` key for our scenarios. + +```terminal-silent +blackfire-player run .blackfire.yaml --ssl-no-verify +``` + +But if you run this... error! + +> Unable to crawl a non-absolute URI /. Did you forget to set an endpoint? + +Duh! Our `scenario.bkf` file had an `endpoint` config: + +[[[ code('202e2c4bd6') ]]] + +You *can* copy this into your `.blackfire.yaml` file. *Or* you can define +the endpoint by adding `--endpoint=https://localhost:8000`: + +```terminal-silent +blackfire-player run .blackfire.yaml --ssl-no-verify --endpoint=https://localhost:8000 +``` + +Now... it works! + +## Building the Custom Scenario + +So... why did we move the scenario into this file? To find out, add this change +to git... and commit it. + +```terminal-silent +git add . +git commit -m "moving scenarios into blackfire config file" +``` + +Then deploy: + +```terminal +symfony deploy --bypass-checks +``` + +Once that finishes... let's go see what changed. First, if we simply went to our +site and manually created a profile - like for the homepage - the new `scenarios` +config would have absolutely *no* effect. Scenarios don't do *anything* to an +individual profile. Instead, scenarios affect *builds*. + +Let's start a new one: I'll give this one a title: "With custom scenarios". Go! + +Awesome!! Now, instead of that "Untitled Scenario" that tested the two URLs we +configured, it's using our "Basic visit" scenario! It goes to the homepage, then +clicks "Log In" to go to that page. + +Yep, as *soon* as we add this `scenarios` key to `.blackfire.yaml`, it +*no longer* tests these URLs. In fact, these are now meaningless. Instead, we're +now in the driver's seat: *we* control the scenario or scenarios that a build +will execute. + +## Per Page Assertions/Tests + +Even *better*, we have a lot more control *now* over the assertions - or "tests"... +Blackfire uses both words - that make a build pass or fail. + +For example, the "HTTP requests should be limited to one per page" will be +run against *all* pages in the scenarios - that's 2 pages right now. But the +homepage *also* has its *own* `assert`: that the SQL queries on this page should +be less than 30. If you look back at the build... we can see that assertion! +We can even click into the profile, click on "Assertions", and see both there. + +So not *only* do we have a lot of control over *which* pages we want to test - even +including filling out forms - but we can *also* do custom assertions on a +page-by-page basis in addition to having global tests. I *love* that. And now I +can remove the comment I put earlier above `assert`: + +[[[ code('023be8dd15') ]]] + +Now that we're running this from inside an environment, this *does* work. + +Next, let's use our power to *carefully* add more time-based assertions on a +page-by-page basis. We'll also learn how you can add your *own* metrics in order +to, well, write performance assertions about pretty much *anything* you can dream +up. diff --git a/sfcasts/cache-compare.md b/sfcasts/cache-compare.md new file mode 100644 index 0000000..79cd924 --- /dev/null +++ b/sfcasts/cache-compare.md @@ -0,0 +1,95 @@ +# Using a Caching Layer & Proving its Worth + +Whenever we make something more performant, we often *also* make our code +more complex. So, was the property-caching trick we just used worth +it? Maybe... but I'm going to revert it. + +Remove the property caching logic and just return +`$this->calculateUserActivityText($user)`: + +[[[ code('2597dbf705') ]]] + +And... we don't need the `$userStatuses` property anymore: + +[[[ code('ad6a32065f') ]]] + +We *could* stop here and say: this spot is not worth optimizing. Or, we can try a +different solution - like using a *real* caching layer. After all, this label +probably won't change very often... and it's probably not *critical* that the +label changes at the *exact* moment a user adds enough comments to get to the +next level. Caching could be an easy win. + +## Adding Caching + +Back in `AppExtension`, autowire Symfony's cache object by adding an argument +type-hinted with `CacheInterface` - the one from `Symfony\Contracts\Cache`. I'll +press `Alt`+`Enter` and select "Initialize fields" to make PhpStorm create a new +property with this name and set it in the constructor: + +[[[ code('5031282329') ]]] + +Down in the method, let's first create a cache key that's specific to each user. +How about: `$key = sprintf('user_activity_text_'.`and then `$user->getId()`: + +[[[ code('cbdea6a9bb') ]]] + +Wow, I *just* realized that my `sprintf` here is totally pointless. + +Then, `return $this->cache->get()` and pass this `$key`. If that item exists in +the cache, it will return immediately: + +[[[ code('9cafd8e95d') ]]] + +*Otherwise*, it will execute this callback function, pass us a `CacheItemInterface` +object and *our* job will be to return the value that *should* be stored in cache. + +Hmm... I need the `$user` object inside here. Add `use` then `$user` to bring it +into scope. Then return `$this->calculateUserActivityText($user)`: + +[[[ code('95f4cc0300') ]]] + +I think it's probably safe to cache this value for one hour: that's long enough, +but not *so* long that we need to worry about adding a system to manually *invalidate* +the cache. Set the expiration with `$item->expiresAfter(3600)`: + +[[[ code('b2c76be435') ]]] + +So... does this help? Of course it will! More importantly, because we decided we +don't need to worry about adding more complexity to *invalidate* the cache, +it's probably a big win! But let's find out for sure. + +Move over and refresh. Boo - 500 error. We're in the `prod` environment... and I +forgot to rebuild the cache: + +```terminal +php bin/console cache:clear +``` + +And: + +```terminal +php bin/console cache:warmup +``` + +## Profiling with Cache + +Refresh again. And... profile! I'll name this one: `[Recording] Show page real cache`. +Open up the call graph: https://bit.ly/sf-bf-real-caching. + +This time things look *way* better. But let's not trust it: go compare the *original* +profile - before we even did property caching - to this new one: +https://bit.ly/sf-bf-compare-real-cache. + +Wow. The changes are significant... and there's basically no downside to +the changes we made. Even our memory went down! You can also compare this to the +property caching method: +https://bit.ly/sf-bf-compare-prop-real-caching. Yea... it's way better + +And really, this is *no* surprise: *fully* caching things will... of course be +faster! The *question* is how *much* faster? And if adding caching means that +you *also* need to add a cache invalidation system, is that performance boost +worth it? Since we don't need to worry about invalidation in this case, it was +*totally* worth it. + +Next: let's find & solve a classic N+1 query problem. The final solution might +not be what you traditionally expect. diff --git a/sfcasts/call-graph.md b/sfcasts/call-graph.md new file mode 100644 index 0000000..57de896 --- /dev/null +++ b/sfcasts/call-graph.md @@ -0,0 +1,133 @@ +# Finding Issues via the Call Graph + +There are two different ways to optimize any function: either optimize the +code *inside* that function *or* you can try to call the function less times. In +our case, we found that the most problematic function is `UnitOfWork::createEntity`. +But this is a *vendor* function: it's not *our* code. So it's not something that +we can optimize. And honestly, it's probably already super-optimized anyways. + +But we *could* try to call it less times... if we can understand *what* in our app +is causing so many calls! The call graph - the big diagram in the center of this page - +holds the answer. + +## Call Graph: Visual Function List + +Start by clicking on the magnifying glass next to `createEntity`. Woh! That +zoomed us straight to that "node" on the right. Let's zoom out a little. + +The first thing to notice is that the call graph is basically a visual +representation of the information from the function list. On the left, it says +this function has two "callers". On the right, we can see those two callers. +But when you're trying to figure out the *big* picture of what's going on, +the call graph is *way* nicer. + +## The Critical Path + +Let's zoom out a bunch further. Now we can see a clear red path... that eventually +leads to the dark red node down here. This is called the critical path. One of +Blackfire's main jobs is to help us make sense out of all this data. One way it +does that is exactly this: by highlighting the "path" to the biggest problem in +our app. + +I'm going to hit this little "home" icon - that will reset the call graph, instead +of centering it around the `createEntity` node. In this view, Blackfire *does* +hide some less-important information around the `createEntity` node, but it gives +us the best overall summary of what's going on: we can clearly see the critical +path. The critical thing to understand is: why is that path in our app so slow? + +Let's trace up from the problem node... to find where *our* code starts. Ah, +here's our controller being rendered... and then it renders a template. That's +interesting: it means the problem is coming from *inside* a template... from +inside the `body` block apparently. Then it jumps to a Twig extension called +`getUserActivityText()`... that calls something else +`CommentHelper::countRecentCommentsForUser()`. That's the last function before it +jumps into Doctrine. + +## Finding the Problem + +So the problem in our code is something around this `getUserActivityText()` stuff. +Let's open up this template: `main/sighting_show.html.twig` - at +`templates/main/sighting_show.html.twig`. + +If you look at the site itself, each commenter has a label next to them - like +"hobbyist" or "bigfoot fanatic" - that tells us how *active* they are in the great +and noble quest of finding BigFoot. Over in the Twig template, we *get* this text +via a custom Twig filter called `user_activity_text`: + +[[[ code('f1e1bd19b2') ]]] + +If you're not familiar with Twig, no problem. The important piece is that +whenever this filter code is hit, a function inside `src/Twig/AppExtension.php` +is called... it's this `getUserActivityText()` method: + +[[[ code('25f70cc54f') ]]] + +This counts how many "recent" comments this user has made... and via our +complex & proprietary algorithm, it prints the correct label. + +Back over in Blackfire, it told us that the last call before Doctrine was +`CommentHelper::countRecentCommentsForUser()` - that's *this* function call +right here! + +[[[ code('eebcbf8fdf') ]]] + +Let's go open that up - it's in the `src/Service` directory: + +[[[ code('91c2e18d2c') ]]] + +Ah. If you don't use Doctrine, you might not see the problem - but it's one +that can easily happen no matter *how* you talk to a database. Hold +Command or Ctrl and click the `getComments()` method to jump inside: + +[[[ code('36223d01bf') ]]] + +Here's the story: each `User` on our site has a database relationship to the +`comment` table: every user can have many comments. The way our code is written, +Doctrine is querying for *all* the data for *every* comment that a User has +*ever* made... simply to then loop over them, and count how many were created within +the last 3 months: + +[[[ code('9ef46f3586') ]]] + +It's a massively inefficient way to get a simple count. *This* is problem number one. + +It seems obvious now that I'm looking at it. But the nice thing is that... it's +not a huge deal that I did this wrong originally - Blackfire points it out. And +not over-obsessing about performance during development helps prevent +premature optimization. + +## Attempting the Performance Bug Fix + +So let's fix this performance bug. Open up `src/Repository/CommentRepository.php`. +I've already created a function that will use a direct COUNT query to get the +number of recent comments *since* a certain date: + +[[[ code('df24b758bf') ]]] + +Let's use this... instead of my current, crazy logic. + +To access `CommentRepository` inside `CommentHelper` - this *is* a bit specific +to Symfony - create a `public function __construct()` and *autowire* it by adding +a `CommentRepository $commentRepository` argument: + +[[[ code('a3de00027e') ]]] + +Add a `private $commentRepository` property... and set it in the constructor: +`$this->commentRepository = $commentRepository`: + +[[[ code('333c9d8fc0') ]]] + +Now... I don't need *any* of this logic. Just return +`$this->commentRepository->countForUser()`. Pass this `$user`... and go steal +the `DateTimeImmutable` from below and use that for the second argument. Celebrate +by killing the rest of the code: + +[[[ code('82442bb0ca') ]]] + +If we've done a good job, we will hopefully be calling that `UnitOfWork` function +*many* less times - the 23 calls into Doctrine from `CommentHelper` eventually +caused many, many things to be called. + +So... let's profile this and see the result! We'll do that next and use Blackfire's +"comparison" feature to *prove* that this change *was* good... except for one +small surprise. diff --git a/sfcasts/cloud-environments.md b/sfcasts/cloud-environments.md new file mode 100644 index 0000000..b05890a --- /dev/null +++ b/sfcasts/cloud-environments.md @@ -0,0 +1,87 @@ +# Staging Servers on SymfonyCloud + +For your site, you *hopefully* have a staging environment - or maybe *multiple* +staging environments where you can deploy new features and test them. What about +*those* machines? Should we *also* run Blackfire builds on them? + +## Why Profile Staging Servers? + +At first, that *might* not seem important. After all, if a staging machine is a +bit slow, who cares? But thanks to the *assertions* we've been writing, if we +executed our Blackfire scenarios on a *staging* machine, we could identify +performance failures *before* deploying them to production. And if you have a +*really* cool setup, you can even have build results posted automatically to +your pull request. OooOOoo. + +## Separating Staging from Production on Blackfire + +Getting Blackfire set up on a staging server *seems* simple enough: just repeat +the Blackfire installation process... on a different server! But stop! I don't +want you to *quite* do that. + +Why? I want your Blackfire production environment to *only* contains builds from +your *actual* production servers. I want this to be a *perfect* history and +representation of production *only*. If we suddenly start adding builds from a +staging server - which maybe has different hardware specs... or is running a buggy +new feature - some of those builds will fail... and we'll get extra noise in +our notifications. + +Instead, I like to create a *second* Blackfire environment and send profiles +to *it*. If I have *multiple* staging servers, I make them *all* use this same +new environment. + +## SymfonyCloud Environments + +But... before we create that *second* Blackfire environment... I need you to - once +again - pretend like Blackfire doesn't exist at *all*... for a few minutes. + +Because before we talk about how we *profile* a staging server, we need to *create* +a staging server and deploy to it. SymfonyCloud has an *incredible* way to do this. +*Unfortunately*, the feature in Symfony cloud that *does* this is called... +environments. And it has absolutely *nothing* to do with Blackfire environments. + +Here's how it works: in addition to your `master` branch, which is your production +server, SymfonyCloud allows you to *deploy* different git *branches*. Each deploy +will get its own unique URL. Each branch deployment is called an "environment". If +you run: + +```terminal +symfony envs +``` + +Yep! We currently have *one* environment: `master`. It's the "current" environment +because we're checked out to the `master` git branch locally. + +Ok, pretend that we're working on a new feature. And so, we want to +create a new local branch for it. Instead of doing that manually, run: + +```terminal +symfony env:create some_feature +``` + +This does two things. First, it created a new local branch called `some_feature`. +That's no big deal: we could have done that by hand. Second, it *deploys* that +branch! It does this by creating a "clone" of the master environment: - even creating +a copy of the production database! + +I'll fast-forward through the deploy. When it finishes, it gives us a URL to the +deploy. This is a *different* URL than on production: it's a totally separate, +isolated deployment. Let's open this the lazy way: + +```terminal +symfony open:remote +``` + +Say hello to our *staging* server for the `some_feature` branch, which you can +see contains a *copy* of the production database! How cool is that? + +## Configuring Blackfire on the Environments + +Back on Blackfire, refresh to see the builds for the production environment. When +we deployed to that environment, it did *not* create a new build. We expected +that. When we added the integration to SymfonyCloud - we told it to trigger a +build on this Blackfire environment whenever we deploy the *master* branch *only*. +We did that because we *don't* want these staging servers to create builds here. + +Next, let's create a *second* environment and configure our staging servers to +use *it*. diff --git a/sfcasts/command-line-scripts.md b/sfcasts/command-line-scripts.md new file mode 100644 index 0000000..477eb06 --- /dev/null +++ b/sfcasts/command-line-scripts.md @@ -0,0 +1,157 @@ +# Profiling Command Line scripts + +As handy as the CLI tool is for profiling AJAX requests, its *true* purpose is +something different: it's to allow us to profile our custom command-line scripts. +Let's check out an example. I've already created a command line script that you +can execute by calling: + +```terminal +php bin/console app:update-sighting-scores +``` + +What does it do? Let me show you! Each Bigfoot sighting on the site has, what we +call, a "Bigfoot believability score". Right now, this shows zero for *every* sighting. +That's because we use a highly-complex and proprietary algorithm to calculate +this. It's *such* a heavy process that, instead of figuring it out on page-load, +we store the current value in a column on each row of the table. To *populate* that +column, we run this command once a day: it loops over all the sightings, calculates +the newest "believability score" and saves it back to the database. Try it: + +```terminal-silent +php bin/console app:update-sighting-scores +``` + +It takes a few seconds... and when we go back to the site and refresh... we find +out that this Bigfoot sighting in *kind of* believable - a score of 5 out of 10. + +The code for this lives at `src/Command/UpdateSightingScoresCommand.php`: + +[[[ code('c8b793a778') ]]] + +You *might* already see a problem. But if you don't... that's ok! Let's see what +Blackfire thinks. This time, run that *same* command, but put `blackfire run` +at the beginning: + +```terminal-silent +blackfire run bin/console app:update-sighting-scores +``` + +Woh. It's a *lot* slower now: we're seeing evidence of how the PHP extension +slows down the process... and wow... it's just getting slower, and slower. I'm +going to use the magic of TV to speed things up. + +Ok, let's look at that profile! http://bit.ly/sf-bf-console-original + +Woh! Some `computeChangeSet()` function was called almost 500,000 times! Ah! That's +taking up *half* of the exclusive time! Because this call is *such* a problem, +Blackfire is hiding a *lot* of data, all of which is unimportant relative to +what we *are* seeing. + +That's cool because the result is a *super* simple call graph: here's our +command... here's `EntityManager::flush()`... and then it goes into deep +Doctrine stuff. + +Let's check out the command and look for the `EntityManager::flush()` call: + +[[[ code('4810ce612d') ]]] + +Yep! I flush once each time at the end of the loop, which updates that database +row. If you're familiar with Doctrine, you might know the problem: you don't +*need* to call `flush()` inside the loop. Instead, move this *after* the loop: + +[[[ code('c651727cb3') ]]] + +With this change, Doctrine will try to perform *all* update queries at the *same* +time... which even lets it try to *optimize* those queries if it can. But the *big* +problem with our old code was something related to Doctrine's +`UnitOfWork::computeChangeSet()`. *Each* time you call `flush()` in Doctrine, it +looks at *all* the objects it has queried for - so *all* of the `BigFootSighting` +objects - and checks *every single one* to see if any data has changed that needs +to be sync'ed back to the database with an `UPDATE` query. Yep, with the *old* +code, it was checking *every* property of *every* record for updated data on +*every* loop. Hence...the 450,000 calls! + +Let's profile again with the updated code. + +```terminal-silent +blackfire run php bin/console app:update-sighting-scores +``` + +This time it's *much* faster - I don't even think we need to compare the profiles: +56 seconds down to 1. Open it up: http://bit.ly/sf-bf-console2. + +## Complexity, Speed & Reliability + +Could we optimize this further? Maybe! But this performance enhancement already +came at a cost: reduced reliability. I originally put the call to `flush()` inside +the loop *not* because I didn't know better... but to make the command a little +more resilient. If, for example, the command gets through *half* of the records +and then has an error, with the *new* code, *none* of the scores will be saved. + +It's beyond the scope of this tutorial, but I *love* to make my command-line +scripts *super* forgiving. If this were a real app, I would probably save the +datetime that I last calculated the score for each record and use that to query +for *only* the rows that have *not* been updated in the last 24 hours. I would +*also* move the `flush()` back into the loop: + +```php +$sightings = $this->bigFootSightingRepository + ->findAllScoreNotUpdatedSince(new \DateTime('-1 month')); + +foreach ($sightings as $sighting) { + // ... + + $sighting->setScore($score); + $sighting->setScoreLastUpdatedAt(new \DateTime()); + $this->entityManager->flush(); +} +``` + +Thanks to those changes, if this command failed half-way through, the first half +of the records would already be updated and we could run the command again to *resume* +with the ones that are still *not* updated. + +But wouldn't that make the command super-slow again? Yep! And with the help of +Blackfire, you can test solutions that improve performance without making the +command less reliable. For example, we could make the first query only return +an array of integer ids. Then, inside the loop, use that id to query for the +*one* object you need. That would mean we only have *one* `BigFootSighting` object +in memory at a time instead of all of them: + +```php +$sightingIds = $this->bigFootSightingRepository + ->findIdsScoreNotUpdatedSince(new \DateTime('-1 month')); + +foreach ($sightingIds as $id) { + $sighting = $this->bigFootSightingRepository->find($id); + + $sighting->setScore($score); + $sighting->setScoreLastUpdatedAt(new \DateTime()); + $this->entityManager->flush(); +} +``` + +You can go further by calling `EntityManager::clear()` after `flush()` to, sort of, +"clear" Doctrine's memory of the `BigFootSighting` object you just finished... +so that it doesn't check it for changes when we call `flush()` during the *next* time +through the loop: + +```php +$sightingIds = $this->bigFootSightingRepository + ->findIdsScoreNotUpdatedSince(new \DateTime('-1 month')); + +foreach ($sightingIds as $id) { + $sighting = $this->bigFootSightingRepository->find($id); + + $sighting->setScore($score); + $sighting->setScoreLastUpdatedAt(new \DateTime()); + $this->entityManager->flush(); + $this->entityManager->clear($sighting); +} +``` + +The point is: like with *everything*, make your code do what it needs to... +then use Blackfire to solve the real performance issues... if you have any. + +Next, there's a *giant* screen in Blackfire that we haven't even looked at yet. +What!? It's... the Timeline! diff --git a/sfcasts/comparison-build-tests.md b/sfcasts/comparison-build-tests.md new file mode 100644 index 0000000..4d23338 --- /dev/null +++ b/sfcasts/comparison-build-tests.md @@ -0,0 +1,146 @@ +# Testing a Build Compared to the Last Build + +A *long* time ago in this tutorial, we talked about Blackfire's *truly* awesome +"comparison" feature. If you profile a page, make a change, then profile it +again, you can *compare* those two profiles to see *exactly* how that change +impacted performance. + +When you use the build system, you can do the *exact* same thing... and you can +*even* write "tests" that compare a build to the *previous* build. For example, +you could say: + +> Yo! If the wall time on the homepage is *suddenly* 30% *slower* than the +> previous build, I want this build to fail. + +## Adding a Comparison Test with percent() + +*How* can we do that? It's dead simple. Add a new global metric - how about +"Pages are not suddenly much slower" - and set this to run on every page: +`path: /.*`. For the assertion, we can use a special function called percent: +`percent(main.wall_time) < 30%`: + +[[[ code('a136e55781') ]]] + +That's it! There's also a function called `diff()`. If you said +`diff(metrics.sql.queries.count) < 2` it means that the *difference* between +the number of SQL queries on the new profile minus the old profile should be +less than 2. + +Let's see what this looks like! Find your terminal and commit these changes: + +```terminal-silent +git status +git add . +git commit -m "adding global wall time diff assert" +``` + +Now... deploy! + +```terminal-silent +symfony deploy --bypass-checks +``` + +## Comparison Tests: Not for Manual Builds + +But... bad news. If we waited for that to finish deploying... and then triggered +a new custom build... that test would *not* run. In fact, I want you to see +that. Wait for the deploy to finish - okay, good - then move back over and start +a build. + +This does what we expect: it executes our scenario and creates 2 profiles. +Look at the 3 successful constraints for the homepage: we see the other global +test about "HTTP requests should be limited"... but we don't see the new one. +What gives? + +So... when you create a build, you can specify a "previous" build that it should +be compared to by using an internal "build id". Our project is too new to see it, +but this happens automatically with "periodic" builds: our comparison assertion +*will* execute on periodic builds. + +***TIP +Triggering builds via a webhook requires an Enterprise plan. +*** + +But when we create a manual build... there's no way to specify a "previous" build... +which is why the comparison stuff doesn't work. *Fortunately*, since I don't want +to wait 12 hours to see if this is working, there is *another* way to trigger +a build: through a webhook. Basically, if you want to create a build from outside +the Blackfire UI, you can do that by making a request to a specific URL. And when +you do that, you can *optionally* specify the "previous build" that this new build +should be compared to. + +## Automatic Build on Deploy + +This webhook-triggered-build is *especially* useful in one specific situation: +creating a build each time you *deploy*. If you did that correctly, your +comparison assertion would compare the latest deploy to the *previous* deploy... +which is pretty awesome. + +Because we're using SymfonyCloud, this is dead-simple to set up. + +Find the Blackfire SymfonyCloud documentation and, down here under "Builds", +I'll select our environment. Basically, by running this command, we can tell +SymfonyCloud to send a webhook to create a Blackfire build *each* time we deploy. + +Copy it, move over to your terminal and... paste: + +```terminal-silent +symfony integration:add --type=webhook --url='https://USER:PASS@blackfire.io/api/v2/builds/env/aaaabbee-abcd-abcd-abcd-c49b32bb8f17/symfonycloud' +``` + +Hit enter to report all events and enter again to report all states. For the +environments - this is asking which *SymfonyCloud* environments should trigger +builds. Answer with just `master` - I'll explain why soon. + +And... done! Let's redeploy our app. Oh, but before we do, refresh our builds +page. Ok, we have 5 builds right now. Now run: + +```terminal +symfony redeploy --bypass-checks +``` + +This should be pretty quick. Then... go refresh the page. Yes! A new build - +number 6 - triggered by SymfonyCloud. And it *passes*. Awesome! Let's redeploy +again: + +```terminal-silent +symfony redeploy --bypass-checks +``` + +When that finishes... there's build 7! But to see the comparison stuff in action, +I need to do a *real* deploy so that the next build is tied to a *new* Git sha. +I'll do a meaningless change, commit, then deploy: + +```terminal-silent +git commit -m "triggering deploy" --allow-empty +symfony deploy --bypass-checks +``` + +## Seeing the Compared Builds + +Actually, I could have skipped changing *any* files and committed with +`--allow-empty` to create an empty commit. When this finishes... no surprise! +We have build 8! + +On *this* build, it's super cool: each profile has a "Show Comparison" link to open +the "comparison" view of that profile compared to the *same* profile on the build +from the *last* deploy - which - if you click "latest successful build" - is +build 7. + +Back on build 8, click the "Show 4 successful constraints" link. There it is! +We can see our "Pages are not suddenly *much* slower" assertion! It's comparing +the wall time of this profile to the one from the last build. + +Click to open up the profile... and make sure you're on the Assertions tab. +I love this: 2 page-specific assertions from the scenario, and 2 global assertions: +one using the `percent()` function. + +The "Recommendations" *also* got a bit better: Blackfire automatically has some +built-in recommendations using `diff`: this *recommends* that the new profile +should have less than 2 *additional* queries compared to the last build. It +*looks* like it failed... but that's just because the *other* part of this +recommendation - not making more than 10 total queries - failed. + +Next: what about running builds on your staging server so you can catch performance +issues before going to production? Or what about executing Blackfire builds on +each pull request? We can *totally* do that - with a *second* environment. diff --git a/sfcasts/comparisons.md b/sfcasts/comparisons.md new file mode 100644 index 0000000..9a667a7 --- /dev/null +++ b/sfcasts/comparisons.md @@ -0,0 +1,60 @@ +# Comparisons: Validate Performance Changes, Find Side Effects + +We've just updated our code to make a `COUNT` query instead of querying for *all* +the comments for a user... just to count them. So, the page will *definitely* be +faster. Right? Are you *absolutely* sure? Well, *I* think it will be faster... but +sometimes making one part of your code faster... will make other parts slower. +Fortunately, Blackfire has a special way to *prove* that a performance tweak *does* +in fact help. + +Let's profile the page now - I'll refresh... then click to profile. Give it a name +to stay organized `[Recording] Show page after count query`. + +Ok! Let's go see the call graph! https://bit.ly/sfcast-bf-profile2 + +Hey! 270 milliseconds total time - the last one was 415. So it *is* faster. We win! +Tutorial over! + +Well... yeah, I agree: it does look faster. But an important aspect of optimization +is understanding *why* something is faster. Like, did this reduce CPU time? I/O +wait time? And, maybe more importantly, did this change cause anything to be +*worse*? For example, a change might decrease CPU time, but *increase* memory. +If that happened, would the change *really* be a good one? It depends. + +## Comparing Profiles + +This leads me to one of my *favorite* tools from Blackfire: the ability to +*compare* profiles. Click back to your dashboard: the top two profiles are from the +initial profile and then the page after using the COUNT query. On the right, hover +over the "Compare" button on the original, click, then click the new one. + +Say hello to the *comparison* view: https://bit.ly/sf-bf-compare1-2. +Everything that's faster, or "better" is in blue. Anything that's slower or worse +will be in red. And yea, it looks like the new profile is better in *every* single +category. Ok, the I/O wait is higher - but .1 millisecond higher - that's just +"noise". + +Anyways, the comparison *proves* that this *was* a good change. Really, it's a huge +win! On the call graph, in the darkest blue, the critical path *this* time is the +path that *improved* the most. Click the `UnitOfWork` call now. Wow. The inclusive +time is down by 90 milliseconds and even the memory plummeted: down 1.39 megabytes. + +***TIP +The SQL Query information requires a Profiler plan or higher. +*** + +But wait. One of the items on top is called "SQL Queries". The total query *time* +is less than before... but we've *added* 5 *more* queries. We removed these 18 +queries... but added 23 new ones. + +Is that a problem? Probably not. Overall, this change was good. And if having too +many queries *does* create a *real* problem - not just an imaginary one of "too +many queries" - Blackfire will help us discover that. The big takeaway here is: +don't just assume that a performance enhancement... *is* actually better. We'll +see this later - not *every* change we'll do in this tutorial will prove to be +a good one. + +Next: Blackfire has a deep understanding of PHP, database queries, Redis calls +and even libraries like Symfony, Doctrine, Magento, Composer, eZ platform, +Wordpress and others. Thanks to that, it automatically detects problems +and recommends solutions. diff --git a/sfcasts/custom-metrics.md b/sfcasts/custom-metrics.md new file mode 100644 index 0000000..79a70e4 --- /dev/null +++ b/sfcasts/custom-metrics.md @@ -0,0 +1,91 @@ +# Per-Page Time Metrics & Custom Metrics + +We know that the scenario will be executed against our *production* +server only. If we profiled a *local* page, this stuff has no effect. +That means that the results of these profiles *should* have *less* variability. +Not *no* variability: if your production server is under heavy traffic, the +profiles might be slower than normal. But, it will have less variability than trying +to compare a profile that you created on your local machine with a profile created +on production: those are totally different machines and setups. + +***TIP +I also recommend adding `samples 10` to each scenario. This will then use +10 samples (like normal Blackfire profiles) and further reduce variability: + +``` + visit url("/") + name "Homepage" + samples 10 + ... +``` +*** + +## Cautiously Adding Time-Based Assertions + +This means that you can... *maybe* add some time-based assertions... as long as +you're conservative. For example, on the homepage, let's `assert` that +`main.wall_time < 100ms`: + +[[[ code('113a6550f5') ]]] + +By the way, *most* metrics start with `metrics.` and you can look on the timeline +to see what's available. A *few* metrics - like wall time and peak memory - start +with `main.`. + +Anyways, as you can see inside Blackfire, our homepage on production +*normally* has a wall time of about 50ms... so 100ms is *fairly* conservative. +But time-based metrics are *still* fragile. Doing this *will* likely result in +some random failures from time-to-time. + +Let's commit this: + +```terminal-silent +git status +git add . +git commit -m "adding homepage time assertions" +``` + +And deploy: + +```terminal-silent +symfony deploy --bypass-checks +``` + +## Custom Metrics + +While that's deploying, I want to show you a *super* powerful feature that we +won't have time to experiment with: custom metrics. Google for "Blackfire metrics". +In addition to the timeline, this page *also* lists *all* of the metrics that +are available. + +But you can also create your *own* metrics inside `.blackfire.yaml`. In addition +to `tests` and `scenarios`, we can have a `metrics` key. For example, this +creates a custom metric called "Markdown to HTML". The *real* magic is the +`matching_calls` config: any time the `toHtml` method of this made-up +`Markdown` class is called, its data will be *grouped* into the `markdown_to_html` +metric. + +That's powerful because you can immediately use that metric in your tests. For +example, you could assert that this metric is called exactly zero times - as a +way to make sure that some caching system is *avoiding* the need for this to ever +happen on production. Or, you could check the memory usage... or other dimension. + +You can use some pretty serious logic to create these metrics: making it match +only a specific *caller* for a function, OR logic, regex matching and ways to +match methods, calls from classes that implement an interface and many other +things. You can even create *separate* metrics for the *same* method based on which +*arguments* are passed to them. They went a little nuts. + +## Checking the Time-Based Metric + +Anyways, let's check on the deploy. Done! Go back - I'll close this tab - +and let's create a new build. Call it "With homepage wall time assert". Start build! + +And... it passes! This time we can see an extra constraint on the homepage: wall +time needs to be less than 100ms. If it's *greater* than 100ms and you have +notifications configured, you'll know immediately. + +Next: now that we have this idea of builds being created every 6 hours, we can +do some cool stuff, like *comparing* a build to the build that happened before +it. Heck we can even write *assertions* about this! Want a build to fail if +a page is 30% *slower* than the build before it? We can do that. diff --git a/sfcasts/deploy.md b/sfcasts/deploy.md new file mode 100644 index 0000000..e48bbb4 --- /dev/null +++ b/sfcasts/deploy.md @@ -0,0 +1,108 @@ +# Deploying to SymfonyCloud + +Transition point! *Everything* we've talked about so far has included profiling +our *local* version of the site. But things get even *cooler* when we start to +profile our *production* site. Having *real* data often shows performance problems +that you just *can't* anticipate locally. And because of the way that Blackfire works, +we can create profiles on production *without* slowing down our servers and +affecting real users. *Plus*, once we're profiling on production, we can unlock +even *more* Blackfire features. + +So... let's get this thing deployed! You can use *any* hosting system you want, +but I'm going to deploy with SymfonyCloud: it's what we use for SymfonyCasts and +it makes deployment dead-simple for Symfony apps. It also has a free trial if you +want to code along with me. + +## Initializing your SymfonyCloud Project + +Find your terminal and make sure you're on your `master` branch. That's not required, +but will make life easier. Start by running: + +```terminal +symfony project:init +``` + +This will create a few config files that tell SymfonyCloud *everything* it +needs to know to deploy our site. The most important file is `.symfony.cloud.yaml`: + +[[[ code('9d88e127ab') ]]] + +Ah, this says we want PHP 7.1. Let's *upgrade* by changing that to 7.3: + +[[[ code('50d5cf8bd0') ]]] + +Back at the terminal, copy the *big* git command: this will add all the new files +to git and commit them: + +```terminal-silent +git add .symfony.cloud.yaml .symfony/services.yaml .symfony/routes.yaml php.ini +git commit -m "Add SymfonyCloud configuration" +``` + +Next, to *tell* SymfonyCloud that we want a new "server" on their system, run: + +```terminal +symfony project:create +``` + +Every "site" in SymfonyCloud is known as a "project" and we only need to run +this command *once* per app. You can ignore the big yellow warning - that's +because I have a few other SymfonyCloud projects attached on my account. Let's +call the project "Sasquatch Sightings" - that's just a name to help us identify +it - and choose the "Development" plan. + +The development plan includes a free 7 day trial... which is *awesome*. You *do* +need to enter your credit card info - that's a way to prevent spammers from creating +free trials - but it won't be charged unless you run `symfony project:billing:accept` +later to keep this project permanently. + +I already have a credit card on file, so I'll use that one. Once we confirm, +this *provisions* our project in the background... I *assume* it's waking up +thousands of friendly robots who are carefully creating our new space in... +the "cloud". Hey! There's one now... dancing! + +And... done! + +## Deploying & Security Checks + +Ready for our first deploy? Just type: + +```terminal +symfony app:prepare:deploy --branch=master --confirm --this-is-not-a-real-command +``` + +Kidding! Just run: + +```terminal +symfony deploy +``` + +And... hello error! This is actually great. Really! The deploy command automatically +checks your `composer.lock` file to see if you're using any dependencies with known +security vulnerabilities. Some of my Symfony packages *do* have vulnerabilities... +and if this were a real app, I would upgrade those to fix that problem. But... +because this is a tutorial... I'm going to ignore this. + +## Our First Deploy + +Run the command again with a `--bypass-checks` flag: + +```terminal-silent +symfony deploy --bypass-checks +``` + +We still see the big message... but it's deploying! This takes care of *many* +things automatically, like running `composer install` and executing database +migrations. This first deploy will be slow - especially to download all the +Composer dependencies. I'll fast-forward. It also handles setting up Webpack +Encore... and even creates a shiny new SSL certificate. Those are *busy* robots! + +And... done! It dumped out a funny-looking URL. Copy that. In a *real* project, +you will attach your *real* domain to SymfonyCloud. But this "fake" domain will +work *beautifully* for us. + +Spin back over and pop that URL into your browser to see... a beautiful 500 error! +Wah, wah. Actually, we're *super* close to this all working. Next, let's use a +special command to debug this error, add a database to SymfonyCloud - yep, that's +the piece we're missing - and load some dummy data over a "tunnel". *Lots* of +good, nerdiness! diff --git a/sfcasts/env-profile.md b/sfcasts/env-profile.md new file mode 100644 index 0000000..851fbfa --- /dev/null +++ b/sfcasts/env-profile.md @@ -0,0 +1,71 @@ +# Production Profile: Cache Stats & More Recommendations + +We just profiled our *first* page on production, which is using the Blackfire Server +Id and Token for the *environment* we created. + +## Profiles Belong to the Environment + +Go to https://blackfire.io, click "Environments", open our new environment... +and click the "Profiles" tab. Yep! Whenever *anyone* creates a profile using this +environment's credentials, it will now show up *here*: the profile *belongs* to +this environment. We haven't invited any other users to this environment yet, but +if we did, they would *immediately* be able to access this area *and* trigger new +profiles with *their* browser extension. + +If you go to back to https://blackfire.io to see your dashboard, the new profile +*also* shows up here. But that's purely for convenience. The profile *truly* +belongs to the environment. You can even see that right here. But Blackfire +places *all* profiles that *I* create on this page... to make life nicer. + +Click the profile to jump into it. Of course... this looks *exactly* like any +profile we created on our local machine. But it *does* have a few differences. + +## Caching Information + +Hover over the profile name to find... "Cache Information". We talked about +this earlier: it shows stats about *various* different caches on your server and +how much space each has available. Now that we're profiling on production, this +data is *super* valuable! + +For example, if your OPcache filled up, your site would start to slow down +*considerably*... but it might not be very obvious when that happens. It's not +like there are alarms that go off once PHP runs out of OPcache space. But thanks +to this, you can easily see how things *really* look, right now, on production. +If any of these are full or nearly full, you can read documentation to see which +setting you need to tweak to make that cache bigger. + +## Quality & Security Recommendations + +The *other* thing I want to show you is under "Recommendations" on the left. +There are 3 *types* of recommendations... and we have one of each: the first is +a *security* recommendation, the second is a *quality* recommendation and the third +a *performance* recommendation. Only the *performance* recommendations come +standard: the other two require an "Add on"... which I didn't have until I +started using my organization's plan. + +As always, to get a *lot* more info about a problem and how to fix it, you can +click the question mark icon. + +## Converting Recommendations into Assertions + +One of my *favorite* things about recommendations is that you can easily +*convert* any of these into an *assertion*. If you click on assertions, you'll +remember that we created one "test" that said that every page should have - at +*maximum* - one HTTP request. + +We configured that inside of our `.blackfire.yaml` file: we added `tests`, +configured this test to apply to every URL, and leveraged the metrics system to +write an expression. + +Back on the recommendations, click to see more info on one of these... then +scroll down. *Every* recommendation contains code that you can copy into +your `.blackfire.yaml` file to *convert* that recommendation into a *test*... +or "assertion". + +That *might* not seem important right now... because so far, it looks like doing +that would simply "move" this from a "warning" under "Recommendations" to a +"failure" under "Assertions"... which is cool... but just a visual difference. + +*But*! In a few minutes, we'll discover that these assertions are *much* more +important than they seem. To see why, we need to talk about the *key* feature and +superpower of environments: *builds*. diff --git a/sfcasts/environment-vars.md b/sfcasts/environment-vars.md new file mode 100644 index 0000000..19af1fe --- /dev/null +++ b/sfcasts/environment-vars.md @@ -0,0 +1,78 @@ +# Blackfire Environment Variables + +Often, your production server will have different - hopefully *bigger* - hardware +than your staging server... which means that your staging builds may run slower +than production. That's going to be a problem if you have *time* based +metrics: the wall time of a build may be less than 100ms on production... but +*more* than that on staging: + +[[[ code('9ce71f7a0e') ]]] + +That means the staging builds will always fail. Bummer! + +## Hello Build Variables + +No worries. To help, each environment can define *variables*. Check it out: inside +the metric expression, I'll add a set of parentheses around the `100ms` and then +say *times* and call a `var()` function. I'll invent a new variable: `speed_coefficient` +and give it a *default* value - via the 2nd argument - of 1: + +[[[ code('773ae40b53') ]]] + +*Now*, when this assertion is executed, it will assert that the wall time is less +than 100ms *times* whatever this `speed_coefficient` variable is. What *is* +`speed_coefficient`? It's *totally* something I just made up and it is *not* set +anywhere. Where *do* we set it? Inside our Blackfire environment! + +Copy the variable name and go into the Non-Master environment. On the right, +near the bottom, click the pencil icon to edit our variables. Add the variable +set to... how about 2: that will allow the staging server to be *twice* as slow. + +Do we *also* need to set this inside the "Production" environment? Nope: I'll just +let it use the default value of 1. + +Let's try it! Spin back over to your terminal, add the change... and commit: + +```terminal-silent +git add . +git commit -m "adding speed_coeffient variable for wall time assert" +``` + +As a reminder, we're on the `some_feature` branch. So when we run: + +```terminal +symfony deploy --bypass-checks +``` + +We're deploying to *that* environment. + +## Seeing the Variable in Action + +When that finishes... move back over to the Blackfire environment, refresh and... +hello new build! Look inside. There are two cool things. First, under the homepage, +you can see the `speed_coefficient` in action - the little "2" tells us the value +it's using. So, in reality, it's asserting that 50.8ms is less than *200* +milliseconds. + +## Feature Branch Comparisons + +The *other* thing I want you to notice is that, if you go back to the builds page, +we have now built the `some_feature` branch *twice*. When you click on the second, +newer build, it has the *comparison* stuff! It allows us to *compare* this build +to the *previous* commit on the *same* branch. This allows you to see - +commit-by-commit - *when* a feature started having performance problems. + +And... that's it for the Blackfire tutorial! I hope you *loved* this nerdy trip into +the *depths* of performance as much as I did. Blackfire can give you a *lot* of info +immediately... or you can *really* dive in and make it *sing*. Personally, +I love having the builds and this performance history for SymfonyCasts.com. Oh, +and a special thanks to [Jérôme Vieilledent](https://github.com/lolautruche) - I +almost *definitely* just slaughtered his name - for his endless patience +answering my hundreds of Blackfire questions. + +And as always, if *you* have any questions... or we didn't explain something you +wanted to know about... or you want a cake recipe... we're here for you in the +comments. If you have any *serious* performance wins, we would *love* to hear +about them. + +Alright friends - I wish you a *speedy* day! Seeya next time! diff --git a/sfcasts/environments.md b/sfcasts/environments.md new file mode 100644 index 0000000..40a16f5 --- /dev/null +++ b/sfcasts/environments.md @@ -0,0 +1,127 @@ +# Blackfire Environments + +Now that our site is *deployed* - woo! - how can we get Blackfire working on it? +Well... we already know the answer. If you find the Blackfire Install page... it +makes it easy: I want to install on "a server"... and let's pretend it uses Ubuntu. + +Getting Blackfire installed on your production machine is as easy as running the +commands below to install the Blackfire PHP extension - the Probe, install +the Agent and configure the agent with our server id and token. Easy peasy! + +## Hello: Environments + +But.... *some* Blackfire account levels - offer a kick-butt feature called +*environments*. If you have access to Blackfire environments - or if you're able +to get a "plan" that offers environments, I highly recommend them. + +***TIP +Blackfire environments require a Premium plan or higher. +*** + +An environment is basically an isolated Blackfire account. When you have an +environment, you send your *profiles* to that environment. The first advantage +is that you can *invite* multiple people to an environment, which means that +*anyone* can profile your production site and see other profiles made by people +on your team. It also has *other* superpowers - ahem, builds - that *really* make +it shine. + +## Understanding Organizations + +So let's create an environment! Go back to https://blackfire.io and click on the +"Environments" tab. Actually, click on the "Organizations" tab... that's where +this all starts. Blackfire organizations are a bit like GitHub organizations. +With GitHub, you can subscribe to a "plan" directly on your *personal* account +*or* you can create an organization, have *it* subscribe & pay for a plan, and +then invite individual users to the organization. Blackfire organizations work +*exactly* like that. And if you want to use environments, you need to create +an organization and subscribe to a Blackfire plan *through* that organization. + +This *did* confuse me a bit at first. Basically, unless you just want the +*lowest* Blackfire paid plan, you should probably *always* create an organization +and subscribe to Blackfire through *it*. It just has a few more features than +subscribing with your personal account. + +## Creating an Environment + +*Anyways*, I've already got an organization set up and subscribed to a plan. +Once you *have* an organization, you can click into it to create a new environment. +I already have one for SymfonyCasts.com production. Click to create a new one. +Let's call it: "Sasquatch Sightings Production". + +For the "Environment Endpoint", it wants the URL to the site. Again, if this were +a *real* project, I would attach a *real* domain... but copy the weird domain +name, and paste. Select your timezone, sip some coffee, and... +"Create environment"! + +On the second step, it asks us to provide URLs to test... and it starts with just +one: the homepage. We're going to talk more about this soon, so just leave it. +I'll also uncheck the build notifications - more on those later. + +## Environment vs Personal Server Credentials + +Hit "Save settings" and... we're done! It rewards us with a shiny new "Server Id" +and "Server Token". + +This is *super* important. No matter *how* you install Blackfire on a server, +you eventually need to configure the "Server id" and "Server Token". This is +*basically* a username & password that tells Blackfire which *account* a +profile should be sent to. + +When you register with Blackfire, it immediately created a "Server Id" and +"Server Token" connected with your *personal* account. We used that when we +installed Blackfire on our local machine. But now that we have an environment, +it has its *own* Server Id and token. The drop-down on the Install page is +allowing us to *choose* which credentials we want to see on this page. + +Locally, we should *still* use our *personal* credentials: it keeps things +cleaner. But on *production*, we should use the new *environment's* Server Id +and Token. The install page gives us all the commands we need *using* those +credentials. + +Oh, and by the way: if you have a "free" personal account... but are +attached to an organization with a paid plan, any profiles you create with +your *personal* Server Id and Token will *inherit* the features from that +organization's plan. That lets us use our personal credentials locally and +*still* get all the Blackfire features we're paying for. One exception to that +rule, unfortunately, is "Add-Ons". + +## Configuring Blackfire on SymfonyCloud + +Ok, let's get our production machine set up. I'll select "Symfony Cloud" as my +host... which takes me to a dedicated page on this topic. + +Let's see... step one is, instead of installing Blackfire with something like +`apt-get`, we'll add a line to `.symfony.cloud.yaml`. I already have an +`extensions` key... so just add `blackfire`: + +[[[ code('af0db4c0f2') ]]] + +Boom! Blackfire is installed. Add this file to Git... and commit it: + +```terminal-silent +git add . +git commit -m "adding blackfire extension" +``` + +The *other* step is to *configure* Blackfire. Once again, it has a drop-down +to select between my personal credentials and credentials for an enivornment. +Select our "Sasquatch production" environment. Cool! This gives us a command +to set two SymfonyCloud *variables*. Copy that, move over, and paste: + +```terminal-silent +symfony var:set BLACKFIRE_SERVER_ID=XXXXXX BLACKFIRE_SERVER_TOKEN=XXXXXX +``` + +Ok... we're good! To make both changes take effect, deploy! + +```terminal +symfony deploy --bypass-checks +``` + +I'll fast-forward. Once this finishes... move over and refresh. Ok... everything +still works. *Now*, moment of truth: open the Blackfire browser extension and +create a new profile. It's working! I'll call it: +`[Recording] First profile in production`. + +Next, let's... *look* at this profile! It will contain a *few* new things and +some data that is much more relevant now that we're on production. diff --git a/sfcasts/function-list.md b/sfcasts/function-list.md new file mode 100644 index 0000000..800b778 --- /dev/null +++ b/sfcasts/function-list.md @@ -0,0 +1,123 @@ +# Wall Time, Exclusive Time & Other Wonders + +We just made Blackfire profile our first page. One of the *best* things about +Blackfire is that, instead of just... giving me some raw data-dump and saying: + +> Good luck navigating *that* black pit of data! + +... they expose this *treasure trove* of info on their site with a beautiful +interface. This is called the "call graph". The most challenging part of Blackfire +for me was learning what all this stuff means... so I could *really* get the most +out of it. If you stick with me for the next few minutes, your profiling game +will get a *huge* boost. + +By the way, throughout the tutorial, I'll give you links to view the *exact* +profile on Blackfire that I'm navigating in the video. Feel free to open it up and +play around. The first one is here: +https://bit.ly/sfcasts-bf-profile1. + +And yes, I know, the cool-looking graph in the middle is *calling* to us, but +let's start by looking at the the left side: the list of function calls, ordered +from the functions that took the longest to execute on top... down to the quickest +on the bottom. Well actually, Blackfire "prunes" or "removes" function calls that +took *very* little time... so you won't see *everything* here. + +## Viewing by Different Dimensions + +The functions are ordered by "time" because we're viewing the call graph in the +time "dimension". You can also look at all of this information ordered by several +other dimensions - like which functions took the most *memory*. It's kind of like +the process manager on your computer: you can see which applications are currently +taking up the most CPU, the most memory, reading the most info from your disk or +even using the most network. But more on these dimensions later. + +## Wall Time + +In the profiling world, time is called "wall time". But, it's *nothing* fancy: +wall time is the difference between the time at which a function was entered and +the time at which the function was left. So... wall time is a fancy word for... um... +time: the amount of "time" a function took to run. + +## Inclusive vs Exclusive + +So... we just find the function with the highest wall time and optimize it, right? +Well... what if a function is taking a really long time... but actually, 99% of +that time is due to a function that *it* calls. In that case, the *other* function +is probably the problem. + +To help sort this all out, wall time is divided into two parts: *exclusive* time +and *inclusive* time. If you hover over the red graph, you'll see this: exclusive +time 37.9 milliseconds, inclusive time 101 milliseconds. + +Inclusive time is the *full* time it took for the function to execute. Exclusive +time is more interesting: it's the time a function took to execute *excluding* +the time spent inside *other* functions it called: it's a *pure* measurement +of the time that the code inside *this* function took. + +Right now, we're *actually* ordering this list by *exclusive* time, because that +usually shows you the biggest problems. You can also order by inclusive time... +which is probably not very useful: the top item is where our script starts executing, +the second is the next function call, and so on. Go back to exclusive. + +## Navigating What Calls What + +So apparently, the biggest problem, according to exclusive time, is this +`UnitOfWork::createEntity` function... whatever that is. If you use Doctrine, +you might know what this is - but let's pretend we have *no* idea. + +Before we dive further into the root cause behind this slow function, the +*other* way to order the calls is by the *number* of times each is called. +Wow! Apparently the function that's called the most times - over 6 *thousand* times - +is `ReflectionProperty::setValue`. Huh. I wonder who calls that? + +## Deeper Function Details + +Click to expand that function. I love this! Even though we're viewing the call +graph in the "time" dimension, this gives us *all* the info about this function: +the wall time, I/O wait time, CPU time, memory footprint and network. + +## Wall Time = I/O Time + CPU Time + +This isn't a particularly *time* consuming function - its wall time is 9.13 +milliseconds. Wall time itself is broken down into two pieces, and this is important: +wall time = I/O time + CPU time. There is nothing else: either a function is using +CPU or it's doing some I/O operation, like talking to the filesystem or making network +calls. In this case, the 9.13 milliseconds wall time is *all* CPU time. + +## Finding Callers + +Okay, but who actually *calls* this function so many times? Above this, see +those 3 down arrow buttons? These represent the *three* other functions that +call this one - the size is relative to how many *times* each calls this. Click +the first one. Ah ha! It's `UnitOfWork::createEntity`! That's the function with +the highest exclusive time - it calls this function 4,959 times. Wow. So... it's +definitely a problem. + +If you click the other two arrows, you can see the other two callers: one calls this +984 times and the other 216 times. Both are from Doctrine. + +## Viewing Callees + +Close all of this up and go back to ordering by the highest exclusive time. Open +up `UnitOfWork::createEntity()`. As I mentioned, even though we're currently +viewing the call graph in the "time" dimension, we can see *all* this function's +dimensions right here. + +Hover over the time graph: even though the exclusive time is significant - 37.9 +milliseconds - most of this function's time is still *inclusive*: it's taken up +by other functions that *it* calls. That helps give us a hint as to if the problem +is *inside* this function... or inside something it calls. + +And actually, *every* dimension has inclusive and exclusive measurements: like +CPU time and even memory. If any of these had a high *inclusive* value - meaning +some function *it* calls is *really* taking up that resource - you can see what +functions it calls by clicking one of the arrow buttons *below* this. + +What I *really* want to know though is... what's happening in our code to cause +*this* function - `UnitOfWork::createEntity()` - be called so many times? Click +the biggest arrow above. Ah: `ObjectHydrator::getEntity()` is the main culprit. + +But... honestly... I don't know what that function is either: this is still +*way* too low-level in Doctrine - I have no idea what's really going on. +So next, let's use the call graph - the pretty diagram on the right - to get a +full picture of what's happening going on... and how to fix it. diff --git a/sfcasts/install.md b/sfcasts/install.md new file mode 100644 index 0000000..c832541 --- /dev/null +++ b/sfcasts/install.md @@ -0,0 +1,137 @@ +# Installing the Agent, Probe & Chrome Extension + +So... let's get these pieces installed! Back on the install page, the setup details +will vary based on your operating system. Fortunately, Blackfire has details for +pretty much all situations. I'm on a Mac and will use Homebrew to get everything +working. + +I'll copy the `brew tap` command, move to my terminal, open a new tab and paste: + +```terminal-silent +brew tap blackfireio/homebrew-blackfire +``` + +## Installing the Agent + +That gives me access to the Blackfire packages. *Now*, install the *agent* - that's +the "daemon" that runs in the background - with: + +```terminal +brew install blackfire-agent +``` + +Perfect! It says I need to "register" my agent. And... the browser instructions +confirm that! I'll copy that command, clear the screen and paste: + +```terminal-silent +sudo blackfire-agent --register +``` + +This is going to ask us for our "Server Id" and "Server Token". These are... basically +an internal "username and password" that the agent will use to tell the Blackfire +servers which *account* the profiles should be attached to. Copy the Server Id, +paste, copy the Server Token, paste and... we're good! + +Finally, remember how the "agent" is a service that runs in the background? +We just *installed* the agent, but it's not running yet. Back in the docs, the +next two commands set up the agent as a "service" in Brew, so that it will *always* +be running. Copy the first, paste. + +```terminal-silent +ln -sfv /usr/local/opt/blackfire-agent/*.plist ~/Library/LaunchAgents/ +``` + +Then spin back over again, copy the `launchctl load` command... and paste that. + +```terminal-silent +launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.blackfire-agent.plist +``` + +Cool! If everything worked, the Blackfire agent is now running in the background. +You wont really ever see it or care that it's there... but it is... waiting for +data. + +## Installing the Probe + +Back on the install docs, the next piece we need is the PHP extension - the probe. +Skip this CLI tool for now - we won't need it until later. + +To install the PHP extension, we'll once again use `brew`. But... hopefully you're +*not* still using PHP 5.6. Let me head over to my terminal and see what version +I'm running: + +```terminal +php --version +``` + +7.3.6. Brilliant! So I'll run: + +```terminal +brew install blackfire-php73 +``` + +Notice that the extension doesn't need *any* authentication info - like a server +Id or token. It's beautifully dumb: its job is to profile data, send it to the +agent, and let *it* worry about authentication with the Blackfire servers. + +We *do*, however, as it says, need to restart our web server. For us, that means +going to the other terminal tab, hitting Control + C, and then running + +```terminal +symfony serve +``` + +Is the Blackfire extension working? I don't know! Because we're using Symfony, an +easy way to check is to hover over the web debug toolbar and click the +"View phpinfo()" link. Let's see... yep! The Blackfire PHP extension is here. + +***TIP +If you have XDebug installed, disable it for the best results. +*** + +## Installing the Browser Extension + +At this point, our *server* is set up and ready to profile! Victory! The only +thing we need *now* is a way to tell the probe when to *activate*. That's the +job of the browser extension. + +Go almost *all* the way back to the top of the install page where they talk about +the different pieces. I'm using Chrome, so I'll click the +[Google Chrome](https://blackfire.io/docs/integrations/browsers/chrome) +extension link. I don't have it installed yet, so let's fix that: Add to Chrome. + +There it is! If you refresh the docs... yep! It sees the extension. + +## Profiling our First Page + +Hey! We're ready to profile! Ahhhh! Where should we start? Let's... just click +to view details about any Big Foot sighting. All of this data comes from some data +fixtures that we used to pre-populate the database while setting up the project. +It uses a bunch of random data up here... and each sighting has a bunch of random +comments. + +When we loaded this page a second ago, the PHP extension - the probe - did nothing. +To activate it, click the browser extension. + +Moment of truth! When we click profile, the plugin will send a request to this +page with a special header that tells the probe to activate and start profiling. +Click "Profile"! + +There it goes! It goes from 0 to 100% as it actually makes *10* requests and +averages their data. We can also give this "profile" a name to keep our account +organized: I'll say `[Recording] Show page initial` and hit enter. + +## Troubleshooting Failure + +If you got to 100%, congrats! If you got an error... wah wah. This is *the* most +common place for something to go wrong... and the error will almost always be +the same: Probe not found. This might mean that you forgot to install the PHP +extension, or that the PHP extension was installed on a different PHP binary... +or that the agent isn't running... or that the agent *is* running but you +misconfigured the server id and token. They have great docs to help with this. + +But we had success! Click the "View Call Graph" button to go to a URL on their +site. Hello beautiful Blackfire profile. Wow. + +Next, let's start diving into this *mountain* of information and see how we can use +it to find hidden sasquatch... I mean, hidden performance bugs. diff --git a/sfcasts/instantiation.md b/sfcasts/instantiation.md new file mode 100644 index 0000000..b67293d --- /dev/null +++ b/sfcasts/instantiation.md @@ -0,0 +1,115 @@ +# Spotting Heavy Object Instantiation + +I want to show a... more *subtle* performance problem. To even *see* it, we need +to go back to the `prod` environment: + +[[[ code('698fc11371') ]]] + +Make sure to run `cache:clear`: + +```terminal-silent +php bin/console cache:clear +``` + +`cache:warmup`: + +```terminal-silent +php bin/console cache:warmup +``` + +And also: + +```terminal +composer dump-autoload --optimize +``` + +Let's create a fresh profile of the homepage. I'll call this one: +`[Recording] Homepage prod`. Click to view the timeline: http://bit.ly/sf-bf-instantiation + +Overall, this request is pretty fast. Click into the "Memory" dimension. The +biggest call is `Composer\Autoload\includeFile`: that's *literally* Composer +including files that we need... not a lot of memory optimization we can do about +that. + +But, if we look closer, the memory dimension reveals something else. See this +"Container" thing - the 2nd item on the function list? This is related to Symfony's +container, which is responsible for *instantiating* all of our objects. This +specific function is interesting: it's highlighting a *section* of a file that lives +in our cache directory. If you looked in that file, you would see that this part +of the code is responsible for *including* some of the main files that our app +needs. It's basically another version of the top node: it's code that includes +files for classes we're using. + +## Seeing Object Instantiation + +Ok, so the first few aren't really *that* interesting. Things get much more +intriguing down on the 4th function call: some +`Container{BlahBlah}/getDoctrine_Orm_DefaultEntityManagerService.php` call. +What is this? Well, the details of how this is organized are specific to Symfony: +but this is evidence of something that *every* app does: this is showing the +amount of resources used to *instantiate* Doctrine's EntityManager object. +I know, we don't often think about how much time or how much memory it takes to +*instantiate* an object, but it *can* sometimes be a problem. The next function +call is for the instantiation of Doctrine's Connection service. + +Go down a little bit... I'm looking for something specific... here it is: +`getLoginFormAuthenticatorService()`. This is responsible for instantiating a +`LoginFormAuthenticator` object in our app. It's not a particularly problematic +function though: it's 10th on the list... only takes 2.56 milliseconds and uses about +500 kilobytes. + +## Checking the Instantiation of LoginFormAuthenticator + +Let's check out the class: `src/Security/LoginFormAuthenticator.php`: + +[[[ code('fd3bf2ea51') ]]] + +As its name suggests, this is responsible for authenticating the user when they +submit the login form. + +But, there's something special about this class. Due to the way the Symfony +security system works, Symfony instantiates this object on *every* request. It does +that so it can then call `supports()` to figure out if this service should be +"activated" on this request or not: + +[[[ code('412f2b425a') ]]] + +For this class, it *only* needs to its work when the URL is `/login` and this +is a `POST` request. In *every* other situation, `supports()` returns false +and *no* other methods are called on this class. + +So let's think about this. Instantiating this class takes about 3 milliseconds +and 500 kilobytes... which is not a *ton*... but since *all* it needs to do for +*most* requests is check the current URL... then exit... that *is* kind of heavy. + +## Why Instantiation is Slow? + +The question is: *why* does it take so many resources to instantiate? Well, 500 +kilobytes is not a *ton*, but this *is* - according to Blackfire - one of the +*most* expensive objects that is created on this request. Why? + +Check out the constructor: + +[[[ code('2e55155e0b') ]]] + +In order to instantiate this class, Symfony needs to make sure the `EntityManager` +is instantiated... and the `UrlGenerator`.. and the `CsrfTokenManager`... and the +`UserPasswordEncoder`. If any of *these* services have their *own* dependencies, +even *more* objects may need to be instantiated. In rare situations, creating +a service can be a *huge* performance problem. + +In the case of the `EntityManager` and the `UrlGenerator`... those are pretty +core objects that would *probably* be needed and thus instantiated by *something* +on this request anyways. But `CsrfTokenManager` and `UserPasswordEncoder` are +*not* normally needed. In other words, we're forcing Symfony to instantiate both +of those services on *every* request... even though we *only* need them when the +user is submitting the login form. + +This is a *classic* situation where you have an object that is instantiated on +every request... but only needs to do *real* work in rare cases. Certain event +subscribers - like our `AgreeToTermsSubscriber` - Symfony security voters & Twig +extensions are other examples from Symfony. These services might be quick to +instantiate... so no problem! But they *also* might be expensive. + +So... how *could* we make it quicker to instantiate `LoginFormAuthenticator`? +In Symfony, with a service subscriber. diff --git a/sfcasts/intro.md b/sfcasts/intro.md new file mode 100644 index 0000000..1666032 --- /dev/null +++ b/sfcasts/intro.md @@ -0,0 +1,93 @@ +# Performance, Profilers and APMs + +Hey friends! Welcome to the *fastest*, most *performant* SymfonyCasts +tutorial of all time, on Blackfire. The end. What? We should say a bit more? + +Uh, Blackfire is *all* about having fun while you +discover ways to make your site *absurdly* fast. We're going to see big graphs, +numbers, statistics, animated gifs, and watch all those numbers decrease as we hunt +down and eliminate performance bottlenecks. This stuff is just *fun*. And who +doesn't want a faster site? + +But... ok... *just* "being fun" probably isn't a good enough reason to use Blackfire. +If you're trying to "sell" using a tool to your team... or management, the *real* +reason is profit. Performance is money. Heck, Google even has a page that will +[measure the speed of your site](https://www.thinkwithgoogle.com/feature/testmysite) +and tell you how much *revenue* you can gain by de creasing the rendering time of +your site by various amounts. + +On the flip side, I'm sure you've heard the famous saying: + +> Premature optimization is the root of all evil + +I thought it was Nickelback. If that's true... doesn't a having a cool profiling +tool like Blackfire make you think *more* about prematurely +optimizing? Actually, it's the *opposite*: it let's us focus on creating +features and *then* noticing performance problems *if* there are any. + +## Performance: Server + Network + Rendering + +By the way, your site's performance is really three things put together. First, +the time it takes your server to build the page. Second, the time it +takes to transmit that data over the network. And third, the time it takes for +the browser to display stuff - the frontend. You should focus on *all* of these, +but the *main* parts are the server and frontend. Your browser has tools to +understand and optimize your frontend. Blackfire helps optimize your backend. + +## Application Performance Monitoring (APM) Versus Blackfire + +But it's not the *only* way to monitor performance on your server. The most +well-known way is by using an "application performance monitoring tool" - or APM... +which is an acronym I had to look up about 10 times before I could remember what it +meant! An APM is something that runs on your servers *all* the time, collecting +information about load times, slow queries, slow functions, errors and more. +The most famous one is probably NewRelic, though Blackfire is planning to release +their own sometime soon. + +The *great* thing about an APM is that you can see data from *every* request on +your production servers. The *bad* part is that, because an APM is always running, +it needs to collect data *without* slowing down the page. If it tries to +collect too much, *it* would become the performance bottleneck! + +Blackfire is a *profiler*. The *big* difference is that, instead of running on +*every* single request that our users make... and needing to stay very lightweight, +Blackfire only profiles a page when you *tell* it to. It then makes its *own* +request to the page and collects an *incredible* amount of *extremely* detailed +information. This process *totally* slows down that page load... which is fine, +because there's not a real user waiting for it to return. + +The *point* is: use an APM *and* a profiler. The APM will give you a constant +stream of information from production. The profiler will give you the *deep* +information you need when debugging performance on specific pages. + +## Project Setup + +Ok, enough chat! Let's do this! To remove any bottlenecks and maximize your +learning performance, you should *totally* code along with me. Download the course +code from this page. When you unzip it, you'll find a `start/` directory with the +same code that you see here. Follow the `README.md` file for all the +setup details. This is a Symfony project - but that won't matter much: we'll +mostly focus on understanding and getting the most out of Blackfire. + +The last setup step in the README will be to open a terminal, move into the project, +use the [Symfony binary](https://symfony.com/download) to start a local web +server by typing: + +```terminal +symfony serve +``` + +Ok, let's see the site! Find your browser and head to `https://localhost:8000`. +Now you understand how important this project is. The world has been +looking for Big Foot, or "Sasquatch", for years. Thanks to the Big-Foot fanatic +community on our site - "Sasquatch Sightings" - we're closer than ever. In our +case, better performance doesn't mean more profit, it means, more big foot. + +Do... I know where the performance problems are? Nope. No idea. Honestly, I was +too focused on getting this site to production to obsess over performance. And... +I feel great about that! We'll use Blackfire to find the bottlenecks - if any - +and Sasquash them! + +Next, let's get Blackfire installed on my local machine and start profiling this +local website. And yes, you *can* use Blackfire on production - which is *awesome* - +and something we'll do later in the tutorial. diff --git a/sfcasts/manual-instrumentation.md b/sfcasts/manual-instrumentation.md new file mode 100644 index 0000000..a53ff25 --- /dev/null +++ b/sfcasts/manual-instrumentation.md @@ -0,0 +1,164 @@ +# Manually Profile (Instrument) Part of your Code + +Profiling a page looks like this. + +## Profiling: What happens Behind the Scenes + +First, something tells the Blackfire PHP extension - the "Probe": + +> Hey! Start profiling! + +Which basically means that it starts collecting *tons* of data. The process +of collecting data is called *instrumentation*... because when a concept is +*too* simple, sometimes we tech people like to invent confusing words. +*Instrumentation* means that the PHP extension is collecting data. + +The second step is that - eventually - something tells the PHP extension to *stop* +"instrumentation" and to send the data. The collection of data is known as a +"profile". The PHP extension sends the profile to the agent, which aggregates it, +prune some stuff and ultimately sends it to the Blackfire server. + +So: what is the "thing" that tells the PHP extension to activate? We know that +the PHP extension doesn't profile *every* request... so what is it that says: + +> Hey PHP extension "probe" thing: start profiling! + +The answer - *so* far - is: the browser extension: it sends special information +that tells the probe to do its thing. Or, if you use the `blackfire` command line +utility, which we did earlier to profile a command, then *it* is what tells the +PHP extension to activate. + +In either situation, the extension is activated *before* even the *first* line +of code is executed. That means that *every* single line of PHP code is +"instrumented": our final profile contains *everything*. This is called +auto-instrumentation: instrumentation starts automatically. + +This naturally leads to three interesting questions. + +First, who *is* baby Yoda? I mean, is he... like, related to Yoda? Or just the +same species? + +The second question is: could we trigger, or *create* a Blackfire profile in a +*different* way? Could we, for example, dynamically tell the PHP extension to create +a profile from *inside* our code under some specific condition? + +And third, *regardless* of who *triggers* the profile, could we "zoom in" and +only collect profiling data for *part* of our code? Like, could we create a profile +that only collects data about the code from our controller instead of the +entire request? + +Let's actually start with that last question: profiling a *specific* part of +our code, instead of the whole thing. To be *fully* honest, I don't know if this +part has a *ton* of practical use-cases, but it *will* give you an even better +idea of how Blackfire works behind the scenes. + +## Installing the Blackfire SDK + +To help with this crazy experiment, we're going to install Blackfire's PHP SDK. +Find your terminal, dial up your modem to the Internet, and run: + +```terminal +composer require blackfire/php-sdk +``` + +This is a normal PHP library that helps interact directly with Blackfire from +*inside* your code. You'll see how. + +When it finishes, move over and open `src/Controller/MainController.php`: + +[[[ code('5efb26151b') ]]] + +Ok: this is the controller for our homepage. Let's pretend that when we profile +this page, we don't want to collect data about *all* of our code. Nope, we want +to, sort of, "zoom in" and see *only* what's happening inside the controller. + +## Manually Instrumenting Code + +We can do that by saying `$probe = \BlackfireProbe::getMainInstance()`: + +[[[ code('a9a0b1e8d5') ]]] + +Remember: the PHP extension is called the "probe"... that's important if you want +this to make sense. Then call `$probe->enable()`: + +[[[ code('eebd633917') ]]] + +At the bottom, I'll set the rendered template to a `$response` variable, add +`$probe->disable()` and finish with `return $response`: + +[[[ code('27613dd79d') ]]] + +Okay, so... what the heck does this do? The first thing I want you to notice is +that if I refresh the homepage a bunch of times... and then go to +https://blackfire.io, I do *not* have any new profiles. Adding this code does +not "trigger" a new profile to be created: it does *not* tell the PHP extension - +the "probe" - that it should to do its work. + +Instead, *if* a profile is currently being created, this tells the probe *when* +to start collecting data. Hmm, this isn't going to *quite* make sense until we +see it in action. Trigger a new profile on the homepage. I'll call this one: +`[Recording] Only instrument some code`. + +Click to view the call graph: https://bit.ly/sf-bf-partial-profile. + +*Fascinating*. This contains *less* information than normal. It has a few things +on top - `main()` and `handleRaw()`... but basically it jumps straight to the +`homepage()` method. + +## How Disabling Auto-Instrumentation Works + +What's happening here is that the *only* code that the probe "instrumented", the +*only* code that it collected information on, is the code between the `enable()` +and `disable()` calls: + +[[[ code('df396b7518') ]]] + +This... completely confused me the first time I saw it. What *really* happens is +this: as soon as we use the browser extension to tell the probe to do its job, +the PHP extension starts instrumenting - so, collection data - *immediately*. +Initially, it *is* collecting data about *every* line of PHP code. + +But as *soon* as it sees `$probe->enable()`, it basically *forgets* about all +the data collected so far. The `$probe->enable()` call says: + +> Hey! Start instrumenting *here*. If you've already collected some data before +> thanks to auto-instrumentation, get rid of it. + +This effectively *disables* auto-instrumentation: we're now *controlling* which +code is instrumented instead of it happening automatically. Once the code hits +`$probe->disable()` instrumentation stops. + +You can actually use `$probe->enable()` and `$probe->disable()` multiple times +in your code if you want to profile different pieces: `$probe->enable()` only +forgets data it's already collected the *first* time you call it. + +Oh, and you can *also* optionally call `$probe->close()` - you'll see this in +their documentation: + +[[[ code('2f98e2bc2d') ]]] + +That tells the PHP extension that you're *definitely* done +profiling and it can send the data to the agent. But, it's not *strictly* required, +because it'll be sent automatically when the script ends anyways. + +So... this feature is *maybe* useful... but it's *definitely* a nice intro into +taking more control of the profiling process. + +## We haven't used the SDK Yet + +And.. fun fact! We installed the `blackfire/php-sdk` library... but we haven't +*actually* used it yet! This `\BlackfireProbe` class is *not* from the `php-sdk` +library: it's from the Blackfire PHP *extension*. As long as you have the +extension installed, that class will exist. We're interacting *directly* with +the extension. + +So... why did we install the SDK if we didn't need it? Because... it gave us +auto-complete on that class. And you all know that I freakin' *love* auto-complete. + +The SDK has a, sort of, "stub" version of this class. This is *not* the code that +was *actually* executed when we called those methods... but having this at least +shows us what methods and arguments exist. + +Next, let's actually *use* the PHP SDK to do something a bit more interesting. +I want to *create* a profile automatically in my code *without* needing to use +the browser extension. This *does* have real-world use-cases. diff --git a/sfcasts/metadata.yml b/sfcasts/metadata.yml index 4492d9b..f8d4608 100644 --- a/sfcasts/metadata.yml +++ b/sfcasts/metadata.yml @@ -1 +1,38 @@ -chapters:[] +chapters: + - intro + - the-pieces + - install + - function-list + - call-graph + - comparisons + - recommendations + - property-caching + - cache-compare + - n-plus-one + - n-plus-one-joins + - profile-all + - blackfire-cli + - command-line-scripts + - timeline + - timeline-surprise + - instantiation + - service-subscriber + - manual-instrumentation + - auto-profile + - probe-create-subscriber + - performance-tests + - metrics + - assertions + - blackfire-player + - blackfire-player-expects + - deploy + - sf-cloud-database + - environments + - env-profile + - build-basics + - build-scenarios + - custom-metrics + - comparison-build-tests + - cloud-environments + - staging-environments + - environment-vars diff --git a/sfcasts/metrics.md b/sfcasts/metrics.md new file mode 100644 index 0000000..34a1d47 --- /dev/null +++ b/sfcasts/metrics.md @@ -0,0 +1,203 @@ +# All about Metrics + +Where did this metrics string come from - this `metrics.http.requests.count`? + +[[[ code('4e37b19b3c') ]]] + +There are two things I want to say about this. First, Blackfire stores *tons* of +raw data about your profile in little "categories" called metrics. More on that +soon. And second, inside the `assert()` call, you're using a special +"expression" language that's similar to JavaScript. It's technically Symfony's +ExpressionLanguage if you want to read more. Behind-the-scenes, `metrics` +is probably some object... and we're referencing an `http` property, then +a `requests`... property then a `count` property & then we're comparing that to 1. + +## What Metrics are Available + +Ok, cool. So... how the heck did I know to use this *exact* string to get the HTTP +call count? This goes back to the Blackfire timeline. On the profile, click the +timeline link. + +When we talked about the timeline earlier, we talked about how, on the left side, +there are these "timeline" metrics. At *that* point, these were just a nice way +to add color to different sections of the timeline. + +But *now* we understand that there is a *lot* more power behind this info: this +shows us *all* the pieces of data we can use in our tests... and in other places +that we'll talk about soon. + +For example, there's a metric called `symfony.events.count` which equals seven. +You could use that in a metric if, for some reason, you wanted to assert that a +certain number of events were dispatched. If I needed to do an assertion about +the number of HTTP requests, I would probably search the metrics for http. +Apparently there are two... and if you looked closer, you'd find that `http.requests` +is *perfect*. Most of these metrics have data about multiple *dimensions*: we +can say `http.requests.count` to get the actual number or `http.requests.memory` +to get how much memory they used. + +In the test system, we start with `metrics.` then use *anything* we find here. + +## Fixing the Performance Bug + +We now have a performance bug in our application that we've *proven* with +a test. And at this point, the actual way we *fix* that bug is not as important: +all we care about is that we can change some code and get this test to pass. + +The logic for the API calls lives in `src/GitHub/GitHubApiHelper.php`: it has +two public function and each makes one API request: + +[[[ code('cf6b070569') ]]] + +How can we make this page only make *1* HTTP request? Well, if you looked closely.. +Ah! Too close! Ahh. You'd find that you can get all the information you need +by *only* making this *second* HTTP request. The details aren't important - so let's +just jump in. + +Add a new property called `$githubOrganizations` set to an empty array: + +[[[ code('1ccbfdf344') ]]] + +As we loop over the repositories for a specific organization, we will *store* +that organization's info. Add a new variable called `$publicRepoCount` set to 0: + +[[[ code('bfd1144726') ]]] + +the number of public repositories an organization has is one of the pieces of data +we need. + +Then, inside the `foreach`: if `$repoData['private'] === false` - that's one of +the keys on `$repoData` - say `++$publicRepoCount`: + +[[[ code('885bc9356f') ]]] + +So, as we're looping over the repositories, we're counting how many are public. + +Finally, at the bottom, if *not* `isset($this->githubOrganizations[$organization])`, +then `$this->githubOrganizations[$organization] = new GitHubOrganization()`: + +[[[ code('82bedb41de') ]]] + +This needs two arguments. The first is the organization name. We can probably use +the `$organization` argument... or you can use `$data[0]` - to get the first +repository - then `['owner']['login']`. For the second argument, pass +`$publicRepoCount`: + +[[[ code('1e42caf771') ]]] + +Now, *each* time we call this method, we *capture* the organization's information +and store it on this property. So if we call this method *first* and *then* the +other method... we could *cheat* and return the `GitHubOrganization` object that's +stored on the property. It's property caching! + +Check it out: if `isset($this->githubOrganizations[$organization])` then return +that immediately without doing any work: + +[[[ code('8cebde886e') ]]] + +So... *are* we calling these two methods in the "correct" order to get this to +work? Check out the controller: + +[[[ code('7e8d416ac1') ]]] + +Nope! Swap these two lines so the *first* call will set up the caching +for the second: + +[[[ code('805d20a615') ]]] + +Phew! Let's see if that helps. It was a complicated fix... but thanks +to our test, we will know for *sure* if it worked. Go! + +```terminal-silent +php bin/phpunit tests/Controller/MainControlerTest.php +``` + +They pass! This *proves* that we reduced the HTTP calls from two to one. + +## Typos in Metrics + +What I *love* about the metrics system is that there are *many* to choose from. +What I *don't* love is that you need to manually look up everything that's available. +*Fortunately*, if you make a typo - the error is great. Change `count` to `vount`: + +[[[ code('0efb861d87') ]]] + +And re-run the test: + +```terminal-silent +php bin/phpunit tests/Controller/MainControlerTest.php +``` + +> An error occurred when profiling the test + +And when we follow the profile link... check out that error! + +> The following assertions are not valid... Property "vount" does not exist, +> available ones are: + +... and it lists all the properties. That's *super* friendly. Fix the typo: + +[[[ code('6b5291e239') ]]] + +## Organizing Blackfire Assertions into Separate Test Cases + +The *one* downside to adding Blackfire assertions in your tests is that they *do* +slow things down because instrumentation happens and we need to wait for Blackfire +to create the profile. + +Because of that, as a best practice, we usually like to isolate our performance +tests from our normal tests. Check it out: copy the test method name, paste it +below, and call it `testGetGitHubOrganizationBlackfireHttpRequests()`: + +[[[ code('f4afb170ce') ]]] + +And... copy the contents of the original method and paste here. Now... we only need +to create the `$client`, create `$blackfireConfig` and, inside `assertBlackfire()`, +*just* make the request: + +[[[ code('e554b46137') ]]] + +Back in the original method, we can simplify... in fact we can go *all* the way +back to the way it was before: create the client, make the request, assert something: + +[[[ code('05f8976359') ]]] + +*Why* is this useful? Because *now* we can *skip* the Blackfire tests if we're +just trying to get something to work. How? Above the performance test, add +`@group blackfire`: + +[[[ code('27cbef04bf') ]]] + +Thanks to that, we can add `--exclude-group=blackfire` to *avoid* the Blackfire +tests: + +```terminal-silent +php bin/phpunit tests/Controller/MainControlerTest.php --exclude-group=blackfire +``` + +Yep! Just one test, two assertions. Another nice detail is to add +`@requires extension blackfire`: + +[[[ code('7069bb26db') ]]] + +Now, if someone is *missing* the Blackfire extension, instead of the tests +exploding, they'll be marked as skipped. + +## Don't do Time-Based Assertions + +The *last* thing I want to mention about assertions is this: please, please *please* +avoid time-based assertions. They're the *easiest* to create - I know. It's +*super* tempting to want to create an assertion that the request should take +less than 500 milliseconds. If you *do* this, you will hate your tests. + +Why? Because there's *way* too much variability in time: the request might run +fast enough on one machine, but *not* fast enough on another. Or your server +*might* just have a bad day... and suddenly your tests are failing. Relying on +time makes your tests fragile. + +Next, we're going to talk *more* about metrics and assertions. We know that we +can add assertions to profiles that are created inside our tests. + +But we an *also* add *global* assertions: tests that run *any* time you create +a profile for *any* page! If you want to make sure that a specific page - or +*any* page - doesn't make more than, I don't know, 10 database queries, you can +add an "assertion" for that and see a *big* failure if you break the rules. diff --git a/sfcasts/n-plus-one-joins.md b/sfcasts/n-plus-one-joins.md new file mode 100644 index 0000000..93c51fb --- /dev/null +++ b/sfcasts/n-plus-one-joins.md @@ -0,0 +1,115 @@ +# Fixing N+1 With a Join? + +We made a *huge* leap forward by telling Doctrine to make `COUNT` queries to +count the comments for each `BigFootSighting`... instead of querying for *all* the +comments *just* to count them. That's a big win. + +Could we go further... and make a smarter query that can grab all this data +at once? That *is* the classic solution to the N+1 problem: need the data for +some Bigfoot sightings *and* their comments? Add a JOIN and get all the data at +once! Let's give that a try! + +## Adding he JOIN + +The controller for this page lives at `src/Controller/MainController.php` - it's +the `homepage()` method: + +[[[ code('8d5b5c8501') ]]] + +To help make the query, this uses a function in +`src/Repository/BigFootSightingRepository.php` - this `findLatestQueryBuilder()`: + +[[[ code('4fffc80064') ]]] + +*This* method ... if you did some digging ... creates the query that returns +these results. + +And... it's fairly simple: it grabs all the records from the `big_foot_sighting` +table, orders them by `createdAt` and sets a max result - a `LIMIT`. + +To *also* get the comment data, add `leftJoin()` on `big_foot_sighting.comments` +and alias that joined table as `comments`. Then use `addSelect('comments')` to +not only *join*, but also *select* all the fields from `comment`: + +[[[ code('c3d7c0c482') ]]] + +Let's... see what happens! To be safe, clear the cache: + +```terminal-silent +php bin/console cache:clear +``` + +And warm it up: + +```terminal-silent +php bin/console cache:warmup +``` + +Now, move over, refresh and profile! I'll call this one: `[Recording] Homepage with join`: +https://bit.ly/sf-bf-join. + +Go check it out! Woh! This... looks weird... it looks *worse*! Let's do a +compare from the `EXTRA_LAZY` profile to the new one: https://bit.ly/sf-bf-join-compare. + +Wow... this is much, much worse: CPU is way up, I/O... it's up in every category, +especially network: the amount of data that went over the network. We *did* make +less queries - victory! - but they took 8 milliseconds longer. We're now returning +*way* more data than before. + +So this was a *bad* change. It seems obvious now - but in a different situation +where you might be doing different things with the data, this *same* solution +could have been the right one! Let's remove the join and rely on the `EXTRA_LAZY` +solution. + +## A Smarter Join? + +Yes, this *will* mean that we will once again have 27 queries. If you don't like +that, there *is* another solution: you could make the `JOIN` query smarter - it +would look like this: + +```php +// src/Repository/BigFootSightingRepository.php +public function findLatestQueryBuilder(int $maxResults): QueryBuilder +{ + return $this->createQueryBuilder('big_foot_sighting') + ->leftJoin('big_foot_sighting.comments', 'comments') + ->groupBy('big_foot_sighting.id') + ->addSelect('COUNT(comments.id) as comment_count') + ->setMaxResults($maxResults) + ->orderBy('big_foot_sighting.createdAt', 'DESC'); +} +``` + +The key is that instead of selecting *all* the comment data... which we don't need... +this selects *only* the count. It gets the *exact* data we need, in one query. +From a performance standpoint, it's probably the perfect solution. + +But... it has a downside: complexity. Instead of returning an array of +`BigFootSighting` objects, this will return an array of... arrays... where each +has a `0` key that is the `BigFootSighting` object and a `comment_count` key with the count. +It's just... a bit weird to deal with. For example, the template would need to +be updated to take this into account: + +```jinja +{% for sightingData in sightings %} + {% set sighting = sightingData.0 %} + {% set commentCount = sightingData.comment_count %} + + {# ... #} + {{ sighting.title }} + + {{ commentCount }} + {# ... #} +{% endfor %} +``` + +*And*... because of the pagination that this app is using... the new query would +actually produce an error. So let's keep things how they are now. If the extra +queries ever become a *real* problem on production, *then* we can think about spending +time improving this. Sometimes profiling is about knowing what *not* to fix... +because it may not be worth it. + +Next, if you were surprised that we didn't see any evidence of the network request +that the homepage is making to render the SymfonyCasts repository info from +GitHub, that's because the homepage is more complex than it might seem. Let's +use a cool "Profile all" feature to see *all* requests that the homepage makes. diff --git a/sfcasts/n-plus-one.md b/sfcasts/n-plus-one.md new file mode 100644 index 0000000..4ea34cf --- /dev/null +++ b/sfcasts/n-plus-one.md @@ -0,0 +1,104 @@ +# The N+1 Problem & EXTRA_LAZY + +At this point, I'm pretty happy with the show page that we've been profiling. So +let's look at something different: let's profile the homepage at +https://localhost:8000/. + +Ok, this page has a list of all of the sightings... and on the right, that shows +some SymfonyCasts repository info from GitHub. Let's refresh... though... that's +not really needed - and profile! I'll call this one: `[Recording] Original homepage` - +https://bit.ly/sf-bf-homepage-original. + +Ok! 165 milliseconds! Let's view the call graph. Well... this looks familiar! +We have the *same* number 1 exclusive-time function as before: +`UnitOfWork::createEntity()`. In *that* situation, it meant that we were querying +for too many items and so Doctrine was *hydrating* too many objects. Is it the +same problem now? And if so, why? Can we optimize it? + +Time to put on our profiling detective hats. Let's follow the hot path! We enter +`MainController::homepage()` and render a template... so the problem is coming +from our *template*. Interesting. Next `_sightings.html.twig` is rendered... and +then something called `twig_length_filter` executes `loadOneToManyCollection()`, which +is from Doctrine. Let's do some digging in that template: +`templates/main/_sightings.html.twig`. + +We saw that it was referencing something called `twig_length_filter`. Search the +template for `length`. Ah: `sighting.comments|length`: + +[[[ code('2ef9d351d0') ]]] + +## Finding the N+1 Problem + +Look back on the site: one of the things it does is prints the number of +*comments* for each article. The `length` filter counts how many items are in +`sighting.comments`, which is a database relationship from the `big_foot_sighting` +table to the `comment` table. + +If you're not familiar with Doctrine, when you call `sighting.comments`, at that +moment, Doctrine queries for *all* of the comments for that specific `BigFootSighting` +record. I'll open up `src/Entity/BigFootSighting.php`. Yep, we're accessing the +`comments` property, which is a `OneToMany` relationship to `Comment`: + +[[[ code('a61746fb8d') ]]] + +The point is: for *each* `BigFootSighting` that we are rendering, Doctrine is making +an *extra* query to fetch *all* the comments for that sighting. This is basically +the classic N+1 problem. If we want to print 25 `BigFootSighting` rows, in addition +to the 1 query to fetch the 25 rows, the system will *also* make 25 *additional* +queries to fetch the *comments* for each sighting. That's 25 + 1 queries. + +You can see this in the SQL queries in Blackfire: we have one query from +`big_foot_sighting` - the query above is related to the pagination logic - then +*25* queries from the `comment` table. + +## Counting with `fetch="EXTRA_LAZY"` + +Okay, we have identified the problem: we are not only making a lot of queries... +but those queries are *also* fetching *all* the comment data... just to count them. +Silliness! + +One *simple* solution might be... just to tell Doctrine to make a COUNT query +instead of fetching all the data. We would *still* have 25 extra queries... but +they would be much faster. + +In Doctrine, we can do this really easily. If you access a relationship - like +the `comments` property - and *only* count it, we can *ask* Doctrine to do a +COUNT query instead of loading *all* the comment data. How? Above the +`comments` property, add `fetch="EXTRA_LAZY"`: + +[[[ code('f831189e2a') ]]] + +Before we try this, don't forget that we're in the `prod` environment: +run `cache:clear`: + +```terminal-silent +php bin/console cache:clear +``` + +And `cache:warmup`: + +```terminal-silent +php bin/console cache:warmup +``` + +Ok, let's see if this helps! Spin over, refresh the page and... profile! I'll call +this one: `[Recording] homepage EXTRA_LAZY` - https://bit.ly/sf-bf-extra-lazy. +I'll close the other tab and view the call graph. + +Was this better? Well, `createEntity()` isn't the biggest problem anymore... +so that's a good sign! Let's compare to be sure: go from the original homepage... +to the most recent profile: https://bit.ly/sf-bf-extra-lazy-compare. + +And... wow! Yea, this is a *huge* win in every category! So, was this a good change? +Absolutely: this was an *awesome* change. + +But, even though the queries are much faster... we're still making the same *number* +of queries. Is that something we care about? I don't know? But that's the great +thing about profiling with Blackfire: you don't need to *absolutely* optimize +everything. If you're not sure if something is a problem, you can deploy and +check it on production to see if it's *really* slowing things down under realistic +conditions. *Especially* because sometimes improving performance comes at a +cost of extra complexity. + +Next, let's see if we *can* reduce the number of queries. Will it help performance? +If so, is it enough for the added complexity? diff --git a/sfcasts/performance-tests.md b/sfcasts/performance-tests.md new file mode 100644 index 0000000..6c5e9a8 --- /dev/null +++ b/sfcasts/performance-tests.md @@ -0,0 +1,114 @@ +# Performance Tests + +Let's profile the Github API endpoint again. I'll cheat and go directly to +`/api/github-organization`... and click to profile this. I'll call it: +`[Recording] GitHub Ajax HTTP requests` because we're going to look closer +at the HTTP requests that our app makes to the GitHub API. + +Click to view the call graph: https://bit.ly/sf-bf-http-requests + +Oh wow - this request was *super* slow - 1.83 seconds - a lot slower than we've +seen before. We can see that `curl_multi_select()` is the problem: this is our +code making requests to the GitHub API, which is *apparently* running a bit +slow at the moment. + +## We have a Performance "Bug" + +Lucky for us, that's *exactly* what I wanted to talk about! At the top, Blackfire +tells me that this page made *two* HTTP requests. And HTTP requests are *always* +expensive for performance. + +If you studied the data from the two API endpoints that we're using, you would +discover that it's *possible* - by writing some clever code - to get *all* the +info our app needs with just *one* HTTP request. + +What I'm saying is: our page is making one more HTTP request than it *truly* +needs to. If you think about it... that's kind of a performance "bug": we're +making 2 HTTP requests and we only need 1. + +In an ideal world, when we find a bug, the process for fixing it looks like this. +First, write a test for the *expected* behavior. Second, run that test and watch +it fail. And third, fix the bug and make sure the test passes. + +Whelp, when it comes to a *performance* bug... we can do the *exact* same thing! +We can write a functional test that *asserts* that this endpoint only makes *one* +HTTP request. It's... pretty awesome. + +## Running the Functional Test + +Find your editor and open `tests/Controller/MainControllerTest.php`. I already +set up a functional test that makes a request to `/api/github-organization` +and checks some basic data on the response: + +[[[ code('1bf80b8901') ]]] + +Let's makes sure this passes. Run PHPUnit and point it directly at this class: + +```terminal +php bin/phpunit tests/Controller/MainControlerTest.php +``` + +The first time you run this script, it will probably download PHPUnit in the +background. When it finishes... go tests go! All green. + +## Adding a Performance Assertion + +Here's the idea: in addition to asserting that this response contains JSON +with an `organization` key, I *also* want to assert that it only made one HTTP +request. To do that, first add a trait from the SDK: `use TestCaseTrait`. Next, +in the method, add `$blackfireConfig = new Configuration()` - the one from +`Blackfire\Profile`: the *same* `Configuration` class we used earlier when we +gave our custom-created profile a title. This time call `assert()` and pass it +a *very* special string: `metrics.http.requests.count == 1`: + +[[[ code('2d0373ca8e') ]]] + +I'll show you where that came from soon. Finally, below this, call +`$this->assertBlackfire()` and pass this `$blackfireConfig` and a callback function: + +[[[ code('652d0d2c80') ]]] + +So... this confused me at first. When we call `$this->assertBlackfire()` it will +execute this callback. Inside, we will do whatever work we want - like making +the request. Finally, when the callback finishes, Blackfire will execute +this assertion against the code that we ran: + +[[[ code('1efbf704e7') ]]] + +To get this to work, we need to `use ($client)`: + +[[[ code('70248e61cf') ]]] + +If this doesn't make sense yet... don't worry: we'll dive a bit deeper soon. +But right now... try it! Run the test again: + +```terminal-silent +php bin/phpunit tests/Controller/MainControlerTest.php +``` + +And... it fails! Woo! Failed that `metrics.http.requests.count == 1`! + +## Performance Tests Create Real Profiles + +Behind the scenes, the Blackfire SDK created a *real* Blackfire profile for the +request! You can even copy the profile URL and go check it out! This takes us to +an "assertions" tab. We're making 2 requests instead of the expected one. We'll +talk a lot more about assertions soon. + +Ok, but how did this *really* work? It's beautifully simple. When you run the test, +it *does* make a real Blackfire profile in the background. However, if you go to +your Blackfire homepage, you won't see it. + +Why? Hold `Cmd` or `Ctrl` and click the `assertBlackfire()` method. I love it: +this method uses the SDK - *just* like we did! - to create a *real* profile. When +it does that, it *also* adds a `skip_timeline` option, which simply tells Blackfire +to hide this from our profile page... so it doesn't get cluttered up with all +these test profiles. You can *totally* override that if you wanted... via the +`Configuration` object. + +In reality, the Blackfire PHPUnit integration is doing the *exact* same thing +that we just finished doing in our code: manually creating a new profile. This +is *really* nothing new... and I *love* that! + +Except... for this metrics thing. Where did that string come from? And what else +can we do here? Let's dive into metrics next. diff --git a/sfcasts/probe-create-subscriber.md b/sfcasts/probe-create-subscriber.md new file mode 100644 index 0000000..c9c3178 --- /dev/null +++ b/sfcasts/probe-create-subscriber.md @@ -0,0 +1,157 @@ +# Creating an Automatic Probe Early in your Code + +Once we determine that we want to *create* a probe dynamically in our code, +we *really* want to do that as *early* as possible so that Blackfire can +"instrument" as much of our *code* as possible. + +## Generating the Event Subscriber + +In Symfony, we can do that with an event subscriber... which we will *generate* +to be super lazy. First, in `.env`, make sure that you're back in the `dev` +environment: + +[[[ code('76738815a3') ]]] + +Then, find your terminal and run: + +```terminal +php bin/console make:subscriber +``` + +Call it `BlackfireAutoProfileSubscriber`... and we want to listen to +`RequestEvent`: Go check out the code +`src/EventSubscriber/BlackfireAutoProfileSubscriber.php`: + +[[[ code('9f4b7716c7') ]]] + +So, when this `RequestEvent` happens - which Symfony dispatches *super* early +when handling a request, we want to create & enable the probe. Copy all +of the `$shouldProfile` code, remove it from the controller and paste it here: + +[[[ code('e602a0b448') ]]] + +## Creating the Prove in the Subscriber + +Now add `$request = $event->getRequest()`. To make this *only* profile the GitHub +organization AJAX call - whose URL is `/api/github-organization` - set +`$shouldProfile` equal to `$request->getPathInfo() === '/api/github-organization'`: + +[[[ code('9ed2004c8c') ]]] + +In a real app, I would add *more* code to make sure `$shouldProfile` is *only* +true on the *very* specific requests we want to profile. + +Now I'll re-type the `t` on `Client` and select the correct `Client` class so +that PhpStorm adds that `use` statement to the top of the class for me: + +[[[ code('e0da2d33ea') ]]] + +Thanks PhpStorm! + +But before we try this, I want to code for one edge case: if *not* +`$event->isMasterRequest()`, then `return`: + +[[[ code('eb43743ef3') ]]] + +It might not be important in your app, but Symfony has a "sub-request" system... +and the short explanation is that we don't want to profile those: they are not *real* +requests... and would make a big mess of things. + +Ok, let's try this! I'll close a tab... then refresh the homepage... which +causes the AJAX request to be made. You can see it's slow. Now reload the list +of profiles on Blackfire... there it is! Open it up. + +And... oh wow, oh weird! 281 *microseconds*. Give this a name: +`[Recording] Auto from subscriber`: http://bit.ly/sf-bf-broken-auto-profile + +This profile is... broken. That's 281 *microseconds* - so .281 milliseconds. +And the entire profile is just the `Probe::enable()` call itself! + +## Probe Auto-Close Too Early + +What happened!? Well... remember: the `$probe` object automatically calls +`close()` on *itself* as soon as that variable is garbage collected... which +happens at the end of the subscriber method. That means.... we profiled exactly +*one* line of code. + +The solution is to call `$probe->close()` manually... which - more importantly - +will require us to store the `Probe` object in a way where PHP *won't* garbage collect +it too early. + +So here's the goal: call `$probe->close()` as *late* as possible during the request +lifecycle. We can do this by listening to a *different* event: when +`TerminateEvent::class` is dispatched - that's *very* late in Symfony - call +the `onTerminateEvent()` method: + +[[[ code('b2b39ec123') ]]] + +I'll hit an `Alt`+`Enter` shortcut to create that method... then add the argument +`TerminateEvent $event`: + +[[[ code('0a4a4c05ab') ]]] + +To be able to call `$probe->close()`, we need to store the probe object on a property. +Add `private $probe` with some documentation that says that this will either be +a `Probe` instance from `Blackfire` or `null`: + +[[[ code('95184122e7') ]]] + +Update the code below to be `$this->probe = $blackfire->createProbe()`: + +[[[ code('41c4b3b819') ]]] + +Finally, inside `onTerminateEvent`, if `$this->probe` - I should *not* have put +that exclamation point, that's a mistake - then `$this->probe->close()`: + +[[[ code('6d8f601e0c') ]]] + +If you assume that I did *not* include the exclamation point... then this makes +sense! *If* we created the probe, then we will close it. Problem solved. And... +*really*... the fact that we set the probe onto a *property* is the real magic: +that will prevent PHP from garbage-collecting that object... which will prevent +it from closing itself until we're ready. + +## Increasing the Event Priority + +While we're here, let's make this a little bit cooler. Change `onRequestEvent` +to be an array... and add `1000` as the second item: + +[[[ code('54b130e506') ]]] + +This syntax is... weird. But the result is good: it says that we want to listen +to this event with a priority of 1000. That will make our code run even *earlier* +so that even *more* code will get profiled. + +## Configuration: Name your Profile + +Oh, and there's one other cool thing we can do: we can *configure* the profile. +Add `$configuration = new Configuration()` from `Blackfire\Profile`. Thanks to +this, we can control a number of things about the profile... the best being +`->setTitle()`: `Automatic GitHub org Profile`. Pass this to `createProbe()`: + +[[[ code('d308c1aa00') ]]] + +That's it! Let's see how things whole thing works. Back at the browser, I'll +close the old profile... and refresh the homepage. Once the AJAX call finishes... +reload the Blackfire profile list. Ah! We were too fast - it's still processing. +Try again and... there it is! + +Open it up! http://bit.ly/sf-bf-auto-profile-subscriber + +*Much* better. A few things might still look a *bit* odd... because we're +still not profiling *every* single line of code. For example, `Probe::enable()` +seems to wrap everything. But all the important data is there. + +To avoid making a *million* of these profiles as we keep coding, I'll go back to +the subscriber and avoid profiling entirely by setting `$shouldProfile = false`: + +[[[ code('38668d10d1') ]]] + +Next: you already write automated tests for your app to help *prove* that key +features never have bugs. You... ah... do write tests right? Let's... say you +do. Me too. + +Anyways, have you ever thought about writing automated tests to prevent +*performance* bugs? Yep, that's possible! We can use Blackfire *inside* our test +suite to add performance *assertions*. It's pretty sweet... and now that we +understand the SDK, it will feel great. diff --git a/sfcasts/profile-all.md b/sfcasts/profile-all.md new file mode 100644 index 0000000..33a150a --- /dev/null +++ b/sfcasts/profile-all.md @@ -0,0 +1,106 @@ +# Profile All Requests (Including Ajax) + +When you open the browser extension to create a profile, it has a few options that +we've been... ignoring so far. + +## Debugging Mode + +***TIP +Debugging mode is available via the Debugging add-on. +*** + +For example, "debugging mode" will tell Blackfire to *disable* pruning - that's +when it removes data for functions that don't take a lot of resources - and also +to disable anonymization - that's when it hides exact details used in SQL queries +and HTTP requests. Debugging mode is nice if something weird is going on.. and +you want to *fully* see what's happening inside a request. + +## Distributed Profiling + +***TIP +Distributed profiling is available to Premium plan users or higher. +*** + +Another superpower of Blackfire is called distributed profiling... which you either +won't care about... or it's the most awesome thing ever. Imagine you have a +micro-service architecture where, when you load the page, it makes a few HTTP +requests to some microservices. If you have Blackfire installed on all of your +microservices, Blackfire will automatically create profiles for *every* request +made to *every* app. The final result is a profile with sub-profiles that show you +how the entire infrastructure is working together. It's... pretty incredible. + +But, if you want to disable it and *only* profile this main app, you can do that +with this option. + +## Disabling Aggregation + +The last option is to "disable aggregation". That's a fancy way of telling Blackfire +that you want to make & profile just *one* request, instead of making 10 requests +and averaging the results. + +## Profiling All Requests + +But what I *really* want to look at is this "Profile all requests" link. Hit +"Record"... then refresh. Woh! Cool! It already made 2 requests! And if I scroll +down a little bit... there's a third! Let's stop right there. + +That jumps us to our Blackfire dashboard. These *last* three profiles were just +created: one for the homepage and two others: these are both AJAX calls! Surprise! +Without even thinking about it, we discovered a few extra requests that are part +of that page. + +This first one - `/api/github-organization` - is what loads this GitHub repository +info on the right. This makes an API call for the most popular repositories +under the Symfonycasts organization... which is kind of silly... but it was a *great* +way to show how network requests look in Blackfire. We'll see that in a minute. + +This other request - for `/_sightings` - is an AJAX call that powers the forever +scroll on the page. + +Basically... I like using "profile all requests" in 3 situations. One, to get +an idea of what's all happening on a page. Two, to profile AJAX requests... though +I'll show you another way to do that soon. And three, to profile form submits: fill +out the form, hit "Record", then submit. + +## Checking out the Network Requests + +Let's look closer at the `/api/github-organization` AJAX profile: +https://bit.ly/sf-bf-github-org. As I mentioned, this makes a network request +to the GitHub API to load repository information. The profile... is almost comical! +Out of the 438 millisecond wall time - 82% of it is from `curl_multi_select()` - +that's the time spent making any API calls. + +It's kind of fun to look at this in the CPU dimension, which is only 74 +milliseconds. `curl_multi_exec()` is *still* the biggest offender... but it's +a lot less obvious what the critical path is. Compare that with the I/O wait +dimension, which includes network time. The critical path is *ridiculously* +obvious here. This is an extreme example of how different dimensions can be more +or less useful depending on the situation. + +One of the interesting things is that... this is *not* the full call graph. +According to this, the code goes straight from `handleRaw()` - which is the first +call into the Symfony Framework - to our controller. In reality, there are many +more function calls in between. Switch back to the CPU dimension. Yep! This +shows more nodes. + +*This* is the result of that "pruning" I mentioned a few minutes ago. Blackfire +removes function calls that don't consume any significant resources so that +the critical path - from a *performance* standpoint - is more obvious. The call +graph also automatically hides or shows some info based on what you're zoomed +in on. + +In this situation, the critical path is obvious. You can also see the network +requests on top. There are actually *two*: one that returns 1.5 kilobytes and +another that returns 5. + +This shows the network time too... but at *least* if you're using the Symfony +HTTP client like I am, these numbers aren't right - they're far too small... +I think that's due to the asynchronous nature of Symfony's HTTP Client. +That's ok - because the overall cost *is* showing up correctly in all the other +dimensions. + +So how do we fix this? Should we add some caching? Or somehow try to make only +*one* API call instead of two? We're actually going to revisit and fix this problem +later. For now, I wanted you to be aware of the "Profile All" feature. Next, +let's check out the Blackfire command-line tool, which has *two* superpowers... +one of which has nothing to do with the command line. diff --git a/sfcasts/property-caching.md b/sfcasts/property-caching.md new file mode 100644 index 0000000..b525a11 --- /dev/null +++ b/sfcasts/property-caching.md @@ -0,0 +1,144 @@ +# Property Caching + +Now that we've got our application in production mode and we've dumped the autoloader, +it's easier to see what the biggest performance problem is on this page: +https://bit.ly/sf-bf-profile4 + +And actually, there might *not* be any more problems worth solving. I mean, +it's loading in 104 milliseconds... *even* with the Probe doing all the profiling +work. + +But... let's see for sure. The function with the highest exclusive time *now* is +`PDOStatement::execute()`... which is a low-level function that *executes* SQL +queries. + +***TIP +The SQL Query information requires a Profiler plan or higher. +*** + +If we hover over the query info, these are only taking 12.5 milliseconds... but +we *are* making 43 SQL calls on this page. Is that a problem? It's not ideal, +but is it worth fixing? I guess it depends on how much you care... and whether the +fix would be easy or if it would add a lot of complexity to our app. + +## Navigating the Call Graph: Top to Bottom, Bottom to Top + +When you're trying to identify where the problem is, there are two ways to look +at the call graph - and I often do both to help me understand what's going on. +First, you can read from top to bottom - trace through your *whole* application +flow to figure out what's going on down the hot path. Or, you can do the opposite: +start at the bottom - start *where* the problem is... and trace up to find where +your code starts. + +Let's start from the top: `handleRaw()` is the framework booting up... and as +we trace down... it renders our controller, renders our template... and +we're once again inside the `body` block. This is really the same as last time! +Our `AppExtension::getUserActivityText()` calls the `countRecentCommentsForUser()` function 23 +times. That makes sense: we probably have 23 comments on the page... and for +each comment, we need to count *all* the author's comments to print out this label. + +## Navigating Dimensions + +Before we think about if, and *how* we might fix this, let's back up and look +at other *dimensions* of this profile. In addition to wall time, we can completely +re-draw the call graph based on only I/O time or CPU time. Remember, wall time +is I/O time + CPU time. Or we could do something totally different: look at +which functions are using the most *memory*... or even the most network bandwidth. + +When we look at this in the network dimension, `PDOStatement::execute()` - the +function that makes SQL calls - shows up here as a *big* problem. That's because +SQL queries are technically network requests. + +Re-draw the call graph for the I/O Wait time dimension. We see the same problem +here because network calls - and so SQL calls - are part of I/O wait time. + +The point is: while "wall time" is *typically* the most useful dimension, don't +forget about these other ones: they can give us more information about what's +going on. Is a function slow because of inefficient code inside? Or is it, for +example, because of a network call? + +Click back to I/O wait time - `PDOStatement::execute()` is *definitely* the issue +according to this - and the critical path is pretty clear. This *one* function is +taking over *half* the I/O wait time... but that's only 6 milliseconds. Optimizing +this might not be worth it... but let's at least see if we can figure out how to +call it less times. + +As we already discovered, the problem is coming from +`CommentRepository::countForUser()` which is called by +`AppExtension::getUserActivityText()`. + +Over in `src/Twig/AppExtension.php`, each time we render a comment, it calls +`countForUser()` and passes the `User` object attached to this comment: + +[[[ code('1a4694acb5') ]]] + +## Property Caching + +Can we optimize this? Well... sometimes, the *same* user will comment many times +on the same sighting - like this `vborer` user. When that happens, we're +making a query to count that user's comments for *every* comment. That's wasteful! + +So here's one idea: leverage "property caching". Basically, we'll keep track of +the "status" strings for each user and use that to avoid calculating the status +more than once for a given user. + +Start by moving most of the logic into a private function called +`calculateUserActivityText()`: this will have a `User` argument and return a +string: + +[[[ code('21a6a9ac56') ]]] + +Next, add a new property to the top of the file: `private $userStatuses = []`: + +[[[ code('681ac88340') ]]] + +Back in the public function, here's the magic: if *not* +`isset($this->userStatuses[$user->getId()])`, then set it by saying +`$this->userStatuses[$user->getId()] = $this->calculateUserActivityText($user)`. +At the bottom of the function, return `$this->userStatuses[$user->getId()]`: + +[[[ code('ae6370d772') ]]] + +This is one of my *favorite* performance tricks because it has *no* downside, +except for some extra code. If `getUserActivityText()` is called and passed the +same User multiple times within a single request, we won't duplicate any work. + +So... we probably made our site faster, right? Let's find out! Since we're in +Symfony's `prod` environment, just to be safe, let's clear the cache: + +```terminal +php bin/console cache:clear +``` + +and warm it up: + +```terminal-silent +php bin/console cache:warmup +``` + +Back in the browser, refresh the page and... let's profile! I'll name this one +`[Recording] show page try property caching`. View the call graph: +https://bit.ly/sf-bf-profile-prop-caching. + +Ok - `PDOStatement` still looks like a main problem... but I think we're a +*little* faster. You know what? Let's just compare the two profiles. Go back +to the dashboard and compare the previous profile to this one. +https://bit.ly/sf-bf-compare-prop-caching. I'll close the old profile. + +Ok, so it *did* help - lower time in each dimension... and we saved 5 queries. +So, this is a win, right? *Maybe*. If you profiled other Big foot sighting pages, +which I did, you would find that this often did *not* help... or helped *very* little. +In fact, this is the *first* time I've seen it help *nearly* this much. + +So, does the improvement justify the added complexity in our code? If we can +repeat this 13% improvement consistently, yea, it is. But if it's more like 1%, +probably not. + +And even 13% is not *that* much... and `PDOStatement::execute()` is *still* the +biggest problem. I feel like the profile is trying to ask us: is there a *better* +way to optimize this? + +Next, let's try another approach: using a *real* cache layer. *Truly* caching +things has its own downside: added complexity in your code and *possibly* - depending +on what you're caching - the need to worry about *invalidating* cache. We'll +want to be sure it's worth it. diff --git a/sfcasts/recommendations.md b/sfcasts/recommendations.md new file mode 100644 index 0000000..f63c396 --- /dev/null +++ b/sfcasts/recommendations.md @@ -0,0 +1,124 @@ +# Recommendations + +Head back to the Blackfire dashboard... and click into the latest profile - the +one with our COUNT query improvement - https://bit.ly/sfcast-bf-profile2. + +The critical path is now much less clear... there are kind of two critical paths... +but neither end in a node with a red background... which would indicate an +obvious issue. This might mean that there aren't any more easy performance +"wins" on this page... it might be fast enough! + +## Focus in Improvement, Not Absolute Time + +The response time from the profile was 270 milliseconds. If you're not satisfied +with that, remember two things. First, we're profiling Symfony in its +`dev` environment. Switching to `prod` would be faster... we'll do that +soon. And second, the time you see in a profile will *never* be quite as fast as +the real thing, because when the probe is activated - the PHP extension that does +all the profiling - it slows things down. So don't obsess over any *absolute* +numbers. Instead, focus on finding ways to *improve* each number. + +## Switching to Symfony's prod Environment + +The function that takes up the most *exclusive* time is from something called +`DebugClassLoader`. Ah. Our local Symfony app is currently running in its `dev` +environment, which adds a lot of debugging tools, like the web debug toolbar. +That stuff also slows things down... which makes profiling less useful: the +profiler is cluttered up with function calls that won't *really* be there in +production. That extra noise makes finding the *true* performance issues harder. + +So, let's switch our app to the `prod` environment while profiling. + +Open up `.env`, find the `APP_ENV` variable, and change it to `prod`: + +[[[ code('4bef8cd1b5') ]]] + +That will make things more realistic... but it also means that after... pretty +much *any* change to our code, we will need to clear & warm the cache. No big deal: +at your terminal, run: + +```terminal +php bin/console cache:clear +``` + +and then: + +```terminal +php bin/console cache:warmup +``` + +Ok, let's profile again! I'll refresh... just to make sure the page is +working and... profile! I'll call this one `[Recording] Show page in prod mode`. +Cool! 106 milliseconds is a huge improvement! Click to open the call graph: +https://bit.ly/sf-bf-profile3 + +*Now* the function list and the call graph look a bit more useful. There's no +*super* problematic, red-background node on the graph... but the function that +takes up the most exclusive time - `PDOStatement::execute()` - at least makes +sense: that's executing database queries. + +## Hello Recommendations + +***TIP +The Recommendations information requires a "Profiler" plan level or higher. +*** + +Back on our site, you *may* have noticed that each time we've profiled, a little +exclamation icon showed up. If you clicked that, it would take you to a +"Recommendations" section of the profile. The exclamation point was telling us +that we're failing one or more Blackfire recommendations. + +I dig this feature. Because Blackfire is written *for* PHP, it has special +knowledge of how queries are made, how Composer works, Symfony, Magento and so +many other things. The Blackfire team has *used* that knowledge to add a bunch +of things that they call "recommendations". I call them "sanity checks". + +For example, Blackfire counted our queries and said: + +> Hey! FYI - you've got a bunch of queries on this page... maybe you should try +> to have less than 10. + +Yea, our 43 queries *is* pretty high. Does that mean we should immediately run +into our code and fix it? Nah. It's just a good thing to keep on your radar. + +There's also a recommendation that Doctrine annotation metadata should be cached +in production. Honestly... I'm not sure why that's there - Symfony apps come with +a `config/packages/prod/doctrine.yaml` file that takes care of caching these +when you're in the `prod` environment. When I tried to reproduce this later... it +went away. So let's ignore it for now. If it comes back *later* when we deploy +to production, then I *will* want to look into it further. + +## Composer Autoloader Recommendation + +The *last* recommendation is *awesome*: + +> The Composer autoloader class map should be dumped in production + +By the way. if you don't know what something means, the cute question mark can help. + +Look back at the function list: the *second* highest function *was* something +related to Composer's autoload system. Blackfire *nailed* that this is an issue. + +You may already know this, but when you deploy, you're *supposed* to run a special +command - or add a special option - that tells Composer to dump an *optimized* +autoload file. Blackfire is telling us that we forgot to do this locally. + +Let's fix this: it will help clean up even *more* stuff on the profile. At your +terminal, run: + +```terminal +composer dump-autoload --optimize +``` + +Perfect! Refresh the page... it works... and create another profile - I'll +call this: `[Recording] Show page after optimized autoloader`. Click to view +the call graph: https://bit.ly/sf-bf-profile4 and close the old one. + +It's not *significantly* faster, but we've removed at least one heavy-looking +function call from our list. That will help us focus on any *real* problems. +Check out the recommendations now. Yea! The Composer one is *gone*. Later, +we'll learn how to add custom assertions - which are basically a way to write +your *own* custom recommendations. + +Next, let's look deeper at what's going on with this `PDOStatement::execute` stuff. +Is our page fast enough? Or can we discover some further, hidden optimizations? diff --git a/sfcasts/service-subscriber.md b/sfcasts/service-subscriber.md new file mode 100644 index 0000000..faf10d5 --- /dev/null +++ b/sfcasts/service-subscriber.md @@ -0,0 +1,120 @@ +# Service Subscribers + +Because this service is instantiated on every request... it means that all four of +the objects in its constructor *also* need to be instantiated: + +[[[ code('6c0c186b70') ]]] + +That's not a *huge* deal... except that two of these services *probably* wouldn't +be instantiated during a normal request *and* aren't even used unless the current +request is a login form submit. In other words, we're *always* instantiating +these objects... even though we don't need them! + +How can we fix this? By using a service subscriber: it's a strategy in Symfony +that allows you to get a service you need... but *delay* its instantiation until - +and unless - you actually need to use it. It's great for performance. But, like many +things, it comes at a cost: a bit more complexity. + +## Implementing ServiceSubscriberInterface + +Start by adding an interface to this class: `ServiceSubscriberInterface`: + +[[[ code('37dfe2fea7') ]]] + +Then I'll move to the bottom of the file, go to the "Code"->"Generate" menu - or +`Command`+`N` on a Mac - and select "Implement Methods" to generate the *one* method +this interface requires: `getSubscribedServices()`: + +[[[ code('46957eb743') ]]] + +What does this return? An array of type-hints for all the services we need. For +this class, it's these four. So, return `EntityManagerInterface::class`, +`UrlGeneratorInterface::class`, `CsrfTokenManagerInterface::class` and +`OtherLongInterfaceName::class`, uh, `UserPasswordEncoderInterface::class`: + +[[[ code('b5eca5e365') ]]] + +By doing this, we can now *remove* these four arguments. Replace them with +`ContainerInterface` - the one from `Psr\Container` - `$container`: + +[[[ code('ae947bd059') ]]] + +When Symfony sees the new interface and this argument, it will pass us a, sort of, +"mini-container" that holds the 4 objects we need. But it does this in a way where +those 4 objects aren't *created* until we use them. + +Finish this by removing the old properties... and having just one: `$container`. +Set it with `$this->container = $container`: + +[[[ code('ad4d96517e') ]]] + +## Using the Container Locator + +Because those properties are gone, *using* the services looks a bit different. For +example, down here for `CsrfTokenManager`, now we need to say +`$this->container->get()` and pass it the type-hint `CsrfTokenManagerInterface::class`: + +[[[ code('819997ff0c') ]]] + +This will work *just* like before *except* that the `CsrfTokenManager` won't be +instantiated *until* this line is hit... and if this line *isn't* hit, it *won't* +be instantiated. + +For `entityManager`, use `$this->container->get(EntityManagerInterface::class)`, for +`passwordEncoder`, `$this->container->get(UserPasswordEncoderInterface::class)` and +finally, for `urlGenerator`, use `$this->container->get->(UrlGeneratorInterface::class)`. +I'll copy that and use it again inside `getLoginUrl()`: + +[[[ code('d56005dfa3') ]]] + +So, a *little* bit more complicated... but it *should* take less resources to +create this class. The question is: did this make *enough* difference for us to +*want* this added complexity? Let's find out. First, clear the cache: + +```terminal-silent +php bin/console cache:clear +``` + +And warm it up: + +```terminal-silent +php bin/console cache:warmup +``` + +## Comparing the Results + +Move back over... I'll close some tabs and... refresh. Profile again: I'll call +this one: `[Recording] Homepage service subscriber`: +https://bit.ly/sf-bf-service-subscriber. View the call graph. + +Excellent! Go back to the "Memory" dimension and search for "login". The call is +still here but it's taking a lot less memory *and* less time. Let's compare this to +be sure though. Click back to the homepage and go from the previous profile to +this one: https://bit.ly/sf-bf-service-subscriber-compare. + +Nice! The wall time is down by 4%... CPU is down and memory *also* decreased... +but *just* a little bit. + +So was this change worth it? Probably. But this doesn't mean you should run around +and use service subscribers *everywhere*. Why? Because they add complexity to your +code *and*, unless you have a specific situation, it won't help much or at all. +Use Blackfire to find the *real* problems and target those. + +For example, we also could have made this same change to our `AgreeToTermsSubscriber`: + +[[[ code('af5d825bc9') ]]] + +This class is *also* instantiated on every request... but rarely needs to do +its work. That means we are causing the `FormFactory` object to be instantiated +on every request. + +But, go back to the latest profile... click to view the memory dimension... and +search for "agree". There it is! It took 1.61 milliseconds and 41 kilobytes to +instantiate this. That's... a lot less than the login authenticator. So, is +making this class a service subscriber worth it? For me, no. I'd rather get back +to writing features or fixing bigger performance issues. + +Next, we can take a lot more control of the profiling process, like profiling just +a *portion* of our code *or* automatically triggering a profile based on +some condition, instead of needing to manually use the browser extension. Let's +talk about the Blackfire SDK next. diff --git a/sfcasts/sf-cloud-database.md b/sfcasts/sf-cloud-database.md new file mode 100644 index 0000000..a27dccb --- /dev/null +++ b/sfcasts/sf-cloud-database.md @@ -0,0 +1,151 @@ +# Database Tricks on SymfonyCloud + +We just deployed to SymfonyCloud!!! Well, I mean, we *did*... but it doesn't... +ya know... *work* yet. Because this is the *production* 500 error, we can't see +the real problem. + +No worries! Head back to your terminal. The `symfony` command has an easy way to +check the production logs. It is... + +```terminal +symfony logs +``` + +This prints a list of *all* the logs. The `app/` directory is where our application +is deployed to - so the first item is our project's `var/log/prod.log` file. You +can also check out the raw access log... or everything. Hit 0 to "tail" the `prod.log` +file. And... there it is: + +> An exception has occurred... Connection refused. + +## Adding a Database to SymfonyCloud + +I recognize this: it's a database error.... which... hmm... makes sense: we haven't +told SymfonyCloud that we *need* a database! Let's go do that! + +Google for "SymfonyCloud MySQL" to find... oh! A page that talks about *exactly* +that. Ok, we need to add a little bit of config to 2 files. The first is +`.symfony/services.yaml`. This is where you tell SymfonyCloud about all the +"services" you need - like a database service, ElasticSearch, Redis, RabbitMQ, etc. + +Copy the config for `.symfony/services.yaml`... then open that file and paste: + +[[[ code('ff823a448b') ]]] + +The database is actually MariaDB, which is why the version here is 10.2: +MariaDB version 10.2. + +Notice that we've used the key `mydatabase`. That can be *anything* you want: we'll +*reference* this string from the *other* config file that we need to change: +`.symfony.cloud.yaml`. + +Inside *that* file, we need a `relationships` key: this is what *binds* the +web container to that database service. Let's see... we don't have a +`relationships` key yet, so let's add it: `relationships` and, below, add our +*first* relationship with a special string: `database` set to `mydatabase:mysql`: + +[[[ code('91d65a60a1') ]]] + +This syntax... is a little funny. The `mydatabase` part is referring to whatever +key we used in `services.yaml` - and then we say `:mysql`... because that service +is a `mysql` type. + +The *really* important thing is that we called this relationship `database`. Thanks +to that `SymfonyCloud` will expose an environment variable called `DATABASE_URL` +which contains the *full* MySQL connection string: username, host, database name +and all: + +[[[ code('dc0f4fcefe') ]]] + +It's literally `DATABASE_URL` and not `PIZZA_URL` because we called the +relationship `database` instead of `pizza`... which would have been less +descriptive, but more delicious. + +This is important because `DATABASE_URL` *happens* to be the environment variable +that our app will use to connect to the database. In other words, our app will +*instantly* have database config. + +Back at the terminal, hit `Ctrl`+`C` to exit from logging. Let's add the two changes +and commit them: + +```terminal +git add . +git commit -m "adding SfCloud database" +``` + +Now, deploy! + +```terminal +symfony deploy +``` + +Oh, duh - run with the `--bypass-checks` flag: + +```terminal-silent +symfony deploy --bypass-checks +``` + +The deploy will still take some time - it has a lot of work to do - but it'll +be faster than before. When it finishes... it dumps the same URL - that +won't change. But to be even *lazier* than last time, let's tell the command to +open this URL in my browser... *for* me: + +```terminal +symfony open:remote +``` + +## Tunneling to the Database + +And... we have a deployed site! Woo! The database is empty... but if this were +a real app, it would start to be populated by *real* users entering their *real* +Bigfoot sightings... cause Bigfoot is... totally real. + +But... to make this a bit more interesting for *us*, let's load the fixture data +one time on production. + +This is a bit tricky because the fixture system - which comes from +DoctrineFixturesBundle - is a Composer "dev" dependency... which means that +it's not even *installed* on production. That's good for performance. If it +*were* installed, we could run: + +```terminal +symfony ssh +``` + +To SSH into our container, and then execute the command to load the fixtures. But... +that won't work. + +No problem! We can do something cooler. Exit out of SSH, and run: + +```terminal +symfony tunnel:open +``` + +I *love* this feature. Normally, the remote database isn't accessible by *anything* +other than our container: you can't connect to it from anywhere else on the +Internet. It's totally firewalled. But *suddenly*, we can connect to the production +database locally on port 30000. We can *use* that to run the fixtures command +locally - but send the data up to *that* database. Do it by running: + +```terminal +DATABASE_URL=mysql://root:@127.0.0.1:30000/main php bin/console doctrine:fixtures:load +``` + +Ok, let's break this down. First, there is actually a *much* easier way to do all +of this... but I'll save that for some future SymfonyCloud tutorial. Basically, +we're running the `doctrine:fixtures:load` command but sending it a *different* +`DATABASE_URL`: one that points at our production database. When you open a +tunnel, you can access the database with `root` user, no password - and the +database is called `main`. + +The only problem is that this command... takes *forever* to run. I'm not sure +exactly why - but it *is* doing all of this over a network. Go grab some coffee +and come back in a few minutes. + +When it finishes... yes! Go refresh the page! Ha! We have a production site with +at least *enough* data to make profiling interesting. + +Next, let's do that! Let's configure Blackfire on production! That's easy right? +Just repeat the Blackfire install process on a different server... right? Yep! +Wait, no! Yes! Bah! To explain, we need to talk about a *wonderful* concept +in Blackfire called "environments". diff --git a/sfcasts/staging-environments.md b/sfcasts/staging-environments.md new file mode 100644 index 0000000..547b686 --- /dev/null +++ b/sfcasts/staging-environments.md @@ -0,0 +1,145 @@ +# Staging Environment Builds + +We now have *two* versions of our site deployed: our production deploy and a, +sort of, "staging" deploy of a pretend feature we're working on. Blackfire is +*all* set up on the *production* server, but not on the staging server. Let's +fix that! + +Back on the "Install" page, select "SymfonyCloud" as our host to get to its docs. +To set up Blackfire on production, we did 3 things. One, added the extension. Two, +ran this `var:set` command to configure our Blackfire Server id and token. And +three, ran `integration:add` so that every deploy to `master` would trigger a +Blackfire build in our environment. + +*Technically*, on the staging server, the Blackfire extension is already enabled +*and* it's set up to use the Server Id and token from our *production* Blackfire +environment. But, as we talked about in the last chapter, I don't want to mix +my production builds with builds from staging servers. + +## Creating a new Blackfire Environment + +Instead, go back to our Blackfire organization and create a *second* environment. +Let's call it "Sasquatch Sightings Non-master". For the endpoint, use the +production environment URL. But don't worry, that URL won't *actually* be used. +You'll see. + +Hit "Create environment"... then remove the build notifications and save. View +the new environment - I'll get the credentials in a minute. Now, *stop* the +periodic builds. Why? Well in our setup, at any point, we may have zero or +*many* different "staging" servers. There's not just *one* server to build... so +if we did a periodic build... which "staging" server would it use? It just doesn't +make sense in our case. What *does* make sense is to *trigger* a new build each +time we *deploy* to a staging server. + +## Different Server Id and Token on Staging + +Ok, let's think about this: we now have *two* Blackfire environments. We want the +*production* server to use the Blackfire server id and token for the production +environment... and we want every *other* deploy to use the Blackfire id and token +from the new "Non-master" environment. + +*How* you do that depends on how you deploy. For us, we can use a SymfonyCloud +config trick. First, list which variables we have set with: + +```terminal +symfony vars +``` + +We have the two that were set by the `var:set` command we ran earlier. Delete +*both* of them: + +```terminal +symfony var:delete BLACKFIRE_SERVER_ID BLACKFIRE_SERVER_TOKEN +``` + +We're going to *re-add* these in a minute... but with some different options. Now, +go back to the installation page... and refresh... so this shows our new environment. +For the `var:set` command, select the `Non-master` environment. Copy the command, +move over and paste: + +```terminal-silent +symfony var:set BLACKFIRE_SERVER_ID=XXXXXXX BLACKFIRE_SERVER_TOKEN=XXXXXX +``` + +If we stopped now, it would mean that *every* server would send its profiles to +the new Non-Master environment... which is not exactly what we want. But here's +the trick: on the "Install" page, change to the "Production" Blackfire environment, +and copy *its* command. We're going to *override* these variables, but *just* on +the SymfonyCloud `master` environment. + +Paste the command, then add `--env=master --env-level` so that the variables are +used as "overrides" for *only* that environment. Finish with `--inheritable=false` +so that when we create *new* SymfonyCloud environments, they don't inherit these +variables from `master`: we want them to use the *original* values: + +```terminal-silent +symfony var:set BLACKFIRE_SERVER_ID=XXXXXXX BLACKFIRE_SERVER_TOKEN=XXXXXX \ + --env=master --env-level --inheritable=false +``` + +This is a *long* way of saying that the `master` environment on SymfonyCloud will +now use the server id and token for the "Sasquatch Sightings Production" Blackfire +environment. And every *other* deploy will use the credentials for the +"Non-Master" environment. To be sure, run: + +```terminal +symfony vars --env=master +``` + +Yep! 6900 is the server id for Production. Now try: + +```terminal +symfony vars --env=some_feature +``` + +Perfect: that uses the *other* Server id and token. We're good! + +## Staging: Builds on Deploy + +The *last* thing I want to do is run this `integration:add` command again. We +ran this *earlier* to tell SymfonyCloud that it should notify our "Production" +Blackfire environment whenever we deploy to `master`. Now copy the "Non-Master" +environment command... and run it: + +```terminal-silent +symfony integration:add --type=webhook --url='https://USER:PASS@blackfire.io/api/v2/builds/env/aaaabbee-abcd-abcd-abcd-c49b32bb8f17/symfonycloud' +``` + +Say yes to all events, all states *and* all environments. Actually, what we +*really* want to say is: create a build on the "Non-Master" environment every +time any branch *except* for master is deployed... but I don't think that's possible. + +Phew! Let's redeploy both SymfonyCloud environments to see all of this in action: + +```terminal +symfony redeploy --bypass-checks +``` + +Because we're currently checked out to the `some_feature` branch, this *deploys* +*that* branch. When it finishes, run the same command but with `--env=master` to +redeploy production: + +```terminal-silent +symfony redeploy --bypass-checks --env=master +``` + +We also could have *switched* to that branch - `git checkout master` - and *then* +ran `symfony redeploy`. That's the more traditional way. + +Done! Let's go see what that did! First check out the Blackfire production +environment. Yes! The redeploy to `master` created *one* new build. Perfect. +Now check out the Non-master environment. Oh, this has *two* new builds: one +for the `some_feature` deploy and another for the `master` deploy. We don't +*really* want or care about that second one... but it's fine. What we *do* care +about is that *now*, every time we deploy to a non-production server, we get a +new build here. + +If you use GitHub or Gitlab, you can take this one step further by doing 2 things. +First, SymfonyCloud has a feature where it can automatically deploy the code +you have on a pull request. And because that would trigger a new build, *second*, +you can configure Blackfire to *notify* GitHub or Gitlab of your build results +so that they show up *on* the pull request itself. Pretty awesome. + +I *love* our setup. But there's one more environment feature that we haven't +checked out yet: the ability to set *variables* that you use in your scenarios. +Let's check that out next. diff --git a/sfcasts/the-pieces.md b/sfcasts/the-pieces.md new file mode 100644 index 0000000..52f3656 --- /dev/null +++ b/sfcasts/the-pieces.md @@ -0,0 +1,54 @@ +# Blackfire Install: Agent, Probe, Chrome Extension + +So let's get Blackfire installed on our local computer. Head over to +`https://blackfire.io` and log in or register for a new account. As you can see, +I've been busy using Blackfire already. + +## Agent & Probe: How it all Works + +Click the Docs link on top... then installation on the left. Before we jump in +and install everything, I want you to understand just a *little* bit about how this +all works: understanding this helped me a *bunch*. If you want to skip this +and head to the next video you *can*... just prepare to miss out on some cool +diagrams! + +Click the "main components of Blackfire" link and scroll down to find... woh! A +diagram that shows you *exactly* how Blackfire works. + +## The Probe: PHP Extension that Collects Data + +How about... we look at a simplified version. There are 3 things we need to +install. The first is called the "probe", which is really just a PHP extension. +You'll install this wherever your code is running - like on your local machine, +and later on production. The probe's job is simple, but huge! It's responsible for +collecting *all* of the information: all the function calls, how long each took, +which function called which other function, how much memory did something take, +network requests... you get the idea. By the way, the process of "collecting all +the data" is sometimes called instrumentation... which I *only* mention so that +if you see this fancy word... it hopefully won't confuse you... it confused me. + +## The Probe: Collector and Sender + +The *second* thing we will need to install is called the "agent". This is a +service - or "daemon" - that runs on your computer - or on your production machine. +It... just sits there and waits. When the PHP extension - the probe - finishes +collecting all the data, it sends that data to the agent. The agent does some +processing on it - like removing unimportant information and anonymizing things - +then ultimately sends that data to the Blackfire server. It's... the middleman. + +So basically, the probe and agent work together to collect the info and send it +to Blackfire. + +## The Browser Extension: Profiling Activator + +The *last* piece you'll need to install is a browser extension. Remember: the +probe is *not* profiling *every* single request. Normally, when a request comes +in, it yawns... and does nothing. The browser extension's job is to *activate* +profiling. It basically says: + +> Hey probe! Wake up! I'm going to make a request and I *actually* want you to +> do your thing - collect all the data and sent it to the agent. Cool? Text me +> when it's done. + +And... that's it! This bottleneck-fighting superhero trio is our ticket to +performance glory. Next, let's get them installed. diff --git a/sfcasts/timeline-surprise.md b/sfcasts/timeline-surprise.md new file mode 100644 index 0000000..985b0b6 --- /dev/null +++ b/sfcasts/timeline-surprise.md @@ -0,0 +1,84 @@ +# Timeline: Finding a Hidden Surprise + +One of the big spots on the timeline is the `RequestEvent`. It's purple because +this is an event: the *first* event that Symfony dispatches. It happens before +the controller is called... which is pretty obvious in this view. + +Let's zoom in: by double-clicking the square. Beautiful! What happens inside this +event? Apparently... the routing layer happens! That's `RouterListener`. You can +also see `Firewall`: this is where authentication takes place. Security is a complex +system... so being able to see a bit about what happens inside of it is pretty +cool. At some point... it calls a method on `EntityRepository` and we can see +the query for the `User` object that we're logged in as. Pretty cool. + +## The Hidden Slow Listener + +There's one more big chunk under `RequestEvent`: something called +`AgreeToTermsSubscriber`... which is taking 30 milliseconds. Let's open that +class and see what it does: `src/EventSubscriber/AgreeToTermsSubscriber.php`: + +[[[ code('9d6ada090e') ]]] + +Ah yes. Every now and then, we update the "terms of service" on our site. When +we do that, our lovely lawyers have told us that we need to require people to +agree to the updated terms. *This* class handles that: it gets the authenticated +user and, if they're not logged in, it does nothing: + +[[[ code('1fa1592c36') ]]] + +But if they *are* logged in, then it renders a twig template with an +"agree to the terms" form: + +[[[ code('5008a57426') ]]] + +Eventually, *if* the terms have been updated since the last time this `User` +agreed to them, it sets that form as the response instead of rendering the *real* +page. + +We haven't seen this form yet... and... it's not really that important. Because +we *rarely* update our terms, 99.99% of the requests to the site will *not* +display the form. + +So... the fact that this is taking 30 milliseconds... even though it will almost +*never* do anything... is kind of a lot! + +## Blue Memory Footprint + +Oh, and see this blue background? I love this: it's the memory footprint. If we +trace over this call - this is about when the `AgreeToTermsSubscriber` happens - +the memory starts at 3.44 megabytes... and finishes around 4.46. That's 1 megabyte +of memory - kinda high for such a rarely-used function. + +The point is: this method doesn't take *that* long to run. And so, it may not have +shown up as a performance critical path on the call graph. But thanks to the timeline, +this invisible layer jumped out at us. And... I think it *is* taking a bit too +long. + +## Fixing the Slow Code + +Back in the code, the mistake I made is pretty embarrassing. I'm using some pretend +logic to see whether or not we need to render the form. But... I put the check too +late! + +[[[ code('c6f6a310ad') ]]] + +We're doing all the work of rendering the form... even if we don't use it. + +Let's move that code all the way to the top. Ah, too far - it needs to be after +the fake `$latestTermsDate` variable: + +[[[ code('24c600ffac') ]]] + +That looks better. Let's try it! I'll refresh the page. Profile again and call +it `[Recording] Homepage authenticated fixed subscriber`: http://bit.ly/sf-bf-timeline-fix + +Let's jump straight to view the Timeline... double-click `RequestEvent` and this +time... `AgreeToTermsSubscriber` is gone! We can see `RouterListener` and `Firewall`... +but *not* `AgreeToTermsSubscriber`. That's not because our app isn't *calling* +it anymore: it is. It's because Blackfire hides function calls that take almost +no resources. That's great. + +Next, we know that we can write code inside a function that is slow. But did you +know that sometimes even the *instantiation* of an object can eat a lot of resources? +Let's see how that looks in Blackfire and leverage a Symfony feature - service +subscribers - to make instantiation lighter. diff --git a/sfcasts/timeline.md b/sfcasts/timeline.md new file mode 100644 index 0000000..194b434 --- /dev/null +++ b/sfcasts/timeline.md @@ -0,0 +1,111 @@ +# Timeline: Go Behind-the-Scenes with your Code + +Click log in to find our super-secure login system. We not only give you a valid +email address, but even the password! We're *very* generous to our users. + +You can't tell, but now that we're logged in, a new piece of code is... silently +running in the background on each request. Blackfire is going to help us notice this. + +## Back to the dev Environment + +Before we profile this page, open up the `.env` file and switch *back* to the +`dev` environment: + +[[[ code('64b1720aaa') ]]] + +What I'm about to show you is more of a *debugging* tool than a *profiling* tool. +We're switching back to the `dev` environment both to make our life a little bit +easier - no need to clear the cache after changes - *and* because when your code +executes more slowly, Blackfire tends to prune, or remove, less stuff. That's *bad* +for trying to find performance issues, but *good* if your goal is to debug something... +or understand how your app is working. + +I'll refresh the page to make sure that it works. Yep! Our handy web debug toolbar +on the bottom is back! Let's profile! I'll call this one +`[Recording] Homepage authenticated dev`: http://bit.ly/sf-bf-timeline. Poetry. + +When that finishes, as usual, click to view the call graph. Okay: there's not too +much interesting here... especially because the `DebugClassLoader` stuff is once +again adding "noise" that won't be there on production. It's not clear what the +critical path is... and the page, at this point, is probably fast enough for me. + +## Hello Timeline + +So let's look at something else: click the "Timeline" link. OooOOOOo. The timeline... +other than just *looking* cool... is *the* place to go to... just... basically +figure out how your app is working: how does the code flow through all the layers? +What hidden things might be happening? + +For example, this page apparently has 28 SQL queries. But where are these +happening? Are they all in the controller? Are some in the controller and others +are in the template? Are some coming from somewhere else we didn't even think of? +That's something that the call graph can't really tell us. + +I love the timeline... but I'll admit that the first few times I looked at this +page... I didn't really understand what was going on... or how to make this useful. +It *looks* simple enough - we can see the function calls and their child calls +from left to right through the lifecycle of the request - but there's more to it. + +## Metrics + +Let's start on the left: these timeline metrics. Metrics are basically a way +that Blackfire groups function calls together and give them a label. For example, +Blackfire knows that a *specific* function call means that an *event* is being +dispatched. It finds those, labels all of them as `symfony.events` and give them +this purple color so that they show up more clearly on the right. Here's one +Symfony event right here... and there's another one. + +It does the same thing for SQL queries: it knows that `PDOStatement::execute()`, +`PDO::query()` and several other functions mean that an SQL query is being made. +It groups them together, calls them `sql` and labels them as yellow. It's a great +idea... and is just that simple. + +Below this, there is another section called "Other Metrics". These are the same +thing: meaningful groups of function calls. The only difference is that Blackfire +does *not* give these a special color and they don't show up on the timeline. +These are... just... raw data... that sit right here. If you're wondering why +that would *useful*... I was too! For the purpose of the timeline, they are *not* +useful. They'll come in handy later when we talk more about metrics. Metrics are +their own big topic. + +## Finding Metrics in the Timeline + +Let's look at one of the timeline metrics `doctrine.entities.hydrated`. What does +this one mean? Sometimes the title of a metric will tell you a bit more... but +often the metric name is all you really have. Most metrics are self-explanatory. + +Depending on how well you know Doctrine, this might be obvious... or not. This +metric refers to whenever one or more entities are *hydrated* into an object. +Notice the count is 3. For this metric, it's not that there are only 3 *objects* +being hydrated during this request, but that our code asks Doctrine to hydrate one +or more objects on *three* occasions. + +So where are the 3 times that we're hydrating objects? One of the cool things is +that, when you hover over a timeline metric, it adds a border to the matching +boxes on the right. It's... a little subtle... but it does the trick. I wish +you could double-click and... maybe zoom to the matching boxes... but it's tricky +because they may be spread out over the whole request. + +If we hover over `doctrine.entities.hydrated`... hmm... I don't see those. You +need to do a little bit of digging... I'll hover back over. There they are. It +turns out that the 3 calls are *not* all in the same place: they're coming from +three very different *parts* of our code. The first is part of the firewall... +probably querying for the logged in user... and the other two are down in some +template rendering... along with a few similarly-colored `doctrine.dql.parsed` +items. + +I want to look at what's happening inside of this template... but a lot of +these things are *really* small. On top, we can see the entire timeline. Click +where we want to start, move over, and let go! Zoom! + +Much easier to see! In this spot, Doctrine parses its DQL, it makes an SQL query +here... and a different query a bit later. + +So as far as getting insight into what's *really* going on in your application, +you can't get much better than this. You can even see our N+1 problem visually: +it makes a query to count the comments little-by-little as the template renders. + +Hit the "Home" icon to zoom back out. This is cool... but I mentioned that, as soon +as we logged in, there was some *new* code that was now running in the background. +Next, let's look a bit closer at the timeline to discover what that is *and* +a hidden performance problem. diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index a0c653b..ba2e04a 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -6,7 +6,7 @@ use App\Entity\Comment; use App\Entity\User; use Doctrine\Bundle\FixturesBundle\Fixture; -use Doctrine\Persistence\ObjectManager; +use Doctrine\Common\Persistence\ObjectManager; use Faker\Factory; use Faker\Generator; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; diff --git a/src/Kernel.php b/src/Kernel.php index 655e796..785b0be 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -3,36 +3,51 @@ namespace App; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; -use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel as BaseKernel; -use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; +use Symfony\Component\Routing\RouteCollectionBuilder; class Kernel extends BaseKernel { use MicroKernelTrait; - protected function configureContainer(ContainerConfigurator $container): void + private const CONFIG_EXTS = '.{php,xml,yaml,yml}'; + + public function registerBundles(): iterable { - $container->import('../config/{packages}/*.yaml'); - $container->import('../config/{packages}/'.$this->environment.'/*.yaml'); - - if (is_file(\dirname(__DIR__).'/config/services.yaml')) { - $container->import('../config/services.yaml'); - $container->import('../config/{services}_'.$this->environment.'.yaml'); - } elseif (is_file($path = \dirname(__DIR__).'/config/services.php')) { - (require $path)($container->withPath($path), $this); + $contents = require $this->getProjectDir().'/config/bundles.php'; + foreach ($contents as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } } } - protected function configureRoutes(RoutingConfigurator $routes): void + public function getProjectDir(): string { - $routes->import('../config/{routes}/'.$this->environment.'/*.yaml'); - $routes->import('../config/{routes}/*.yaml'); + return \dirname(__DIR__); + } - if (is_file(\dirname(__DIR__).'/config/routes.yaml')) { - $routes->import('../config/routes.yaml'); - } elseif (is_file($path = \dirname(__DIR__).'/config/routes.php')) { - (require $path)($routes->withPath($path), $this); - } + protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void + { + $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php')); + $container->setParameter('container.dumper.inline_class_loader', true); + $confDir = $this->getProjectDir().'/config'; + + $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob'); + $loader->load($confDir.'/{packages}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob'); + $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); + $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); + } + + protected function configureRoutes(RouteCollectionBuilder $routes): void + { + $confDir = $this->getProjectDir().'/config'; + + $routes->import($confDir.'/{routes}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob'); + $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob'); + $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob'); } } diff --git a/src/Migrations/.gitignore b/src/Migrations/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/migrations/Version20190919165125.php b/src/Migrations/Version20190919165125.php similarity index 100% rename from migrations/Version20190919165125.php rename to src/Migrations/Version20190919165125.php diff --git a/migrations/Version20190919170420.php b/src/Migrations/Version20190919170420.php similarity index 100% rename from migrations/Version20190919170420.php rename to src/Migrations/Version20190919170420.php diff --git a/migrations/Version20190919170603.php b/src/Migrations/Version20190919170603.php similarity index 100% rename from migrations/Version20190919170603.php rename to src/Migrations/Version20190919170603.php diff --git a/migrations/Version20190919170845.php b/src/Migrations/Version20190919170845.php similarity index 100% rename from migrations/Version20190919170845.php rename to src/Migrations/Version20190919170845.php diff --git a/migrations/Version20190919171138.php b/src/Migrations/Version20190919171138.php similarity index 100% rename from migrations/Version20190919171138.php rename to src/Migrations/Version20190919171138.php diff --git a/migrations/Version20190919173458.php b/src/Migrations/Version20190919173458.php similarity index 100% rename from migrations/Version20190919173458.php rename to src/Migrations/Version20190919173458.php diff --git a/migrations/Version20190919174624.php b/src/Migrations/Version20190919174624.php similarity index 100% rename from migrations/Version20190919174624.php rename to src/Migrations/Version20190919174624.php diff --git a/migrations/Version20191010181756.php b/src/Migrations/Version20191010181756.php similarity index 100% rename from migrations/Version20191010181756.php rename to src/Migrations/Version20191010181756.php diff --git a/migrations/Version20191010184120.php b/src/Migrations/Version20191010184120.php similarity index 100% rename from migrations/Version20191010184120.php rename to src/Migrations/Version20191010184120.php diff --git a/src/Repository/BigFootSightingRepository.php b/src/Repository/BigFootSightingRepository.php index 6bb8be4..d0a075a 100644 --- a/src/Repository/BigFootSightingRepository.php +++ b/src/Repository/BigFootSightingRepository.php @@ -4,7 +4,7 @@ use App\Entity\BigFootSighting; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\ORM\QueryBuilder; /** diff --git a/symfony.lock b/symfony.lock index 753fc1d..8c9f13d 100644 --- a/symfony.lock +++ b/symfony.lock @@ -5,7 +5,7 @@ "repo": "github.com/symfony/recipes", "branch": "master", "version": "1.0", - "ref": "a2759dd6123694c8d901d0ec80006e044c2e6457" + "ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672" }, "files": [ "config/routes/annotations.yaml" @@ -27,12 +27,12 @@ "version": "v2.9.2" }, "doctrine/doctrine-bundle": { - "version": "2.0", + "version": "1.6", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "2.0", - "ref": "368794356c1fb634e58b38ad2addb36933f2e73e" + "version": "1.6", + "ref": "5e476e8edf3fa8e7f045ad034f89bb464424f5c1" }, "files": [ "config/packages/doctrine.yaml", @@ -50,23 +50,23 @@ "repo": "github.com/symfony/recipes", "branch": "master", "version": "3.0", - "ref": "e5b542d4ef47d8a003c91beb35650c76907f7e53" + "ref": "fc52d86631a6dfd9fdf3381d0b7e3df2069e51b3" }, "files": [ "src/DataFixtures/AppFixtures.php" ] }, "doctrine/doctrine-migrations-bundle": { - "version": "2.2", + "version": "1.2", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "2.2", - "ref": "baaa439e3e3179e69e3da84b671f0a3e4a2f56ad" + "version": "1.2", + "ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1" }, "files": [ "config/packages/doctrine_migrations.yaml", - "migrations/.gitignore" + "src/Migrations/.gitignore" ] }, "doctrine/event-manager": { @@ -93,9 +93,6 @@ "doctrine/reflection": { "version": "v1.0.0" }, - "doctrine/sql-formatter": { - "version": "1.1.1" - }, "easycorp/easy-log-handler": { "version": "1.0", "recipe": { @@ -108,24 +105,12 @@ "config/packages/dev/easy_log_handler.yaml" ] }, - "friendsofphp/proxy-manager-lts": { - "version": "v1.0.3" - }, "fzaninotto/faker": { "version": "v1.8.0" }, "jdorn/sql-formatter": { "version": "v1.2.17" }, - "laminas/laminas-code": { - "version": "3.4.1" - }, - "laminas/laminas-eventmanager": { - "version": "3.2.1" - }, - "laminas/laminas-zendframework-bridge": { - "version": "1.1.1" - }, "monolog/monolog": { "version": "1.25.1" }, @@ -153,9 +138,6 @@ "psr/container": { "version": "1.0.0" }, - "psr/event-dispatcher": { - "version": "1.0.0" - }, "psr/log": { "version": "1.1.0" }, @@ -187,15 +169,16 @@ "version": "v4.3.4" }, "symfony/console": { - "version": "5.1", + "version": "3.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "5.1", - "ref": "c6d02bdfba9da13c22157520e32a602dbee8a75c" + "version": "3.3", + "ref": "482d233eb8de91ebd042992077bbd5838858890c" }, "files": [ - "bin/console" + "bin/console", + "config/bootstrap.php" ] }, "symfony/css-selector": { @@ -222,9 +205,6 @@ "symfony/dependency-injection": { "version": "v4.3.4" }, - "symfony/deprecation-contracts": { - "version": "v2.2.0" - }, "symfony/doctrine-bridge": { "version": "v4.3.4" }, @@ -234,9 +214,6 @@ "symfony/dotenv": { "version": "v4.3.4" }, - "symfony/error-handler": { - "version": "v5.2.2" - }, "symfony/event-dispatcher": { "version": "v4.3.4" }, @@ -255,7 +232,7 @@ "repo": "github.com/symfony/recipes", "branch": "master", "version": "1.0", - "ref": "c0eeb50665f0f77226616b6038a9b06c03752d8e" + "ref": "dc3fc2e0334a4137c47cfd5a3ececc601fa61a0b" }, "files": [ ".env" @@ -265,19 +242,18 @@ "version": "v4.3.4" }, "symfony/framework-bundle": { - "version": "5.2", + "version": "4.2", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "5.2", - "ref": "6ec87563dcc85cd0c48856dcfbfc29610506d250" + "version": "4.2", + "ref": "61ad963f28c091b8bb9449507654b9c7d8bbb53c" }, "files": [ + "config/bootstrap.php", "config/packages/cache.yaml", "config/packages/framework.yaml", "config/packages/test/framework.yaml", - "config/preload.php", - "config/routes/dev/framework.yaml", "config/services.yaml", "public/index.php", "src/Controller/.gitignore", @@ -323,11 +299,10 @@ "repo": "github.com/symfony/recipes", "branch": "master", "version": "3.3", - "ref": "d7249f7d560f6736115eee1851d02a65826f0a56" + "ref": "6240c6d43e8237a32452f057f81816820fd56ab6" }, "files": [ "config/packages/dev/monolog.yaml", - "config/packages/prod/deprecations.yaml", "config/packages/prod/monolog.yaml", "config/packages/test/monolog.yaml" ] @@ -339,32 +314,27 @@ "version": "v1.0.6" }, "symfony/phpunit-bridge": { - "version": "5.1", + "version": "4.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "5.1", - "ref": "cb82a2355ec62fef0e7028ac969fe86fa722feb8" + "version": "4.3", + "ref": "b0582341f1df39aaf3a9a866cdbe49937da35984" }, "files": [ ".env.test", "bin/phpunit", + "config/bootstrap.php", "phpunit.xml.dist", - "tests/bootstrap.php" + "tests/.gitignore" ] }, - "symfony/polyfill-intl-grapheme": { - "version": "v1.22.0" - }, "symfony/polyfill-intl-icu": { "version": "v1.12.0" }, "symfony/polyfill-intl-idn": { "version": "v1.12.0" }, - "symfony/polyfill-intl-normalizer": { - "version": "v1.22.0" - }, "symfony/polyfill-mbstring": { "version": "v1.12.0" }, @@ -374,9 +344,6 @@ "symfony/polyfill-php73": { "version": "v1.12.0" }, - "symfony/polyfill-php80": { - "version": "v1.22.0" - }, "symfony/profiler-pack": { "version": "v1.0.4" }, @@ -387,26 +354,27 @@ "version": "v4.3.5" }, "symfony/routing": { - "version": "5.1", + "version": "4.2", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "5.1", - "ref": "b4f3e7c95e38b606eef467e8a42a8408fc460c43" + "version": "4.2", + "ref": "4c107a8d23a16b997178fbd4103b8d2f54f688b7" }, "files": [ - "config/packages/prod/routing.yaml", + "config/packages/dev/routing.yaml", "config/packages/routing.yaml", + "config/packages/test/routing.yaml", "config/routes.yaml" ] }, "symfony/security-bundle": { - "version": "5.1", + "version": "3.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "5.1", - "ref": "0a4bae19389d3b9cba1ca0102e3b2bccea724603" + "version": "3.3", + "ref": "e5a0228251d1dd2bca4c8ef918e14423c06db625" }, "files": [ "config/packages/security.yaml" @@ -436,9 +404,6 @@ "symfony/stopwatch": { "version": "v4.3.4" }, - "symfony/string": { - "version": "v5.2.2" - }, "symfony/test-pack": { "version": "v1.0.6" }, @@ -449,16 +414,16 @@ "version": "v4.3.4" }, "symfony/twig-bundle": { - "version": "5.0", + "version": "3.3", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "5.0", - "ref": "fab9149bbaa4d5eca054ed93f9e1b66cc500895d" + "version": "3.3", + "ref": "369b5b29dc52b2c190002825ae7ec24ab6f962dd" }, "files": [ - "config/packages/test/twig.yaml", "config/packages/twig.yaml", + "config/routes/dev/twig.yaml", "templates/base.html.twig" ] }, @@ -496,22 +461,18 @@ ] }, "symfony/webpack-encore-bundle": { - "version": "1.9", + "version": "1.0", "recipe": { "repo": "github.com/symfony/recipes", "branch": "master", - "version": "1.9", - "ref": "579d8de06df2ceb34d39e84e84c0c051b9b5ac68" + "version": "1.0", + "ref": "d3b160b9799c91cbfc3df6f018c795f3bf88698a" }, "files": [ - "assets/app.js", - "assets/bootstrap.js", - "assets/controllers.json", - "assets/controllers/hello_controller.js", - "assets/styles/app.css", + "assets/css/app.css", + "assets/js/app.js", "config/packages/assets.yaml", "config/packages/prod/webpack_encore.yaml", - "config/packages/test/webpack_encore.yaml", "config/packages/webpack_encore.yaml", "package.json", "webpack.config.js" diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 469dcce..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,11 +0,0 @@ -bootEnv(dirname(__DIR__).'/.env'); -} diff --git a/webpack.config.js b/webpack.config.js index 056b04a..4380052 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,4 @@ -const Encore = require('@symfony/webpack-encore'); +var Encore = require('@symfony/webpack-encore'); // Manually configure the runtime environment if not already configured yet by the "encore" command. // It's useful when you use tools that rely on webpack.config.js file. @@ -17,13 +17,15 @@ Encore /* * ENTRY CONFIG * + * Add 1 entry for each "page" of your app + * (including one that's included on every page - e.g. "app") + * * Each entry will result in one JavaScript file (e.g. app.js) * and one CSS file (e.g. app.css) if your JavaScript imports CSS. */ - .addEntry('app', './assets/app.js') - - // enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js) - .enableStimulusBridge('./assets/controllers.json') + .addEntry('app', './assets/js/app.js') + //.addEntry('page1', './assets/js/page1.js') + //.addEntry('page2', './assets/js/page2.js') // When enabled, Webpack "splits" your files into smaller pieces for greater optimization. .splitEntryChunks() @@ -45,14 +47,15 @@ Encore // enables hashed filenames (e.g. app.abc123.css) .enableVersioning(Encore.isProduction()) - .configureBabel((config) => { - config.plugins.push('@babel/plugin-proposal-class-properties'); + // enables @babel/preset-env polyfills + .configureBabel(() => {}, { + useBuiltIns: 'usage', + corejs: 3 }) - // enables @babel/preset-env polyfills - .configureBabelPresetEnv((config) => { - config.useBuiltIns = 'usage'; - config.corejs = 3; + .copyFiles({ + from: './assets/images', + to: 'images/[path][name].[hash:8].[ext]' }) // enables Sass/SCSS support @@ -61,15 +64,16 @@ Encore // uncomment if you use TypeScript //.enableTypeScriptLoader() - // uncomment if you use React - //.enableReactPreset() - // uncomment to get integrity="..." attributes on your script & link tags // requires WebpackEncoreBundle 1.4 or higher //.enableIntegrityHashes(Encore.isProduction()) // uncomment if you're having problems with a jQuery plugin //.autoProvidejQuery() + + // uncomment if you use API Platform Admin (composer req api-admin) + //.enableReactPreset() + //.addEntry('admin', './assets/js/admin.js') ; module.exports = Encore.getWebpackConfig();