├── .gitignore ├── src ├── Scraper │ ├── Tracking.php │ ├── SimpleHtml.php │ ├── OpenGraph.php │ ├── SocialNetworkProfile.php │ ├── ArticleInfo.php │ └── SiteInfo.php ├── Event │ ├── PageScrapeEvent.php │ └── DataFilterEvent.php └── UrlPreview.php ├── tests ├── PopularSitesTest.php ├── MediaSitesTest.php ├── EcommerceTest.php ├── SocialSitesTest.php └── NewsAndArticlesTest.php ├── composer.json ├── LICENSE ├── example.php ├── CONTRIBUTING.md ├── readme.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /src/Scraper/Tracking.php: -------------------------------------------------------------------------------- 1 | getCrawler(); 13 | 14 | if (count($crawler->filter('title'))) { 15 | $html = $crawler->html(); 16 | $site = [ 17 | 'tracking' => [] 18 | ]; 19 | 20 | // Check for Google Analytics code 21 | if (stripos($html, 'google-analytics') !== false) { 22 | $site['tracking'][] = 'Google Analytics'; 23 | } 24 | 25 | // Check for Piwik 26 | if (stripos($html, 'piwik') !== false) { 27 | $site['tracking'][] = 'Piwik'; 28 | } 29 | 30 | // pass along the scraped info 31 | $event->addData('site', $site); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /tests/PopularSitesTest.php: -------------------------------------------------------------------------------- 1 | urlPreview = new UrlPreview; 12 | } 13 | 14 | public function testGithubPageAfterRedirect(): void { 15 | 16 | $fb = $this->urlPreview->loadUrl('https://layered.dev'); 17 | $data = $fb->getAll(); 18 | 19 | // site details 20 | $this->assertEquals('https://github.com', $data['site']['url']); 21 | $this->assertEquals('GitHub', $data['site']['name']); 22 | 23 | // page details 24 | $this->assertEquals('profile', $data['page']['type']); 25 | $this->assertEquals('https://github.com/LayeredStudio', $data['page']['url']); 26 | $this->assertStringContainsString('Layered', $data['page']['title']); 27 | $this->assertArrayHasKey('url', $data['page']['image']); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/Event/PageScrapeEvent.php: -------------------------------------------------------------------------------- 1 | data = $data; 18 | $this->crawler = $crawler; 19 | } 20 | 21 | public function setData(string $section, array $data) { 22 | $this->data[$section] = $data; 23 | } 24 | 25 | public function addData(string $section, array $data) { 26 | $this->data[$section] = array_merge($this->data[$section], array_filter($data)); 27 | } 28 | 29 | public function getData(): array { 30 | return $this->data; 31 | } 32 | 33 | public function getCrawler(): Crawler { 34 | return $this->crawler; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/Event/DataFilterEvent.php: -------------------------------------------------------------------------------- 1 | data = $data; 19 | $this->section = $section; 20 | $this->crawler = $crawler; 21 | } 22 | 23 | public function setData(array $data) { 24 | $this->data = $data; 25 | } 26 | 27 | public function addData(array $data) { 28 | $this->data = array_merge($this->data, array_filter($data)); 29 | } 30 | 31 | public function getData(): array { 32 | return $this->data; 33 | } 34 | 35 | public function getSection(): string { 36 | return $this->section; 37 | } 38 | 39 | public function getCrawler(): Crawler { 40 | return $this->crawler; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layered/page-meta", 3 | "description": "Get detailed info for any URL on the internet! Scraper for HTML, OpenGraph, Schema data", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": ["scraper", "url-preview", "link-preview", "opengraph", "embed", "oembed", "schema"], 7 | "authors": [ 8 | { 9 | "name": "Andrei Igna", 10 | "email": "andrei@laye.red" 11 | } 12 | ], 13 | "require": { 14 | "fabpot/goutte": "~4.0", 15 | "symfony/event-dispatcher": "~5.1" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Layered\\PageMeta\\": "src/" 20 | } 21 | }, 22 | "scripts": { 23 | "test": "phpunit tests", 24 | "check-style": "phpcs -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src", 25 | "fix-style": "phpcbf -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^9.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Layered 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | '; 8 | 9 | $urls = [ 10 | 'https://www.instagram.com/p/BdAVIhHD5sR/', 11 | 'https://store.google.com', 12 | 'https://github.com', 13 | 'https://www.youtube.com/watch?v=D_eZxSYRhco', 14 | 'https://twitter.com/SpaceX/status/960910705808609280', 15 | 'https://www.wired.com/story/mind-games-the-tortured-lives-of-targeted-individuals/', 16 | 'https://www.nytimes.com/2018/03/06/automobiles/autoshow/european-automakers-electric-cars-geneva.html', 17 | 'https://www.theverge.com/2017/10/31/16579748/apple-iphone-x-review', 18 | 'https://en.blog.wordpress.com/2018/02/02/reader-conversations/', 19 | 'https://medium.com/@ev/welcome-to-medium-9e53ca408c48', 20 | 'https://dribbble.com/shots/4165767-Notifications-for-iPhone-and-iPad', 21 | 'http://amzn.eu/3tbEF8j', 22 | 'https://www.ebay.es/itm/Bicicleta-FAT-Bike-cambio-Shimano/112562360485', 23 | 'https://www.facebook.com/KeyAndPeele/photos/rpp.256107687771505/852998691415732/?type=3&theater', 24 | 'https://www.reddit.com/r/funny/comments/82ckwf/pic_of_two_plump_pigeons_perched_on_the_ledge_but/', 25 | 'https://www.netflix.com/title/80014749' 26 | ]; 27 | 28 | $previewer = new UrlPreview; 29 | 30 | foreach ($urls as $index => $url) { 31 | print_r($previewer->loadUrl($url)->getAll()); 32 | if ($index >= 15) break; 33 | } 34 | -------------------------------------------------------------------------------- /tests/MediaSitesTest.php: -------------------------------------------------------------------------------- 1 | urlPreview = new UrlPreview; 12 | } 13 | 14 | public function testYoutubeVideo(): void { 15 | 16 | $fb = $this->urlPreview->loadUrl('https://www.youtube.com/watch?v=L3pk_TBkihU'); 17 | $data = $fb->getAll(); 18 | 19 | // site details 20 | $this->assertEquals('https://www.youtube.com', $data['site']['url']); 21 | $this->assertEquals('YouTube', $data['site']['name']); 22 | 23 | // video details 24 | $this->assertEquals('video', $data['page']['type']); 25 | $this->assertStringContainsString('TENET', $data['page']['title']); 26 | $this->assertArrayHasKey('url', $data['page']['image']); 27 | $this->assertArrayHasKey('video', $data['page']); 28 | } 29 | 30 | public function testNetflix(): void { 31 | 32 | $fb = $this->urlPreview->loadUrl('https://www.netflix.com/title/80014749'); 33 | $data = $fb->getAll(); 34 | 35 | // site details 36 | $this->assertEquals('https://www.netflix.com', $data['site']['url']); 37 | $this->assertEquals('Netflix', $data['site']['name']); 38 | 39 | // video details 40 | $this->assertEquals('website', $data['page']['type']); 41 | $this->assertStringContainsString('Rick and Morty', $data['page']['title']); 42 | $this->assertArrayHasKey('url', $data['page']['image']); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/LayeredStudio/url-preview). 6 | 7 | Please check [Issues](https://github.com/LayeredStudio/url-preview/issues) and [Pull Requests](https://github.com/LayeredStudio/url-preview/pulls), your patch/fix may be already in progress. 8 | 9 | ## Pull Requests 10 | 11 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``. 12 | 13 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 14 | 15 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 16 | 17 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 18 | 19 | - **Create feature branches** - Don't ask us to pull from your master branch. 20 | 21 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 22 | 23 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 24 | 25 | 26 | ## Running Tests 27 | 28 | ``` bash 29 | $ composer test 30 | ``` 31 | 32 | 33 | **Happy coding**! 34 | -------------------------------------------------------------------------------- /tests/EcommerceTest.php: -------------------------------------------------------------------------------- 1 | urlPreview = new UrlPreview; 12 | } 13 | 14 | public function testAmazonProduct(): void { 15 | 16 | $fb = $this->urlPreview->loadUrl('https://www.amazon.co.uk/dp/B0748CLMD5/'); 17 | $data = $fb->getAll(); 18 | 19 | // site details 20 | $this->assertEquals('https://www.amazon.co.uk', $data['site']['url']); 21 | $this->assertEquals('Amazon', $data['site']['name']); 22 | 23 | // product details 24 | $this->assertEquals('product', $data['page']['type']); 25 | $this->assertStringContainsString('Moon', $data['page']['title']); 26 | $this->assertArrayHasKey('url', $data['page']['image']); 27 | 28 | // seller details (author) 29 | $this->assertArrayHasKey('name', $data['author']); 30 | $this->assertArrayHasKey('url', $data['author']); 31 | } 32 | 33 | public function testGoogleStore(): void { 34 | 35 | $fb = $this->urlPreview->loadUrl('https://store.google.com/us/'); 36 | $data = $fb->getAll(); 37 | 38 | // site details 39 | $this->assertEquals('https://store.google.com', $data['site']['url']); 40 | $this->assertEquals('Google Store', $data['site']['name']); 41 | 42 | // page details 43 | $this->assertEquals('website', $data['page']['type']); 44 | $this->assertArrayHasKey('url', $data['page']['image']); 45 | } 46 | 47 | public function testGoogleStoreProduct(): void { 48 | 49 | $fb = $this->urlPreview->loadUrl('https://store.google.com/us/product/google_nest_hub_max'); 50 | $data = $fb->getAll(); 51 | 52 | // site details 53 | $this->assertEquals('https://store.google.com', $data['site']['url']); 54 | $this->assertEquals('Google Store', $data['site']['name']); 55 | 56 | // product details 57 | $this->assertEquals('website', $data['page']['type']); 58 | $this->assertStringContainsString('Nest Hub Max', $data['page']['title']); 59 | $this->assertArrayHasKey('url', $data['page']['image']); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/Scraper/SimpleHtml.php: -------------------------------------------------------------------------------- 1 | getCrawler(); 13 | 14 | if (count($crawler->filter('title'))) { 15 | $site = [ 16 | 'icon' => [], 17 | 'language' => current(explode('-', trim($crawler->filter('html')->attr('lang')))), 18 | 'author' => '', 19 | 'generator' => '', 20 | 'theme-color' => '' 21 | ]; 22 | 23 | $page = [ 24 | 'url' => $crawler->getUri(), 25 | 'medium' => '', 26 | 'title' => trim($crawler->filter('title')->text()), 27 | 'description' => '', 28 | 'keywords' => '' 29 | ]; 30 | 31 | $extra = []; 32 | 33 | $crawler->filter('meta[name]')->each(function($node) use(&$site, &$page, &$extra) { 34 | $metaName = strtolower($node->attr('name')); 35 | $content = trim($node->attr('content')); 36 | 37 | if (isset($site[$metaName]) && !empty($content)) { 38 | $site[$metaName] = $content; 39 | } else if (isset($page[$metaName]) && !empty($content)) { 40 | $page[$metaName] = $content; 41 | } else { 42 | $extra[$metaName] = $content; 43 | } 44 | }); 45 | 46 | // check for site icon 47 | $crawler->filter('link[rel=apple-touch-icon], link[rel~=icon]')->each(function($node) use(&$site) { 48 | if (!empty($node->attr('href'))) { 49 | $site['icon'][$node->attr('rel')] = $node->attr('href'); 50 | } 51 | }); 52 | 53 | // get the best quality image as icon 54 | ksort($site['icon']); 55 | $site['icon'] = $site['icon'] ? current($site['icon']) : '/favicon.ico'; 56 | $site['icon'] = \Layered\Pagemeta\UrlPreview::makeAbsoluteUri($crawler->getUri(), $site['icon']); 57 | 58 | // rename 'medium' to 'type' - consistent with OpenGraph field name 59 | $page['type'] = $page['medium']; 60 | unset($page['medium']); 61 | 62 | // basic check for responsiveness 63 | $site['responsive'] = !!count($crawler->filter('meta[name="viewport"]')); 64 | 65 | // pass along the scraped info 66 | $event->addData('site', $site); 67 | $event->addData('page', $page); 68 | $event->addData('extra', $extra); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/Scraper/OpenGraph.php: -------------------------------------------------------------------------------- 1 | 'video', 14 | 'instapp:photo' => 'photo', 15 | 'ebay-objects:item' => 'product', 16 | 'ebay-objects:ecommerce' => 'website', 17 | 'medium-com:collection' => 'website' 18 | ]; 19 | 20 | public static function scrape(Event $event) { 21 | $crawler = $event->getCrawler(); 22 | 23 | if (count($crawler->filter('meta[property^="og:"]'))) { 24 | $data = []; 25 | $site = [ 26 | 'site_name' => '' 27 | ]; 28 | $page = [ 29 | 'url' => $crawler->getUri(), 30 | 'type' => '', 31 | 'title' => '', 32 | 'description' => '', 33 | 'image' => '', 34 | 'video' => '' 35 | ]; 36 | $extra = []; 37 | 38 | $crawler->filter('meta[property^="og:"]')->each(function($node) use(&$data) { 39 | $property = substr($node->attr('property'), 3); 40 | $content = trim($node->attr('content')); 41 | 42 | if (strpos($property, ':') !== false) { 43 | $property = explode(':', $property, 2); 44 | 45 | if (!isset($data[$property[0]])) { 46 | $data[$property[0]] = []; 47 | } elseif (isset($data[$property[0]]) && !is_array($data[$property[0]])) { 48 | $data[$property[0]] = [ 49 | self::guessFieldType($data[$property[0]]) => $data[$property[0]] 50 | ]; 51 | } 52 | 53 | $data[$property[0]][$property[1]] = $content; 54 | 55 | } else { 56 | $data[$property] = $content; 57 | } 58 | }); 59 | 60 | foreach ($data as $key => $value) { 61 | if (isset($site[$key])) { 62 | $site[$key] = $value; 63 | } else if (isset($page[$key])) { 64 | $page[$key] = $value; 65 | } else { 66 | $extra[$key] = $value; 67 | } 68 | } 69 | 70 | // rename 'site_name' to 'name' 71 | $site['name'] = $site['site_name']; 72 | unset($site['site_name']); 73 | 74 | $page['type'] = self::$pageTypes[$page['type']] ?? $page['type']; 75 | 76 | if (isset($extra['locale'])) { 77 | $site['language'] = current(explode('_', $extra['locale'])); 78 | } 79 | 80 | // pass along the scraped info 81 | $event->addData('site', $site); 82 | $event->addData('page', $page); 83 | $event->addData('extra', $extra); 84 | } 85 | } 86 | 87 | protected static function guessFieldType($string) { 88 | $type = 'text'; 89 | 90 | if (filter_var($string, FILTER_VALIDATE_URL)) { 91 | $type = 'url'; 92 | } elseif (filter_var($string, FILTER_VALIDATE_EMAIL)) { 93 | $type = 'email'; 94 | } 95 | 96 | return $type; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /tests/SocialSitesTest.php: -------------------------------------------------------------------------------- 1 | urlPreview = new UrlPreview; 12 | } 13 | 14 | public function testInstagramPhoto(): void { 15 | 16 | $instagram = $this->urlPreview->loadUrl('https://www.instagram.com/p/CCOQMUdJK3e/'); 17 | $data = $instagram->getAll(); 18 | 19 | $this->assertEquals('Instagram', $data['site']['name']); 20 | $this->assertEquals('photo', $data['page']['type']); 21 | $this->assertEquals('NextRace', $data['author']['name']); 22 | } 23 | 24 | public function testInstagramProfile(): void { 25 | 26 | $instagram = $this->urlPreview->loadUrl('https://www.instagram.com/nextraceapp/'); 27 | $data = $instagram->getAll(); 28 | 29 | $this->assertEquals('Instagram', $data['site']['name']); 30 | $this->assertEquals('profile', $data['page']['type']); 31 | $this->assertEquals('NextRace', $data['author']['name']); 32 | $this->assertEquals('@nextraceapp', $data['author']['handle']); 33 | } 34 | 35 | public function testFacebookImage(): void { 36 | 37 | $fb = $this->urlPreview->loadUrl('https://www.facebook.com/KeyAndPeele/photos/rpp.256107687771505/852998691415732/?type=3&theater'); 38 | $data = $fb->getAll(); 39 | 40 | $this->assertEquals('Facebook', $data['site']['name']); 41 | // TODO add test for image 42 | } 43 | 44 | public function testFacebookPage(): void { 45 | 46 | $fb = $this->urlPreview->loadUrl('https://www.facebook.com/instagram/'); 47 | $data = $fb->getAll(); 48 | 49 | $this->assertEquals('Facebook', $data['site']['name']); 50 | $this->assertEquals('Instagram', $data['page']['title']); 51 | $this->assertEquals('https://www.facebook.com/instagram/', $data['page']['url']); 52 | } 53 | 54 | public function testFacebookPagePost(): void { 55 | 56 | $fb = $this->urlPreview->loadUrl('https://www.facebook.com/Microsoft/posts/10157885048368721'); 57 | $data = $fb->getAll(); 58 | 59 | $this->assertEquals('Facebook', $data['site']['name']); 60 | $this->assertEquals('Microsoft', $data['page']['title']); 61 | $this->assertEquals('website', $data['page']['type']); 62 | $this->assertEquals('https://www.facebook.com/Microsoft/posts/10157885048368721', $data['page']['url']); 63 | } 64 | 65 | public function testRedditImage(): void { 66 | 67 | $fb = $this->urlPreview->loadUrl('https://www.reddit.com/r/funny/comments/82ckwf/pic_of_two_plump_pigeons_perched_on_the_ledge_but/'); 68 | $data = $fb->getAll(); 69 | 70 | $this->assertEquals('reddit', $data['site']['name']); 71 | $this->assertEquals('image', $data['page']['type']); 72 | $this->assertStringStartsWith('r/funny', $data['page']['title']); 73 | $this->assertArrayHasKey('url', $data['page']['image']); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/Scraper/SocialNetworkProfile.php: -------------------------------------------------------------------------------- 1 | getCrawler(); 13 | $data = $event->getData(); 14 | 15 | $site = []; 16 | $page = []; 17 | $profile = []; 18 | 19 | if (preg_match('/^https:\/\/twitter.com\/([A-Za-z0-9_]{1,15})[\/]?$/', $crawler->getUri())) { 20 | preg_match('/([\s\S]+) \(@([A-Za-z0-9_]{1,15})\) | Twitter/', $data['page']['title'], $titleMatches); 21 | 22 | $site['site_name'] = 'Twitter'; 23 | $page['type'] = 'profile'; 24 | $profile = [ 25 | 'name' => $titleMatches[1], 26 | 'handle' => '@' . $titleMatches[2], 27 | 'url' => $crawler->getUri() 28 | ]; 29 | 30 | $jsonData = $crawler->filter('#init-data')->attr('value'); 31 | 32 | if ($jsonData) { 33 | $jsonData = json_decode($jsonData, true); 34 | 35 | $page['description'] = $jsonData['profile_user']['description']; 36 | $page['image'] = [ 37 | 'url' => str_replace('normal', '400x400', $jsonData['profile_user']['profile_image_url_https']), 38 | 'width' => 400, 39 | 'height' => 400 40 | ]; 41 | } 42 | 43 | } elseif (strpos($crawler->getUri(), 'twitter.com') !== false && $data['page']['type'] == 'article') { 44 | preg_match('/twitter.com\/([A-Za-z0-9_]{1,15})\/status/', $crawler->getUri(), $urlMatches); 45 | preg_match('/^([\s\S]+) on Twitter$/', $data['page']['title'], $titleMatches); 46 | 47 | $page['date'] = date(DATE_ATOM, $crawler->filter('.time > a > span')->attr('data-time')); 48 | $page['description'] = trim($data['page']['description'], '“”'); 49 | $profile = [ 50 | 'name' => $titleMatches[1], 51 | 'handle' => '@' . $urlMatches[1], 52 | 'url' => 'https://twitter.com/' . $urlMatches[1] 53 | ]; 54 | } elseif (strpos($crawler->getUri(), 'instagram.com') !== false && $data['page']['type'] == 'profile') { 55 | $title = explode('•', $data['page']['title']); 56 | $name = explode('(', $title[0]); 57 | 58 | $profile = [ 59 | 'name' => trim($name[0]), 60 | 'handle' => trim(str_replace(')', '', $name[1])), 61 | 'url' => $data['page']['url'] 62 | ]; 63 | } elseif (strpos($crawler->getUri(), 'instagram.com') !== false && in_array($data['page']['type'], ['photo', 'video'])) { 64 | preg_match('/ - ([\s\S]+) \(@([a-zA-Z0-9._]+)\) on Instagram: “([\s\S]+)”/', $data['page']['description'], $matches); 65 | 66 | $page['title'] = current(explode(':', $data['page']['title'])); 67 | $page['description'] = $matches[3]; 68 | $profile = [ 69 | 'name' => $matches[1], 70 | 'handle' => '@' . $matches[2], 71 | 'url' => 'https://www.instagram.com/' . $matches[2] . '/' 72 | ]; 73 | } 74 | 75 | $event->addData('site', $site); 76 | $event->addData('page', $page); 77 | $event->addData('author', $profile); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/Scraper/ArticleInfo.php: -------------------------------------------------------------------------------- 1 | getCrawler(); 13 | $page = [ 14 | 'date' => '' 15 | ]; 16 | 17 | $profile = [ 18 | 'name' => '', 19 | 'url' => '' 20 | ]; 21 | 22 | if (count($crawler->filter('title'))) { 23 | 24 | // Extract Author name 25 | $crawler->filter('meta[property=author], meta[name="parsely-author"], meta[property="sailthru.author"]')->each(function($node) use(&$profile) { 26 | $profile['name'] = $node->attr('content'); 27 | }); 28 | 29 | // Extract Author profile URL 30 | $crawler->filter('meta[property="article:author"]')->each(function($node) use(&$profile) { 31 | $profile['url'] = $node->attr('content'); 32 | }); 33 | 34 | if (!$profile['name']) { 35 | $authorName = $crawler->filter('.post-author-name, .entry .author, [class*="byline"], [id*="byline"]'); 36 | if (count($authorName)) { 37 | 38 | // does it have an link inside? 39 | $hasLink = $authorName->filter('a'); 40 | if ($authorName->nodeName() === 'a') { 41 | $profile['name'] = $authorName->text(); 42 | $profile['url'] = $authorName->attr('href'); 43 | } elseif (count($hasLink)) { 44 | $profile['name'] = $hasLink->text(); 45 | $profile['url'] = $hasLink->attr('href'); 46 | } else { 47 | $profile['name'] = $authorName->text(); 48 | } 49 | } 50 | } 51 | 52 | $profile['name'] = trim($profile['name']); 53 | 54 | if (!$profile['url']) { 55 | $authorLinks = $crawler->filter('a[href*="/author"]'); 56 | if (count($authorLinks)) { 57 | $profile['url'] = $authorLinks->attr('href'); 58 | } 59 | } 60 | 61 | if ($profile['url']) { 62 | $profile['url'] = \Layered\PageMeta\UrlPreview::makeAbsoluteUri($event->getCrawler()->getUri(), $profile['url']); 63 | } 64 | 65 | // Extract article date 66 | $crawler->filter('meta[property="article:published"], meta[property="article:published_time"], meta[name="iso-8601-publish-date"], meta[name="parsely-pub-date"], meta[property="sailthru.date"]')->each(function($node) use(&$page) { 67 | $page['date'] = date(DATE_ATOM, strtotime($node->attr('content'))); 68 | }); 69 | 70 | if (!$page['date']) { 71 | $dateElement = $crawler->filter('.entry time[datetime]'); 72 | if (count($dateElement)) { 73 | $page['date'] = $dateElement->attr('datetime'); 74 | } 75 | } 76 | } 77 | 78 | if (strpos($crawler->getUri(), 'nytimes.com') !== false) { 79 | $siteData = $event->getData()['site']; 80 | $profile['name'] = $siteData['author']; 81 | $siteData['author'] = ''; 82 | $event->setData('site', $siteData); 83 | } 84 | 85 | // pass along the scraped info 86 | $event->addData('page', $page); 87 | $event->addData('author', $profile); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /tests/NewsAndArticlesTest.php: -------------------------------------------------------------------------------- 1 | urlPreview = new UrlPreview; 12 | } 13 | 14 | public function testWiredArticle(): void { 15 | 16 | $fb = $this->urlPreview->loadUrl('https://www.wired.com/story/best-sonos-speakers-buying-guide/'); 17 | $data = $fb->getAll(); 18 | 19 | // site details 20 | $this->assertEquals('https://www.wired.com', $data['site']['url']); 21 | $this->assertEquals('Wired', $data['site']['name']); 22 | 23 | // article details 24 | $this->assertEquals('article', $data['page']['type']); 25 | $this->assertStringContainsString('Sonos', $data['page']['title']); 26 | $this->assertArrayHasKey('url', $data['page']['image']); 27 | 28 | // author details 29 | $this->assertArrayHasKey('name', $data['author']); 30 | $this->assertEquals('https://www.wired.com/contributor/jeffrey-van-camp', $data['author']['url']); 31 | } 32 | 33 | public function testNYTimesArticle(): void { 34 | 35 | $fb = $this->urlPreview->loadUrl('https://www.nytimes.com/2020/09/26/technology/ebay-cockroaches-stalking-scandal.html'); 36 | $data = $fb->getAll(); 37 | 38 | // site details 39 | $this->assertEquals('https://www.nytimes.com', $data['site']['url']); 40 | $this->assertEquals('NYTimes', $data['site']['name']); 41 | 42 | // article details 43 | $this->assertEquals('article', $data['page']['type']); 44 | $this->assertStringContainsString('eBay', $data['page']['title']); 45 | $this->assertArrayHasKey('url', $data['page']['image']); 46 | } 47 | 48 | public function testVergeArticle(): void { 49 | 50 | $fb = $this->urlPreview->loadUrl('https://www.theverge.com/2017/10/31/16579748/apple-iphone-x-review'); 51 | $data = $fb->getAll(); 52 | 53 | // site details 54 | $this->assertEquals('https://www.theverge.com', $data['site']['url']); 55 | $this->assertEquals('The Verge', $data['site']['name']); 56 | 57 | // article details 58 | $this->assertEquals('article', $data['page']['type']); 59 | $this->assertStringContainsString('iPhone', $data['page']['title']); 60 | $this->assertArrayHasKey('url', $data['page']['image']); 61 | 62 | // author details 63 | $this->assertArrayHasKey(['url', 'name'], $data['author']); 64 | $this->assertEquals('Nilay Patel', $data['author']['name']); 65 | } 66 | 67 | public function testWordpressArticle(): void { 68 | 69 | $fb = $this->urlPreview->loadUrl('https://en.blog.wordpress.com/2018/02/02/reader-conversations/'); 70 | $data = $fb->getAll(); 71 | 72 | // site details 73 | $this->assertEquals('https://wordpress.com', $data['site']['url']); 74 | $this->assertEquals('The WordPress.com Blog', $data['site']['name']); 75 | 76 | // article details 77 | $this->assertEquals('article', $data['page']['type']); 78 | $this->assertStringContainsString('Conversations', $data['page']['title']); 79 | $this->assertArrayHasKey('url', $data['page']['image']); 80 | 81 | // author details 82 | $this->assertStringContainsString('Jan', $data['author']['name']); 83 | } 84 | 85 | public function testMediumArticle(): void { 86 | 87 | $fb = $this->urlPreview->loadUrl('https://medium.com/@ev/welcome-to-medium-9e53ca408c48'); 88 | $data = $fb->getAll(); 89 | 90 | // site details 91 | $this->assertEquals('https://ev.medium.com', $data['site']['url']); 92 | $this->assertEquals('Medium', $data['site']['name']); 93 | 94 | // article details 95 | $this->assertEquals('article', $data['page']['type']); 96 | $this->assertStringContainsString('Welcome', $data['page']['title']); 97 | $this->assertArrayHasKey('url', $data['page']['image']); 98 | 99 | // author details 100 | $this->assertEquals('Ev Williams', $data['author']['name']); 101 | $this->assertEquals('https://medium.com/@ev', $data['author']['url']); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/Scraper/SiteInfo.php: -------------------------------------------------------------------------------- 1 | 'NYTimes', 13 | 'ebay.com' => 'eBay', 14 | 'ebay.es' => 'eBay', 15 | 'ebay.co.uk' => 'eBay', 16 | 'amazon.com' => 'Amazon', 17 | 'amazon.ca' => 'Amazon', 18 | 'amazon.co.uk' => 'Amazon', 19 | 'amazon.es' => 'Amazon', 20 | 'amazon.de' => 'Amazon', 21 | 'amazon.fr' => 'Amazon', 22 | 'amazon.it' => 'Amazon', 23 | 'facebook.com' => 'Facebook', 24 | 'netflix.com' => 'Netflix', 25 | 'dribbble.com' => 'Dribbble', 26 | 'medium.com' => 'Medium', 27 | 'twitter.com' => 'Twitter', 28 | 'youtube.com' => 'YouTube', 29 | 'instagram.com' => 'Instagram', 30 | 'trello.com' => 'Trello' 31 | ]; 32 | 33 | public static function addSiteNames(Event $event) { 34 | $crawler = $event->getCrawler(); 35 | $data = $event->getData(); 36 | 37 | if ($event->getSection() == 'site' && !$data['name']) { 38 | $host = str_replace('www.', '', parse_url($data['url'], PHP_URL_HOST)); 39 | if (isset(self::$siteNames[$host])) { 40 | $data['name'] = self::$siteNames[$host]; 41 | $event->setData($data); 42 | } 43 | } 44 | } 45 | 46 | public static function siteNameFromHtml(Event $event) { 47 | $crawler = $event->getCrawler(); 48 | $data = $event->getData(); 49 | $parsedUrl = parse_url($crawler->getUri()); 50 | 51 | if (empty($data['site']['name']) && !isset($parsedUrl['query']) && !isset($parsedUrl['path'])) { 52 | $event->addData('site', [ 53 | 'name' => $data['page']['title'] 54 | ]); 55 | } 56 | } 57 | 58 | public static function mediaUrlToArray(Event $event) { 59 | $data = $event->getData(); 60 | 61 | foreach (['image', 'video'] as $field) { 62 | if (isset($data[$field]) && is_string($data[$field])) { 63 | $data[$field] = [ 64 | 'url' => \Layered\PageMeta\UrlPreview::makeAbsoluteUri($event->getCrawler()->getUri(), $data[$field]) 65 | ]; 66 | $event->setData($data); 67 | } 68 | } 69 | } 70 | 71 | public static function ecommerceSites(Event $event) { 72 | $data = $event->getData(); 73 | $crawler = $event->getCrawler(); 74 | 75 | if (strpos($crawler->getUri(), 'amazon.') !== false) { 76 | $imageUrl = $crawler->filter('.a-dynamic-image'); 77 | if (count($imageUrl)) { 78 | $event->addData('page', [ 79 | 'type' => 'product', 80 | 'image' => $imageUrl->attr('data-old-hires') 81 | ]); 82 | } 83 | } elseif (strpos($crawler->getUri(), 'ebay.') !== false) { 84 | $sellerLink = $crawler->filter('a[href*="/usr"]'); 85 | if (count($sellerLink)) { 86 | $event->addData('author', [ 87 | 'name' => $sellerLink->text(), 88 | 'url' => $sellerLink->attr('href') 89 | ]); 90 | } 91 | } 92 | } 93 | 94 | public static function appLinks(Event $event) { 95 | $crawler = $event->getCrawler(); 96 | $appLinks = []; 97 | 98 | $crawler->filter('meta[property^="al:"]')->each(function($node) use(&$appLinks) { 99 | $property = substr($node->attr('property'), 3); 100 | $content = trim($node->attr('content')); 101 | 102 | if (strpos($property, ':') !== false) { 103 | $property = explode(':', $property, 2); 104 | 105 | if (!isset($appLinks[$property[0]])) { 106 | $appLinks[$property[0]] = []; 107 | } 108 | 109 | $appLinks[$property[0]][$property[1]] = $content; 110 | } else { 111 | $appLinks[$property] = $content; 112 | } 113 | }); 114 | 115 | foreach ($appLinks as $platform => $value) { 116 | if (in_array($platform, ['ios', 'ipad', 'iphone'])) { 117 | $appLinks[$platform]['store_url'] = 'https://itunes.apple.com/us/app/' . $appLinks[$platform]['app_name'] . '/id' . $appLinks[$platform]['app_store_id']; 118 | } 119 | if ($platform === 'android') { 120 | $appLinks['android']['store_url'] = 'https://play.google.com/store/apps/details?id=' . $appLinks['android']['package']; 121 | } 122 | } 123 | 124 | $event->setData('app_links', $appLinks); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/UrlPreview.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class UrlPreview { 16 | 17 | private $eventDispatcher; 18 | private $headers = [ 19 | 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9', 20 | 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; PageMeta/1.0; +https://layered.dev)' 21 | ]; 22 | 23 | protected $crawler; 24 | protected $url; 25 | protected $data; 26 | 27 | public function __construct(array $headers = []) { 28 | $this->eventDispatcher = new EventDispatcher(); 29 | $this->goutteClient = new Client(); 30 | $this->goutteClient->setServerParameters(array_merge($this->headers, $headers)); 31 | 32 | // Scrape data from common HTML tags 33 | $this->addListener('page.scrape', ['\Layered\PageMeta\Scraper\SimpleHtml', 'scrape']); 34 | $this->addListener('page.scrape', ['\Layered\PageMeta\Scraper\OpenGraph', 'scrape']); 35 | 36 | // Site specific data scrape 37 | $this->addListener('page.scrape', ['\Layered\PageMeta\Scraper\ArticleInfo', 'scrape']); 38 | $this->addListener('page.scrape', ['\Layered\PageMeta\Scraper\SocialNetworkProfile', 'getProfiles']); 39 | $this->addListener('page.scrape', ['\Layered\PageMeta\Scraper\SiteInfo', 'ecommerceSites']); 40 | $this->addListener('page.scrape', ['\Layered\PageMeta\Scraper\SiteInfo', 'appLinks']); 41 | 42 | // Filter data to a consistent format across sites 43 | $this->addListener('data.filter', ['\Layered\PageMeta\Scraper\SiteInfo', 'addSiteNames']); 44 | $this->addListener('page.scrape', ['\Layered\PageMeta\Scraper\SiteInfo', 'siteNameFromHtml']); 45 | $this->addListener('data.filter', ['\Layered\PageMeta\Scraper\SiteInfo', 'mediaUrlToArray']); 46 | 47 | return $this; 48 | } 49 | 50 | public function loadUrl(string $url): self { 51 | $this->data = [ 52 | 'site' => [ 53 | 'url' => $url, 54 | 'name' => '', 55 | 'secure' => false, 56 | 'responsive' => false, 57 | 'author' => '', 58 | 'generator' => '', 59 | 'icon' => '', 60 | 'language' => '' 61 | ], 62 | 'page' => [ 63 | 'type' => 'website' 64 | ], 65 | 'author' => [], 66 | 'app_links' => [], 67 | 'extra' => [] 68 | ]; 69 | 70 | if (!filter_var($url, FILTER_VALIDATE_URL)) { 71 | throw new \Exception('Invalid URL'); 72 | } 73 | 74 | // load content from URL 75 | $this->url = $url; 76 | $this->crawler = $this->goutteClient->request('GET', $this->url); 77 | 78 | // extract site info 79 | $parsedUrl = $this->parseUrl($this->crawler->getUri()); 80 | $this->data['site']['secure'] = strpos($this->crawler->getUri(), 'https://') !== false; 81 | $this->data['site']['url'] = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; 82 | 83 | // start scraping page 84 | $pageScrapeEvent = new PageScrapeEvent($this->data, $this->crawler); 85 | $this->data = $this->eventDispatcher->dispatch($pageScrapeEvent, PageScrapeEvent::NAME)->getData(); 86 | 87 | return $this; 88 | } 89 | 90 | public function get(string $section): array { 91 | $dataFilterEvent = new DataFilterEvent($this->data[$section] ?? [], $section, $this->crawler); 92 | return $this->eventDispatcher->dispatch($dataFilterEvent, DataFilterEvent::NAME)->getData(); 93 | } 94 | 95 | public function getAll(): array { 96 | return [ 97 | 'site' => $this->get('site'), 98 | 'page' => $this->get('page'), 99 | 'author' => $this->get('author'), 100 | 'app_links' => $this->get('app_links') 101 | ]; 102 | } 103 | 104 | public function addListener(string $eventName, callable $listener, int $priority = 0): self { 105 | $this->eventDispatcher->addListener($eventName, $listener, $priority); 106 | return $this; 107 | } 108 | 109 | protected function parseUrl(string $url): array { 110 | // TODO maybe use a better URL parser 111 | return parse_url($url); 112 | } 113 | 114 | public static function makeAbsoluteUri(string $baseUrl, string $url): string { 115 | if (strpos($url, 'http') === false) { 116 | if (substr($url, 0, 2) === '//') { 117 | $url = 'https:' . $url; 118 | } elseif ($url['0'] === '/') { 119 | $baseUrl = parse_url($baseUrl); 120 | $url = $baseUrl['scheme'] . '://' . $baseUrl['host'] . $url; 121 | } else { 122 | $url = rtrim($baseUrl, '/') . '/' . $url; 123 | } 124 | } 125 | 126 | return $url; 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Page Meta 🕵 2 | 3 | **Page Meta** is a PHP library than can retrieve detailed info on any URL from the internet! 4 | It uses data from HTML meta tags and [OpenGraph](http://ogp.me/) with fallback to detailed HTML scraping. 5 | 6 | ### Highlights 7 | - Works for any valid URL on the internet! 8 | - Follows page redirects 9 | - Uses all scraping methods available: HTML tags, OpenGraph, Schema data 10 | 11 | ### Potential use cases 12 | * Display Info Cards for links in a article 13 | * Rich preview for links in messaging apps 14 | * Extract info from a user-submitted URL 15 | layered-page-meta-link-card 16 | 17 | ## How to use 18 | 19 | #### Installation 20 | 21 | Add `layered/page-meta` as a dependency in your project's `composer.json` file: 22 | ``` bash 23 | $ composer require layered/page-meta 24 | ``` 25 | 26 | #### Usage 27 | 28 | Create a `UrlPreview` instance, then call `loadUrl($url)` method with your URL as first argument. Preview data is retrieved with `get($section)` or `getAll()` methods: 29 | ``` 30 | require 'vendor/autoload.php'; 31 | 32 | $preview = new Layered\PageMeta\UrlPreview([ 33 | 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; YourApp/1.0; +https://example.com)' 34 | ]); 35 | $preview->loadUrl('https://www.instagram.com/p/BbRyo_Kjqt1/'); 36 | 37 | $allPageData = $preview->getAll(); // contains all scraped data 38 | $siteInfo = $preview->get('site'); // get general info about the website 39 | ``` 40 | 41 | #### Behind the scenes 42 | 43 | The library downloads the HTML source of the url you provided, then uses specialized scrapers to extract pieces of information. 44 | Core scrapers can be seen in `src/scrapers/`, and they extract general info for a page: title, author, description, page type, main image, etc. 45 | If you would like to extract a new field, see [Extending the library](#extending-the-library) section. 46 | 47 | User Agent or extra headers can make a big difference when downloading HTML from a website. 48 | There are some websites that forbid scraping and hide the content when they detect a tool like this one. Make sure to read their dev docs & TOS. 49 | 50 | The default User Agent is blocked on sites like Twitter, Instagram, Facebook and others. A workaround is to use this one (thanks for the tip [PVGrad](https://github.com/LayeredStudio/page-meta/issues/2)): 51 | 52 | `'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'` 53 | 54 | #### Returned data 55 | 56 | Returned data will be an `Array` with following format: 57 | ``` 58 | { 59 | "site": { 60 | "secure": true, 61 | "url": "https:\/\/www.instagram.com", 62 | "icon": "https:\/\/www.instagram.com\/static\/images\/ico\/favicon-192.png\/b407fa101800.png", 63 | "language": "en", 64 | "responsive": true, 65 | "name": "Instagram" 66 | }, 67 | "page": { 68 | "type": "photo", 69 | "url": "https:\/\/www.instagram.com\/p\/BbRyo_Kjqt1\/", 70 | "title": "GitHub on Instagram", 71 | "description": "There\u2019s still time to join the #GitHubGameOff and build a game inspired by throwbacks. Get started\u2026", 72 | "image": { 73 | "url": "https:\/\/scontent-mad1-1.cdninstagram.com\/vp\/73b1790d77548031327e64ee83196706\/5B4AD567\/t51.2885-15\/e35\/23421974_1768724519826754_3855913942043852800_n.jpg" 74 | } 75 | }, 76 | "author": { 77 | "name": "GitHub", 78 | "handle": "@github", 79 | "url": "https:\/\/www.instagram.com\/github\/" 80 | }, 81 | "app_links": { 82 | "ios": { 83 | "url": "nflx:\/\/www.netflix.com\/title\/80014749", 84 | "app_store_id": "363590051", 85 | "app_name": "Netflix", 86 | "store_url": "https:\/\/itunes.apple.com\/us\/app\/Netflix\/id363590051" 87 | }, 88 | "android": { 89 | "url": "nflx:\/\/www.netflix.com\/title\/80014749", 90 | "package": "com.netflix.mediaclient", 91 | "app_name": "Netflix", 92 | "store_url": "https:\/\/play.google.com\/store\/apps\/details?id=com.netflix.mediaclient" 93 | } 94 | } 95 | } 96 | ``` 97 | See [`UrlPreview::getAll()`](#getall-array) for info on each returned field. 98 | 99 | ## Public API 100 | `UrlPreview` class provides the following public methods: 101 | 102 | #### `__construct(array $headers): UrlPreview` 103 | Start the UrlPreview instance. Pass extra headers to send when requesting the page URL 104 | 105 | #### `loadUrl(string $url): UrlPreview` 106 | Load and start the scrape process for any valid URL 107 | 108 | #### `getAll(): array` 109 | Get all data scraped from page 110 | 111 | **Return:** `Array` with scraped data in following format: 112 | - `site` - info about the website 113 | - `url` - main site URL 114 | - `name` - site name, ex: 'Instagram' or 'Medium' 115 | - `secure` - Boolean true|false depending on http connection 116 | - `responsive` - Boolean true|false. `True` if site has `viewport` meta tag present. Basic check for responsiveness 117 | - `icon` - site icon 118 | - `language` - ISO 639-1 language code, ex: `en`, `es` 119 | - `page` - info about the page at current URL 120 | - `type` - page type, ex: `website`, `article`, `profile`, `video`, etc 121 | - `url` - canonical URL for the page 122 | - `title` - page title 123 | - `description` - page description 124 | - `image` - `Array` containing image info, if present: 125 | - `url` - image URL 126 | - `width` - image width 127 | - `height` - image width 128 | - `video` - `Array` containing video info, if found on page: 129 | - `url` - video URL 130 | - `width` - video width 131 | - `height` - video width 132 | - `author` - info about the content author, ex: 133 | - `name` - Author's name on a blog, person's name on social network sites 134 | - `handle` - Social media site username 135 | - `url` - Author URL for more articles or Profile URL on social network sites 136 | - `app_links` - `Array` containing apps linked to page, like: 137 | - `ios` - iOS app 138 | - `url` - link for in-app action, ex: 'nflx://www.netflix.com/title/80014749' 139 | - `app_store_id` - Apple AppStore app ID 140 | - `app_name` - name of the app 141 | - `store_url` - link to installable app 142 | - `android` - Android app 143 | - `url` - link for in-app action, ex: 'nflx://www.netflix.com/title/80014749' 144 | - `package` - Android PlayStore app ID 145 | - `app_name` - name of the app 146 | - `store_url` - link to installable app 147 | 148 | #### `get(string $section): array` 149 | Get data in one scraped section `site`, `page`, `profile` or `app_links` 150 | 151 | **Return:** `Array` with section scraped data. See [`UrlPreview::getAll()`](#getall-array) for data format 152 | 153 | #### `addListener(string $eventName, callable $listener, int $priority = 0): UrlPreview` 154 | Attach an event on `UrlPreview` for data processing or scrape process. Arguments: 155 | - `$eventName` - on which event to listen. Available: 156 | - `page.scrape` - fired when the scraping process starts 157 | - `data.filter` - fired when data is requested by `getData()` or `getAll()` methods 158 | - `$listener` - a callable reference, which will get the `$event` parameter with available data 159 | - `$priority` - order on which the callable should be executed 160 | 161 | 162 | ### Extending the library 163 | If there's need to more scraped data for a URL, more functionality can be attached to **PageMeta** library. Example for returing the 'Terms and Conditions' link from pages: 164 | ``` 165 | use Symfony\Component\EventDispatcher\Event; 166 | 167 | $previewer = new \Layered\PageMeta\UrlPreview; 168 | $previewer->addListener('page.scrape', function(Event $event) { 169 | $currentScrapedData = $event->getData(); // check data from other scrapers 170 | $crawler = $event->getCrawler(); // instance of DomCrawler Symfony Component 171 | $termsLink = ''; 172 | 173 | $crawler->filter('a[href*=terms]')->each(function($node) use(&$termsLink) { 174 | $termsLink = $node->attr('href'); 175 | }); 176 | 177 | // forwards the scraped data 178 | $event->addData('site', [ 179 | 'termsLink' => $termsLink 180 | ]); 181 | }); 182 | $previewer->loadUrl('http://github.com'); 183 | ``` 184 | 185 | 186 | ## More 187 | 188 | Please report any issues here on GitHub. 189 | 190 | [Any contributions are welcome](CONTRIBUTING.md) 191 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "42c89fd46c7983d899e1e2c726195884", 8 | "packages": [ 9 | { 10 | "name": "fabpot/goutte", 11 | "version": "v4.0.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/FriendsOfPHP/Goutte.git", 15 | "reference": "293e754f0be2f1e85f9b31262cb811de39874e03" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/293e754f0be2f1e85f9b31262cb811de39874e03", 20 | "reference": "293e754f0be2f1e85f9b31262cb811de39874e03", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=7.1.3", 25 | "symfony/browser-kit": "^4.4|^5.0", 26 | "symfony/css-selector": "^4.4|^5.0", 27 | "symfony/dom-crawler": "^4.4|^5.0", 28 | "symfony/http-client": "^4.4|^5.0", 29 | "symfony/mime": "^4.4|^5.0" 30 | }, 31 | "require-dev": { 32 | "symfony/phpunit-bridge": "^5.0" 33 | }, 34 | "type": "application", 35 | "autoload": { 36 | "psr-4": { 37 | "Goutte\\": "Goutte" 38 | }, 39 | "exclude-from-classmap": [ 40 | "Goutte/Tests" 41 | ] 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Fabien Potencier", 50 | "email": "fabien@symfony.com" 51 | } 52 | ], 53 | "description": "A simple PHP Web Scraper", 54 | "homepage": "https://github.com/FriendsOfPHP/Goutte", 55 | "keywords": [ 56 | "scraper" 57 | ], 58 | "time": "2020-10-14T06:49:09+00:00" 59 | }, 60 | { 61 | "name": "psr/container", 62 | "version": "1.0.0", 63 | "source": { 64 | "type": "git", 65 | "url": "https://github.com/php-fig/container.git", 66 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" 67 | }, 68 | "dist": { 69 | "type": "zip", 70 | "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 71 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 72 | "shasum": "" 73 | }, 74 | "require": { 75 | "php": ">=5.3.0" 76 | }, 77 | "type": "library", 78 | "extra": { 79 | "branch-alias": { 80 | "dev-master": "1.0.x-dev" 81 | } 82 | }, 83 | "autoload": { 84 | "psr-4": { 85 | "Psr\\Container\\": "src/" 86 | } 87 | }, 88 | "notification-url": "https://packagist.org/downloads/", 89 | "license": [ 90 | "MIT" 91 | ], 92 | "authors": [ 93 | { 94 | "name": "PHP-FIG", 95 | "homepage": "http://www.php-fig.org/" 96 | } 97 | ], 98 | "description": "Common Container Interface (PHP FIG PSR-11)", 99 | "homepage": "https://github.com/php-fig/container", 100 | "keywords": [ 101 | "PSR-11", 102 | "container", 103 | "container-interface", 104 | "container-interop", 105 | "psr" 106 | ], 107 | "time": "2017-02-14T16:28:37+00:00" 108 | }, 109 | { 110 | "name": "psr/event-dispatcher", 111 | "version": "1.0.0", 112 | "source": { 113 | "type": "git", 114 | "url": "https://github.com/php-fig/event-dispatcher.git", 115 | "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" 116 | }, 117 | "dist": { 118 | "type": "zip", 119 | "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", 120 | "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", 121 | "shasum": "" 122 | }, 123 | "require": { 124 | "php": ">=7.2.0" 125 | }, 126 | "type": "library", 127 | "extra": { 128 | "branch-alias": { 129 | "dev-master": "1.0.x-dev" 130 | } 131 | }, 132 | "autoload": { 133 | "psr-4": { 134 | "Psr\\EventDispatcher\\": "src/" 135 | } 136 | }, 137 | "notification-url": "https://packagist.org/downloads/", 138 | "license": [ 139 | "MIT" 140 | ], 141 | "authors": [ 142 | { 143 | "name": "PHP-FIG", 144 | "homepage": "http://www.php-fig.org/" 145 | } 146 | ], 147 | "description": "Standard interfaces for event handling.", 148 | "keywords": [ 149 | "events", 150 | "psr", 151 | "psr-14" 152 | ], 153 | "time": "2019-01-08T18:20:26+00:00" 154 | }, 155 | { 156 | "name": "psr/log", 157 | "version": "1.1.3", 158 | "source": { 159 | "type": "git", 160 | "url": "https://github.com/php-fig/log.git", 161 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" 162 | }, 163 | "dist": { 164 | "type": "zip", 165 | "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", 166 | "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", 167 | "shasum": "" 168 | }, 169 | "require": { 170 | "php": ">=5.3.0" 171 | }, 172 | "type": "library", 173 | "extra": { 174 | "branch-alias": { 175 | "dev-master": "1.1.x-dev" 176 | } 177 | }, 178 | "autoload": { 179 | "psr-4": { 180 | "Psr\\Log\\": "Psr/Log/" 181 | } 182 | }, 183 | "notification-url": "https://packagist.org/downloads/", 184 | "license": [ 185 | "MIT" 186 | ], 187 | "authors": [ 188 | { 189 | "name": "PHP-FIG", 190 | "homepage": "http://www.php-fig.org/" 191 | } 192 | ], 193 | "description": "Common interface for logging libraries", 194 | "homepage": "https://github.com/php-fig/log", 195 | "keywords": [ 196 | "log", 197 | "psr", 198 | "psr-3" 199 | ], 200 | "time": "2020-03-23T09:12:05+00:00" 201 | }, 202 | { 203 | "name": "symfony/browser-kit", 204 | "version": "v5.1.8", 205 | "source": { 206 | "type": "git", 207 | "url": "https://github.com/symfony/browser-kit.git", 208 | "reference": "65b7d208280f2700f43ba44a8059a25d7b01347b" 209 | }, 210 | "dist": { 211 | "type": "zip", 212 | "url": "https://api.github.com/repos/symfony/browser-kit/zipball/65b7d208280f2700f43ba44a8059a25d7b01347b", 213 | "reference": "65b7d208280f2700f43ba44a8059a25d7b01347b", 214 | "shasum": "" 215 | }, 216 | "require": { 217 | "php": ">=7.2.5", 218 | "symfony/dom-crawler": "^4.4|^5.0" 219 | }, 220 | "require-dev": { 221 | "symfony/css-selector": "^4.4|^5.0", 222 | "symfony/http-client": "^4.4|^5.0", 223 | "symfony/mime": "^4.4|^5.0", 224 | "symfony/process": "^4.4|^5.0" 225 | }, 226 | "suggest": { 227 | "symfony/process": "" 228 | }, 229 | "type": "library", 230 | "autoload": { 231 | "psr-4": { 232 | "Symfony\\Component\\BrowserKit\\": "" 233 | }, 234 | "exclude-from-classmap": [ 235 | "/Tests/" 236 | ] 237 | }, 238 | "notification-url": "https://packagist.org/downloads/", 239 | "license": [ 240 | "MIT" 241 | ], 242 | "authors": [ 243 | { 244 | "name": "Fabien Potencier", 245 | "email": "fabien@symfony.com" 246 | }, 247 | { 248 | "name": "Symfony Community", 249 | "homepage": "https://symfony.com/contributors" 250 | } 251 | ], 252 | "description": "Symfony BrowserKit Component", 253 | "homepage": "https://symfony.com", 254 | "funding": [ 255 | { 256 | "url": "https://symfony.com/sponsor", 257 | "type": "custom" 258 | }, 259 | { 260 | "url": "https://github.com/fabpot", 261 | "type": "github" 262 | }, 263 | { 264 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 265 | "type": "tidelift" 266 | } 267 | ], 268 | "time": "2020-10-24T12:01:57+00:00" 269 | }, 270 | { 271 | "name": "symfony/css-selector", 272 | "version": "v5.1.8", 273 | "source": { 274 | "type": "git", 275 | "url": "https://github.com/symfony/css-selector.git", 276 | "reference": "6cbebda22ffc0d4bb8fea0c1311c2ca54c4c8fa0" 277 | }, 278 | "dist": { 279 | "type": "zip", 280 | "url": "https://api.github.com/repos/symfony/css-selector/zipball/6cbebda22ffc0d4bb8fea0c1311c2ca54c4c8fa0", 281 | "reference": "6cbebda22ffc0d4bb8fea0c1311c2ca54c4c8fa0", 282 | "shasum": "" 283 | }, 284 | "require": { 285 | "php": ">=7.2.5" 286 | }, 287 | "type": "library", 288 | "autoload": { 289 | "psr-4": { 290 | "Symfony\\Component\\CssSelector\\": "" 291 | }, 292 | "exclude-from-classmap": [ 293 | "/Tests/" 294 | ] 295 | }, 296 | "notification-url": "https://packagist.org/downloads/", 297 | "license": [ 298 | "MIT" 299 | ], 300 | "authors": [ 301 | { 302 | "name": "Fabien Potencier", 303 | "email": "fabien@symfony.com" 304 | }, 305 | { 306 | "name": "Jean-François Simon", 307 | "email": "jeanfrancois.simon@sensiolabs.com" 308 | }, 309 | { 310 | "name": "Symfony Community", 311 | "homepage": "https://symfony.com/contributors" 312 | } 313 | ], 314 | "description": "Symfony CssSelector Component", 315 | "homepage": "https://symfony.com", 316 | "funding": [ 317 | { 318 | "url": "https://symfony.com/sponsor", 319 | "type": "custom" 320 | }, 321 | { 322 | "url": "https://github.com/fabpot", 323 | "type": "github" 324 | }, 325 | { 326 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 327 | "type": "tidelift" 328 | } 329 | ], 330 | "time": "2020-10-24T12:01:57+00:00" 331 | }, 332 | { 333 | "name": "symfony/deprecation-contracts", 334 | "version": "v2.2.0", 335 | "source": { 336 | "type": "git", 337 | "url": "https://github.com/symfony/deprecation-contracts.git", 338 | "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" 339 | }, 340 | "dist": { 341 | "type": "zip", 342 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", 343 | "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", 344 | "shasum": "" 345 | }, 346 | "require": { 347 | "php": ">=7.1" 348 | }, 349 | "type": "library", 350 | "extra": { 351 | "branch-alias": { 352 | "dev-master": "2.2-dev" 353 | }, 354 | "thanks": { 355 | "name": "symfony/contracts", 356 | "url": "https://github.com/symfony/contracts" 357 | } 358 | }, 359 | "autoload": { 360 | "files": [ 361 | "function.php" 362 | ] 363 | }, 364 | "notification-url": "https://packagist.org/downloads/", 365 | "license": [ 366 | "MIT" 367 | ], 368 | "authors": [ 369 | { 370 | "name": "Nicolas Grekas", 371 | "email": "p@tchwork.com" 372 | }, 373 | { 374 | "name": "Symfony Community", 375 | "homepage": "https://symfony.com/contributors" 376 | } 377 | ], 378 | "description": "A generic function and convention to trigger deprecation notices", 379 | "homepage": "https://symfony.com", 380 | "time": "2020-09-07T11:33:47+00:00" 381 | }, 382 | { 383 | "name": "symfony/dom-crawler", 384 | "version": "v5.1.8", 385 | "source": { 386 | "type": "git", 387 | "url": "https://github.com/symfony/dom-crawler.git", 388 | "reference": "0969122fe144dd8ab2e8c98c7e03eedc621b368c" 389 | }, 390 | "dist": { 391 | "type": "zip", 392 | "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0969122fe144dd8ab2e8c98c7e03eedc621b368c", 393 | "reference": "0969122fe144dd8ab2e8c98c7e03eedc621b368c", 394 | "shasum": "" 395 | }, 396 | "require": { 397 | "php": ">=7.2.5", 398 | "symfony/polyfill-ctype": "~1.8", 399 | "symfony/polyfill-mbstring": "~1.0", 400 | "symfony/polyfill-php80": "^1.15" 401 | }, 402 | "conflict": { 403 | "masterminds/html5": "<2.6" 404 | }, 405 | "require-dev": { 406 | "masterminds/html5": "^2.6", 407 | "symfony/css-selector": "^4.4|^5.0" 408 | }, 409 | "suggest": { 410 | "symfony/css-selector": "" 411 | }, 412 | "type": "library", 413 | "autoload": { 414 | "psr-4": { 415 | "Symfony\\Component\\DomCrawler\\": "" 416 | }, 417 | "exclude-from-classmap": [ 418 | "/Tests/" 419 | ] 420 | }, 421 | "notification-url": "https://packagist.org/downloads/", 422 | "license": [ 423 | "MIT" 424 | ], 425 | "authors": [ 426 | { 427 | "name": "Fabien Potencier", 428 | "email": "fabien@symfony.com" 429 | }, 430 | { 431 | "name": "Symfony Community", 432 | "homepage": "https://symfony.com/contributors" 433 | } 434 | ], 435 | "description": "Symfony DomCrawler Component", 436 | "homepage": "https://symfony.com", 437 | "funding": [ 438 | { 439 | "url": "https://symfony.com/sponsor", 440 | "type": "custom" 441 | }, 442 | { 443 | "url": "https://github.com/fabpot", 444 | "type": "github" 445 | }, 446 | { 447 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 448 | "type": "tidelift" 449 | } 450 | ], 451 | "time": "2020-10-24T12:01:57+00:00" 452 | }, 453 | { 454 | "name": "symfony/event-dispatcher", 455 | "version": "v5.1.8", 456 | "source": { 457 | "type": "git", 458 | "url": "https://github.com/symfony/event-dispatcher.git", 459 | "reference": "26f4edae48c913fc183a3da0553fe63bdfbd361a" 460 | }, 461 | "dist": { 462 | "type": "zip", 463 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/26f4edae48c913fc183a3da0553fe63bdfbd361a", 464 | "reference": "26f4edae48c913fc183a3da0553fe63bdfbd361a", 465 | "shasum": "" 466 | }, 467 | "require": { 468 | "php": ">=7.2.5", 469 | "symfony/deprecation-contracts": "^2.1", 470 | "symfony/event-dispatcher-contracts": "^2", 471 | "symfony/polyfill-php80": "^1.15" 472 | }, 473 | "conflict": { 474 | "symfony/dependency-injection": "<4.4" 475 | }, 476 | "provide": { 477 | "psr/event-dispatcher-implementation": "1.0", 478 | "symfony/event-dispatcher-implementation": "2.0" 479 | }, 480 | "require-dev": { 481 | "psr/log": "~1.0", 482 | "symfony/config": "^4.4|^5.0", 483 | "symfony/dependency-injection": "^4.4|^5.0", 484 | "symfony/error-handler": "^4.4|^5.0", 485 | "symfony/expression-language": "^4.4|^5.0", 486 | "symfony/http-foundation": "^4.4|^5.0", 487 | "symfony/service-contracts": "^1.1|^2", 488 | "symfony/stopwatch": "^4.4|^5.0" 489 | }, 490 | "suggest": { 491 | "symfony/dependency-injection": "", 492 | "symfony/http-kernel": "" 493 | }, 494 | "type": "library", 495 | "autoload": { 496 | "psr-4": { 497 | "Symfony\\Component\\EventDispatcher\\": "" 498 | }, 499 | "exclude-from-classmap": [ 500 | "/Tests/" 501 | ] 502 | }, 503 | "notification-url": "https://packagist.org/downloads/", 504 | "license": [ 505 | "MIT" 506 | ], 507 | "authors": [ 508 | { 509 | "name": "Fabien Potencier", 510 | "email": "fabien@symfony.com" 511 | }, 512 | { 513 | "name": "Symfony Community", 514 | "homepage": "https://symfony.com/contributors" 515 | } 516 | ], 517 | "description": "Symfony EventDispatcher Component", 518 | "homepage": "https://symfony.com", 519 | "funding": [ 520 | { 521 | "url": "https://symfony.com/sponsor", 522 | "type": "custom" 523 | }, 524 | { 525 | "url": "https://github.com/fabpot", 526 | "type": "github" 527 | }, 528 | { 529 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 530 | "type": "tidelift" 531 | } 532 | ], 533 | "time": "2020-10-24T12:01:57+00:00" 534 | }, 535 | { 536 | "name": "symfony/event-dispatcher-contracts", 537 | "version": "v2.2.0", 538 | "source": { 539 | "type": "git", 540 | "url": "https://github.com/symfony/event-dispatcher-contracts.git", 541 | "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2" 542 | }, 543 | "dist": { 544 | "type": "zip", 545 | "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ba7d54483095a198fa51781bc608d17e84dffa2", 546 | "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2", 547 | "shasum": "" 548 | }, 549 | "require": { 550 | "php": ">=7.2.5", 551 | "psr/event-dispatcher": "^1" 552 | }, 553 | "suggest": { 554 | "symfony/event-dispatcher-implementation": "" 555 | }, 556 | "type": "library", 557 | "extra": { 558 | "branch-alias": { 559 | "dev-master": "2.2-dev" 560 | }, 561 | "thanks": { 562 | "name": "symfony/contracts", 563 | "url": "https://github.com/symfony/contracts" 564 | } 565 | }, 566 | "autoload": { 567 | "psr-4": { 568 | "Symfony\\Contracts\\EventDispatcher\\": "" 569 | } 570 | }, 571 | "notification-url": "https://packagist.org/downloads/", 572 | "license": [ 573 | "MIT" 574 | ], 575 | "authors": [ 576 | { 577 | "name": "Nicolas Grekas", 578 | "email": "p@tchwork.com" 579 | }, 580 | { 581 | "name": "Symfony Community", 582 | "homepage": "https://symfony.com/contributors" 583 | } 584 | ], 585 | "description": "Generic abstractions related to dispatching event", 586 | "homepage": "https://symfony.com", 587 | "keywords": [ 588 | "abstractions", 589 | "contracts", 590 | "decoupling", 591 | "interfaces", 592 | "interoperability", 593 | "standards" 594 | ], 595 | "time": "2020-09-07T11:33:47+00:00" 596 | }, 597 | { 598 | "name": "symfony/http-client", 599 | "version": "v5.1.8", 600 | "source": { 601 | "type": "git", 602 | "url": "https://github.com/symfony/http-client.git", 603 | "reference": "97a6a1f9f5bb3a6094833107b58a72bc9a9165cc" 604 | }, 605 | "dist": { 606 | "type": "zip", 607 | "url": "https://api.github.com/repos/symfony/http-client/zipball/97a6a1f9f5bb3a6094833107b58a72bc9a9165cc", 608 | "reference": "97a6a1f9f5bb3a6094833107b58a72bc9a9165cc", 609 | "shasum": "" 610 | }, 611 | "require": { 612 | "php": ">=7.2.5", 613 | "psr/log": "^1.0", 614 | "symfony/http-client-contracts": "^2.2", 615 | "symfony/polyfill-php73": "^1.11", 616 | "symfony/polyfill-php80": "^1.15", 617 | "symfony/service-contracts": "^1.0|^2" 618 | }, 619 | "provide": { 620 | "php-http/async-client-implementation": "*", 621 | "php-http/client-implementation": "*", 622 | "psr/http-client-implementation": "1.0", 623 | "symfony/http-client-implementation": "1.1" 624 | }, 625 | "require-dev": { 626 | "amphp/http-client": "^4.2.1", 627 | "amphp/http-tunnel": "^1.0", 628 | "amphp/socket": "^1.1", 629 | "guzzlehttp/promises": "^1.3.1", 630 | "nyholm/psr7": "^1.0", 631 | "php-http/httplug": "^1.0|^2.0", 632 | "psr/http-client": "^1.0", 633 | "symfony/dependency-injection": "^4.4|^5.0", 634 | "symfony/http-kernel": "^4.4.13|^5.1.5", 635 | "symfony/process": "^4.4|^5.0" 636 | }, 637 | "type": "library", 638 | "autoload": { 639 | "psr-4": { 640 | "Symfony\\Component\\HttpClient\\": "" 641 | }, 642 | "exclude-from-classmap": [ 643 | "/Tests/" 644 | ] 645 | }, 646 | "notification-url": "https://packagist.org/downloads/", 647 | "license": [ 648 | "MIT" 649 | ], 650 | "authors": [ 651 | { 652 | "name": "Nicolas Grekas", 653 | "email": "p@tchwork.com" 654 | }, 655 | { 656 | "name": "Symfony Community", 657 | "homepage": "https://symfony.com/contributors" 658 | } 659 | ], 660 | "description": "Symfony HttpClient component", 661 | "homepage": "https://symfony.com", 662 | "funding": [ 663 | { 664 | "url": "https://symfony.com/sponsor", 665 | "type": "custom" 666 | }, 667 | { 668 | "url": "https://github.com/fabpot", 669 | "type": "github" 670 | }, 671 | { 672 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 673 | "type": "tidelift" 674 | } 675 | ], 676 | "time": "2020-10-24T12:01:57+00:00" 677 | }, 678 | { 679 | "name": "symfony/http-client-contracts", 680 | "version": "v2.3.1", 681 | "source": { 682 | "type": "git", 683 | "url": "https://github.com/symfony/http-client-contracts.git", 684 | "reference": "41db680a15018f9c1d4b23516059633ce280ca33" 685 | }, 686 | "dist": { 687 | "type": "zip", 688 | "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41db680a15018f9c1d4b23516059633ce280ca33", 689 | "reference": "41db680a15018f9c1d4b23516059633ce280ca33", 690 | "shasum": "" 691 | }, 692 | "require": { 693 | "php": ">=7.2.5" 694 | }, 695 | "suggest": { 696 | "symfony/http-client-implementation": "" 697 | }, 698 | "type": "library", 699 | "extra": { 700 | "branch-version": "2.3", 701 | "branch-alias": { 702 | "dev-main": "2.3-dev" 703 | }, 704 | "thanks": { 705 | "name": "symfony/contracts", 706 | "url": "https://github.com/symfony/contracts" 707 | } 708 | }, 709 | "autoload": { 710 | "psr-4": { 711 | "Symfony\\Contracts\\HttpClient\\": "" 712 | } 713 | }, 714 | "notification-url": "https://packagist.org/downloads/", 715 | "license": [ 716 | "MIT" 717 | ], 718 | "authors": [ 719 | { 720 | "name": "Nicolas Grekas", 721 | "email": "p@tchwork.com" 722 | }, 723 | { 724 | "name": "Symfony Community", 725 | "homepage": "https://symfony.com/contributors" 726 | } 727 | ], 728 | "description": "Generic abstractions related to HTTP clients", 729 | "homepage": "https://symfony.com", 730 | "keywords": [ 731 | "abstractions", 732 | "contracts", 733 | "decoupling", 734 | "interfaces", 735 | "interoperability", 736 | "standards" 737 | ], 738 | "funding": [ 739 | { 740 | "url": "https://symfony.com/sponsor", 741 | "type": "custom" 742 | }, 743 | { 744 | "url": "https://github.com/fabpot", 745 | "type": "github" 746 | }, 747 | { 748 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 749 | "type": "tidelift" 750 | } 751 | ], 752 | "time": "2020-10-14T17:08:19+00:00" 753 | }, 754 | { 755 | "name": "symfony/mime", 756 | "version": "v5.1.8", 757 | "source": { 758 | "type": "git", 759 | "url": "https://github.com/symfony/mime.git", 760 | "reference": "f5485a92c24d4bcfc2f3fc648744fb398482ff1b" 761 | }, 762 | "dist": { 763 | "type": "zip", 764 | "url": "https://api.github.com/repos/symfony/mime/zipball/f5485a92c24d4bcfc2f3fc648744fb398482ff1b", 765 | "reference": "f5485a92c24d4bcfc2f3fc648744fb398482ff1b", 766 | "shasum": "" 767 | }, 768 | "require": { 769 | "php": ">=7.2.5", 770 | "symfony/polyfill-intl-idn": "^1.10", 771 | "symfony/polyfill-mbstring": "^1.0", 772 | "symfony/polyfill-php80": "^1.15" 773 | }, 774 | "conflict": { 775 | "symfony/mailer": "<4.4" 776 | }, 777 | "require-dev": { 778 | "egulias/email-validator": "^2.1.10", 779 | "symfony/dependency-injection": "^4.4|^5.0" 780 | }, 781 | "type": "library", 782 | "autoload": { 783 | "psr-4": { 784 | "Symfony\\Component\\Mime\\": "" 785 | }, 786 | "exclude-from-classmap": [ 787 | "/Tests/" 788 | ] 789 | }, 790 | "notification-url": "https://packagist.org/downloads/", 791 | "license": [ 792 | "MIT" 793 | ], 794 | "authors": [ 795 | { 796 | "name": "Fabien Potencier", 797 | "email": "fabien@symfony.com" 798 | }, 799 | { 800 | "name": "Symfony Community", 801 | "homepage": "https://symfony.com/contributors" 802 | } 803 | ], 804 | "description": "A library to manipulate MIME messages", 805 | "homepage": "https://symfony.com", 806 | "keywords": [ 807 | "mime", 808 | "mime-type" 809 | ], 810 | "funding": [ 811 | { 812 | "url": "https://symfony.com/sponsor", 813 | "type": "custom" 814 | }, 815 | { 816 | "url": "https://github.com/fabpot", 817 | "type": "github" 818 | }, 819 | { 820 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 821 | "type": "tidelift" 822 | } 823 | ], 824 | "time": "2020-10-24T12:01:57+00:00" 825 | }, 826 | { 827 | "name": "symfony/polyfill-ctype", 828 | "version": "v1.20.0", 829 | "source": { 830 | "type": "git", 831 | "url": "https://github.com/symfony/polyfill-ctype.git", 832 | "reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41" 833 | }, 834 | "dist": { 835 | "type": "zip", 836 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f4ba089a5b6366e453971d3aad5fe8e897b37f41", 837 | "reference": "f4ba089a5b6366e453971d3aad5fe8e897b37f41", 838 | "shasum": "" 839 | }, 840 | "require": { 841 | "php": ">=7.1" 842 | }, 843 | "suggest": { 844 | "ext-ctype": "For best performance" 845 | }, 846 | "type": "library", 847 | "extra": { 848 | "branch-alias": { 849 | "dev-main": "1.20-dev" 850 | }, 851 | "thanks": { 852 | "name": "symfony/polyfill", 853 | "url": "https://github.com/symfony/polyfill" 854 | } 855 | }, 856 | "autoload": { 857 | "psr-4": { 858 | "Symfony\\Polyfill\\Ctype\\": "" 859 | }, 860 | "files": [ 861 | "bootstrap.php" 862 | ] 863 | }, 864 | "notification-url": "https://packagist.org/downloads/", 865 | "license": [ 866 | "MIT" 867 | ], 868 | "authors": [ 869 | { 870 | "name": "Gert de Pagter", 871 | "email": "BackEndTea@gmail.com" 872 | }, 873 | { 874 | "name": "Symfony Community", 875 | "homepage": "https://symfony.com/contributors" 876 | } 877 | ], 878 | "description": "Symfony polyfill for ctype functions", 879 | "homepage": "https://symfony.com", 880 | "keywords": [ 881 | "compatibility", 882 | "ctype", 883 | "polyfill", 884 | "portable" 885 | ], 886 | "funding": [ 887 | { 888 | "url": "https://symfony.com/sponsor", 889 | "type": "custom" 890 | }, 891 | { 892 | "url": "https://github.com/fabpot", 893 | "type": "github" 894 | }, 895 | { 896 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 897 | "type": "tidelift" 898 | } 899 | ], 900 | "time": "2020-10-23T14:02:19+00:00" 901 | }, 902 | { 903 | "name": "symfony/polyfill-intl-idn", 904 | "version": "v1.20.0", 905 | "source": { 906 | "type": "git", 907 | "url": "https://github.com/symfony/polyfill-intl-idn.git", 908 | "reference": "3b75acd829741c768bc8b1f84eb33265e7cc5117" 909 | }, 910 | "dist": { 911 | "type": "zip", 912 | "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/3b75acd829741c768bc8b1f84eb33265e7cc5117", 913 | "reference": "3b75acd829741c768bc8b1f84eb33265e7cc5117", 914 | "shasum": "" 915 | }, 916 | "require": { 917 | "php": ">=7.1", 918 | "symfony/polyfill-intl-normalizer": "^1.10", 919 | "symfony/polyfill-php72": "^1.10" 920 | }, 921 | "suggest": { 922 | "ext-intl": "For best performance" 923 | }, 924 | "type": "library", 925 | "extra": { 926 | "branch-alias": { 927 | "dev-main": "1.20-dev" 928 | }, 929 | "thanks": { 930 | "name": "symfony/polyfill", 931 | "url": "https://github.com/symfony/polyfill" 932 | } 933 | }, 934 | "autoload": { 935 | "psr-4": { 936 | "Symfony\\Polyfill\\Intl\\Idn\\": "" 937 | }, 938 | "files": [ 939 | "bootstrap.php" 940 | ] 941 | }, 942 | "notification-url": "https://packagist.org/downloads/", 943 | "license": [ 944 | "MIT" 945 | ], 946 | "authors": [ 947 | { 948 | "name": "Laurent Bassin", 949 | "email": "laurent@bassin.info" 950 | }, 951 | { 952 | "name": "Trevor Rowbotham", 953 | "email": "trevor.rowbotham@pm.me" 954 | }, 955 | { 956 | "name": "Symfony Community", 957 | "homepage": "https://symfony.com/contributors" 958 | } 959 | ], 960 | "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", 961 | "homepage": "https://symfony.com", 962 | "keywords": [ 963 | "compatibility", 964 | "idn", 965 | "intl", 966 | "polyfill", 967 | "portable", 968 | "shim" 969 | ], 970 | "funding": [ 971 | { 972 | "url": "https://symfony.com/sponsor", 973 | "type": "custom" 974 | }, 975 | { 976 | "url": "https://github.com/fabpot", 977 | "type": "github" 978 | }, 979 | { 980 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 981 | "type": "tidelift" 982 | } 983 | ], 984 | "time": "2020-10-23T14:02:19+00:00" 985 | }, 986 | { 987 | "name": "symfony/polyfill-intl-normalizer", 988 | "version": "v1.20.0", 989 | "source": { 990 | "type": "git", 991 | "url": "https://github.com/symfony/polyfill-intl-normalizer.git", 992 | "reference": "727d1096295d807c309fb01a851577302394c897" 993 | }, 994 | "dist": { 995 | "type": "zip", 996 | "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/727d1096295d807c309fb01a851577302394c897", 997 | "reference": "727d1096295d807c309fb01a851577302394c897", 998 | "shasum": "" 999 | }, 1000 | "require": { 1001 | "php": ">=7.1" 1002 | }, 1003 | "suggest": { 1004 | "ext-intl": "For best performance" 1005 | }, 1006 | "type": "library", 1007 | "extra": { 1008 | "branch-alias": { 1009 | "dev-main": "1.20-dev" 1010 | }, 1011 | "thanks": { 1012 | "name": "symfony/polyfill", 1013 | "url": "https://github.com/symfony/polyfill" 1014 | } 1015 | }, 1016 | "autoload": { 1017 | "psr-4": { 1018 | "Symfony\\Polyfill\\Intl\\Normalizer\\": "" 1019 | }, 1020 | "files": [ 1021 | "bootstrap.php" 1022 | ], 1023 | "classmap": [ 1024 | "Resources/stubs" 1025 | ] 1026 | }, 1027 | "notification-url": "https://packagist.org/downloads/", 1028 | "license": [ 1029 | "MIT" 1030 | ], 1031 | "authors": [ 1032 | { 1033 | "name": "Nicolas Grekas", 1034 | "email": "p@tchwork.com" 1035 | }, 1036 | { 1037 | "name": "Symfony Community", 1038 | "homepage": "https://symfony.com/contributors" 1039 | } 1040 | ], 1041 | "description": "Symfony polyfill for intl's Normalizer class and related functions", 1042 | "homepage": "https://symfony.com", 1043 | "keywords": [ 1044 | "compatibility", 1045 | "intl", 1046 | "normalizer", 1047 | "polyfill", 1048 | "portable", 1049 | "shim" 1050 | ], 1051 | "funding": [ 1052 | { 1053 | "url": "https://symfony.com/sponsor", 1054 | "type": "custom" 1055 | }, 1056 | { 1057 | "url": "https://github.com/fabpot", 1058 | "type": "github" 1059 | }, 1060 | { 1061 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1062 | "type": "tidelift" 1063 | } 1064 | ], 1065 | "time": "2020-10-23T14:02:19+00:00" 1066 | }, 1067 | { 1068 | "name": "symfony/polyfill-mbstring", 1069 | "version": "v1.20.0", 1070 | "source": { 1071 | "type": "git", 1072 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1073 | "reference": "39d483bdf39be819deabf04ec872eb0b2410b531" 1074 | }, 1075 | "dist": { 1076 | "type": "zip", 1077 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531", 1078 | "reference": "39d483bdf39be819deabf04ec872eb0b2410b531", 1079 | "shasum": "" 1080 | }, 1081 | "require": { 1082 | "php": ">=7.1" 1083 | }, 1084 | "suggest": { 1085 | "ext-mbstring": "For best performance" 1086 | }, 1087 | "type": "library", 1088 | "extra": { 1089 | "branch-alias": { 1090 | "dev-main": "1.20-dev" 1091 | }, 1092 | "thanks": { 1093 | "name": "symfony/polyfill", 1094 | "url": "https://github.com/symfony/polyfill" 1095 | } 1096 | }, 1097 | "autoload": { 1098 | "psr-4": { 1099 | "Symfony\\Polyfill\\Mbstring\\": "" 1100 | }, 1101 | "files": [ 1102 | "bootstrap.php" 1103 | ] 1104 | }, 1105 | "notification-url": "https://packagist.org/downloads/", 1106 | "license": [ 1107 | "MIT" 1108 | ], 1109 | "authors": [ 1110 | { 1111 | "name": "Nicolas Grekas", 1112 | "email": "p@tchwork.com" 1113 | }, 1114 | { 1115 | "name": "Symfony Community", 1116 | "homepage": "https://symfony.com/contributors" 1117 | } 1118 | ], 1119 | "description": "Symfony polyfill for the Mbstring extension", 1120 | "homepage": "https://symfony.com", 1121 | "keywords": [ 1122 | "compatibility", 1123 | "mbstring", 1124 | "polyfill", 1125 | "portable", 1126 | "shim" 1127 | ], 1128 | "funding": [ 1129 | { 1130 | "url": "https://symfony.com/sponsor", 1131 | "type": "custom" 1132 | }, 1133 | { 1134 | "url": "https://github.com/fabpot", 1135 | "type": "github" 1136 | }, 1137 | { 1138 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1139 | "type": "tidelift" 1140 | } 1141 | ], 1142 | "time": "2020-10-23T14:02:19+00:00" 1143 | }, 1144 | { 1145 | "name": "symfony/polyfill-php72", 1146 | "version": "v1.20.0", 1147 | "source": { 1148 | "type": "git", 1149 | "url": "https://github.com/symfony/polyfill-php72.git", 1150 | "reference": "cede45fcdfabdd6043b3592e83678e42ec69e930" 1151 | }, 1152 | "dist": { 1153 | "type": "zip", 1154 | "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cede45fcdfabdd6043b3592e83678e42ec69e930", 1155 | "reference": "cede45fcdfabdd6043b3592e83678e42ec69e930", 1156 | "shasum": "" 1157 | }, 1158 | "require": { 1159 | "php": ">=7.1" 1160 | }, 1161 | "type": "library", 1162 | "extra": { 1163 | "branch-alias": { 1164 | "dev-main": "1.20-dev" 1165 | }, 1166 | "thanks": { 1167 | "name": "symfony/polyfill", 1168 | "url": "https://github.com/symfony/polyfill" 1169 | } 1170 | }, 1171 | "autoload": { 1172 | "psr-4": { 1173 | "Symfony\\Polyfill\\Php72\\": "" 1174 | }, 1175 | "files": [ 1176 | "bootstrap.php" 1177 | ] 1178 | }, 1179 | "notification-url": "https://packagist.org/downloads/", 1180 | "license": [ 1181 | "MIT" 1182 | ], 1183 | "authors": [ 1184 | { 1185 | "name": "Nicolas Grekas", 1186 | "email": "p@tchwork.com" 1187 | }, 1188 | { 1189 | "name": "Symfony Community", 1190 | "homepage": "https://symfony.com/contributors" 1191 | } 1192 | ], 1193 | "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", 1194 | "homepage": "https://symfony.com", 1195 | "keywords": [ 1196 | "compatibility", 1197 | "polyfill", 1198 | "portable", 1199 | "shim" 1200 | ], 1201 | "funding": [ 1202 | { 1203 | "url": "https://symfony.com/sponsor", 1204 | "type": "custom" 1205 | }, 1206 | { 1207 | "url": "https://github.com/fabpot", 1208 | "type": "github" 1209 | }, 1210 | { 1211 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1212 | "type": "tidelift" 1213 | } 1214 | ], 1215 | "time": "2020-10-23T14:02:19+00:00" 1216 | }, 1217 | { 1218 | "name": "symfony/polyfill-php73", 1219 | "version": "v1.20.0", 1220 | "source": { 1221 | "type": "git", 1222 | "url": "https://github.com/symfony/polyfill-php73.git", 1223 | "reference": "8ff431c517be11c78c48a39a66d37431e26a6bed" 1224 | }, 1225 | "dist": { 1226 | "type": "zip", 1227 | "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/8ff431c517be11c78c48a39a66d37431e26a6bed", 1228 | "reference": "8ff431c517be11c78c48a39a66d37431e26a6bed", 1229 | "shasum": "" 1230 | }, 1231 | "require": { 1232 | "php": ">=7.1" 1233 | }, 1234 | "type": "library", 1235 | "extra": { 1236 | "branch-alias": { 1237 | "dev-main": "1.20-dev" 1238 | }, 1239 | "thanks": { 1240 | "name": "symfony/polyfill", 1241 | "url": "https://github.com/symfony/polyfill" 1242 | } 1243 | }, 1244 | "autoload": { 1245 | "psr-4": { 1246 | "Symfony\\Polyfill\\Php73\\": "" 1247 | }, 1248 | "files": [ 1249 | "bootstrap.php" 1250 | ], 1251 | "classmap": [ 1252 | "Resources/stubs" 1253 | ] 1254 | }, 1255 | "notification-url": "https://packagist.org/downloads/", 1256 | "license": [ 1257 | "MIT" 1258 | ], 1259 | "authors": [ 1260 | { 1261 | "name": "Nicolas Grekas", 1262 | "email": "p@tchwork.com" 1263 | }, 1264 | { 1265 | "name": "Symfony Community", 1266 | "homepage": "https://symfony.com/contributors" 1267 | } 1268 | ], 1269 | "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", 1270 | "homepage": "https://symfony.com", 1271 | "keywords": [ 1272 | "compatibility", 1273 | "polyfill", 1274 | "portable", 1275 | "shim" 1276 | ], 1277 | "funding": [ 1278 | { 1279 | "url": "https://symfony.com/sponsor", 1280 | "type": "custom" 1281 | }, 1282 | { 1283 | "url": "https://github.com/fabpot", 1284 | "type": "github" 1285 | }, 1286 | { 1287 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1288 | "type": "tidelift" 1289 | } 1290 | ], 1291 | "time": "2020-10-23T14:02:19+00:00" 1292 | }, 1293 | { 1294 | "name": "symfony/polyfill-php80", 1295 | "version": "v1.20.0", 1296 | "source": { 1297 | "type": "git", 1298 | "url": "https://github.com/symfony/polyfill-php80.git", 1299 | "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de" 1300 | }, 1301 | "dist": { 1302 | "type": "zip", 1303 | "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/e70aa8b064c5b72d3df2abd5ab1e90464ad009de", 1304 | "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de", 1305 | "shasum": "" 1306 | }, 1307 | "require": { 1308 | "php": ">=7.1" 1309 | }, 1310 | "type": "library", 1311 | "extra": { 1312 | "branch-alias": { 1313 | "dev-main": "1.20-dev" 1314 | }, 1315 | "thanks": { 1316 | "name": "symfony/polyfill", 1317 | "url": "https://github.com/symfony/polyfill" 1318 | } 1319 | }, 1320 | "autoload": { 1321 | "psr-4": { 1322 | "Symfony\\Polyfill\\Php80\\": "" 1323 | }, 1324 | "files": [ 1325 | "bootstrap.php" 1326 | ], 1327 | "classmap": [ 1328 | "Resources/stubs" 1329 | ] 1330 | }, 1331 | "notification-url": "https://packagist.org/downloads/", 1332 | "license": [ 1333 | "MIT" 1334 | ], 1335 | "authors": [ 1336 | { 1337 | "name": "Ion Bazan", 1338 | "email": "ion.bazan@gmail.com" 1339 | }, 1340 | { 1341 | "name": "Nicolas Grekas", 1342 | "email": "p@tchwork.com" 1343 | }, 1344 | { 1345 | "name": "Symfony Community", 1346 | "homepage": "https://symfony.com/contributors" 1347 | } 1348 | ], 1349 | "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", 1350 | "homepage": "https://symfony.com", 1351 | "keywords": [ 1352 | "compatibility", 1353 | "polyfill", 1354 | "portable", 1355 | "shim" 1356 | ], 1357 | "funding": [ 1358 | { 1359 | "url": "https://symfony.com/sponsor", 1360 | "type": "custom" 1361 | }, 1362 | { 1363 | "url": "https://github.com/fabpot", 1364 | "type": "github" 1365 | }, 1366 | { 1367 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1368 | "type": "tidelift" 1369 | } 1370 | ], 1371 | "time": "2020-10-23T14:02:19+00:00" 1372 | }, 1373 | { 1374 | "name": "symfony/service-contracts", 1375 | "version": "v2.2.0", 1376 | "source": { 1377 | "type": "git", 1378 | "url": "https://github.com/symfony/service-contracts.git", 1379 | "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" 1380 | }, 1381 | "dist": { 1382 | "type": "zip", 1383 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", 1384 | "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", 1385 | "shasum": "" 1386 | }, 1387 | "require": { 1388 | "php": ">=7.2.5", 1389 | "psr/container": "^1.0" 1390 | }, 1391 | "suggest": { 1392 | "symfony/service-implementation": "" 1393 | }, 1394 | "type": "library", 1395 | "extra": { 1396 | "branch-alias": { 1397 | "dev-master": "2.2-dev" 1398 | }, 1399 | "thanks": { 1400 | "name": "symfony/contracts", 1401 | "url": "https://github.com/symfony/contracts" 1402 | } 1403 | }, 1404 | "autoload": { 1405 | "psr-4": { 1406 | "Symfony\\Contracts\\Service\\": "" 1407 | } 1408 | }, 1409 | "notification-url": "https://packagist.org/downloads/", 1410 | "license": [ 1411 | "MIT" 1412 | ], 1413 | "authors": [ 1414 | { 1415 | "name": "Nicolas Grekas", 1416 | "email": "p@tchwork.com" 1417 | }, 1418 | { 1419 | "name": "Symfony Community", 1420 | "homepage": "https://symfony.com/contributors" 1421 | } 1422 | ], 1423 | "description": "Generic abstractions related to writing services", 1424 | "homepage": "https://symfony.com", 1425 | "keywords": [ 1426 | "abstractions", 1427 | "contracts", 1428 | "decoupling", 1429 | "interfaces", 1430 | "interoperability", 1431 | "standards" 1432 | ], 1433 | "time": "2020-09-07T11:33:47+00:00" 1434 | } 1435 | ], 1436 | "packages-dev": [ 1437 | { 1438 | "name": "doctrine/instantiator", 1439 | "version": "1.4.0", 1440 | "source": { 1441 | "type": "git", 1442 | "url": "https://github.com/doctrine/instantiator.git", 1443 | "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" 1444 | }, 1445 | "dist": { 1446 | "type": "zip", 1447 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", 1448 | "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", 1449 | "shasum": "" 1450 | }, 1451 | "require": { 1452 | "php": "^7.1 || ^8.0" 1453 | }, 1454 | "require-dev": { 1455 | "doctrine/coding-standard": "^8.0", 1456 | "ext-pdo": "*", 1457 | "ext-phar": "*", 1458 | "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", 1459 | "phpstan/phpstan": "^0.12", 1460 | "phpstan/phpstan-phpunit": "^0.12", 1461 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 1462 | }, 1463 | "type": "library", 1464 | "autoload": { 1465 | "psr-4": { 1466 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 1467 | } 1468 | }, 1469 | "notification-url": "https://packagist.org/downloads/", 1470 | "license": [ 1471 | "MIT" 1472 | ], 1473 | "authors": [ 1474 | { 1475 | "name": "Marco Pivetta", 1476 | "email": "ocramius@gmail.com", 1477 | "homepage": "https://ocramius.github.io/" 1478 | } 1479 | ], 1480 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 1481 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 1482 | "keywords": [ 1483 | "constructor", 1484 | "instantiate" 1485 | ], 1486 | "funding": [ 1487 | { 1488 | "url": "https://www.doctrine-project.org/sponsorship.html", 1489 | "type": "custom" 1490 | }, 1491 | { 1492 | "url": "https://www.patreon.com/phpdoctrine", 1493 | "type": "patreon" 1494 | }, 1495 | { 1496 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 1497 | "type": "tidelift" 1498 | } 1499 | ], 1500 | "time": "2020-11-10T18:47:58+00:00" 1501 | }, 1502 | { 1503 | "name": "myclabs/deep-copy", 1504 | "version": "1.10.2", 1505 | "source": { 1506 | "type": "git", 1507 | "url": "https://github.com/myclabs/DeepCopy.git", 1508 | "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" 1509 | }, 1510 | "dist": { 1511 | "type": "zip", 1512 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", 1513 | "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", 1514 | "shasum": "" 1515 | }, 1516 | "require": { 1517 | "php": "^7.1 || ^8.0" 1518 | }, 1519 | "replace": { 1520 | "myclabs/deep-copy": "self.version" 1521 | }, 1522 | "require-dev": { 1523 | "doctrine/collections": "^1.0", 1524 | "doctrine/common": "^2.6", 1525 | "phpunit/phpunit": "^7.1" 1526 | }, 1527 | "type": "library", 1528 | "autoload": { 1529 | "psr-4": { 1530 | "DeepCopy\\": "src/DeepCopy/" 1531 | }, 1532 | "files": [ 1533 | "src/DeepCopy/deep_copy.php" 1534 | ] 1535 | }, 1536 | "notification-url": "https://packagist.org/downloads/", 1537 | "license": [ 1538 | "MIT" 1539 | ], 1540 | "description": "Create deep copies (clones) of your objects", 1541 | "keywords": [ 1542 | "clone", 1543 | "copy", 1544 | "duplicate", 1545 | "object", 1546 | "object graph" 1547 | ], 1548 | "funding": [ 1549 | { 1550 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 1551 | "type": "tidelift" 1552 | } 1553 | ], 1554 | "time": "2020-11-13T09:40:50+00:00" 1555 | }, 1556 | { 1557 | "name": "nikic/php-parser", 1558 | "version": "v4.10.2", 1559 | "source": { 1560 | "type": "git", 1561 | "url": "https://github.com/nikic/PHP-Parser.git", 1562 | "reference": "658f1be311a230e0907f5dfe0213742aff0596de" 1563 | }, 1564 | "dist": { 1565 | "type": "zip", 1566 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/658f1be311a230e0907f5dfe0213742aff0596de", 1567 | "reference": "658f1be311a230e0907f5dfe0213742aff0596de", 1568 | "shasum": "" 1569 | }, 1570 | "require": { 1571 | "ext-tokenizer": "*", 1572 | "php": ">=7.0" 1573 | }, 1574 | "require-dev": { 1575 | "ircmaxell/php-yacc": "^0.0.7", 1576 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 1577 | }, 1578 | "bin": [ 1579 | "bin/php-parse" 1580 | ], 1581 | "type": "library", 1582 | "extra": { 1583 | "branch-alias": { 1584 | "dev-master": "4.9-dev" 1585 | } 1586 | }, 1587 | "autoload": { 1588 | "psr-4": { 1589 | "PhpParser\\": "lib/PhpParser" 1590 | } 1591 | }, 1592 | "notification-url": "https://packagist.org/downloads/", 1593 | "license": [ 1594 | "BSD-3-Clause" 1595 | ], 1596 | "authors": [ 1597 | { 1598 | "name": "Nikita Popov" 1599 | } 1600 | ], 1601 | "description": "A PHP parser written in PHP", 1602 | "keywords": [ 1603 | "parser", 1604 | "php" 1605 | ], 1606 | "time": "2020-09-26T10:30:38+00:00" 1607 | }, 1608 | { 1609 | "name": "phar-io/manifest", 1610 | "version": "2.0.1", 1611 | "source": { 1612 | "type": "git", 1613 | "url": "https://github.com/phar-io/manifest.git", 1614 | "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" 1615 | }, 1616 | "dist": { 1617 | "type": "zip", 1618 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", 1619 | "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", 1620 | "shasum": "" 1621 | }, 1622 | "require": { 1623 | "ext-dom": "*", 1624 | "ext-phar": "*", 1625 | "ext-xmlwriter": "*", 1626 | "phar-io/version": "^3.0.1", 1627 | "php": "^7.2 || ^8.0" 1628 | }, 1629 | "type": "library", 1630 | "extra": { 1631 | "branch-alias": { 1632 | "dev-master": "2.0.x-dev" 1633 | } 1634 | }, 1635 | "autoload": { 1636 | "classmap": [ 1637 | "src/" 1638 | ] 1639 | }, 1640 | "notification-url": "https://packagist.org/downloads/", 1641 | "license": [ 1642 | "BSD-3-Clause" 1643 | ], 1644 | "authors": [ 1645 | { 1646 | "name": "Arne Blankerts", 1647 | "email": "arne@blankerts.de", 1648 | "role": "Developer" 1649 | }, 1650 | { 1651 | "name": "Sebastian Heuer", 1652 | "email": "sebastian@phpeople.de", 1653 | "role": "Developer" 1654 | }, 1655 | { 1656 | "name": "Sebastian Bergmann", 1657 | "email": "sebastian@phpunit.de", 1658 | "role": "Developer" 1659 | } 1660 | ], 1661 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 1662 | "time": "2020-06-27T14:33:11+00:00" 1663 | }, 1664 | { 1665 | "name": "phar-io/version", 1666 | "version": "3.0.2", 1667 | "source": { 1668 | "type": "git", 1669 | "url": "https://github.com/phar-io/version.git", 1670 | "reference": "c6bb6825def89e0a32220f88337f8ceaf1975fa0" 1671 | }, 1672 | "dist": { 1673 | "type": "zip", 1674 | "url": "https://api.github.com/repos/phar-io/version/zipball/c6bb6825def89e0a32220f88337f8ceaf1975fa0", 1675 | "reference": "c6bb6825def89e0a32220f88337f8ceaf1975fa0", 1676 | "shasum": "" 1677 | }, 1678 | "require": { 1679 | "php": "^7.2 || ^8.0" 1680 | }, 1681 | "type": "library", 1682 | "autoload": { 1683 | "classmap": [ 1684 | "src/" 1685 | ] 1686 | }, 1687 | "notification-url": "https://packagist.org/downloads/", 1688 | "license": [ 1689 | "BSD-3-Clause" 1690 | ], 1691 | "authors": [ 1692 | { 1693 | "name": "Arne Blankerts", 1694 | "email": "arne@blankerts.de", 1695 | "role": "Developer" 1696 | }, 1697 | { 1698 | "name": "Sebastian Heuer", 1699 | "email": "sebastian@phpeople.de", 1700 | "role": "Developer" 1701 | }, 1702 | { 1703 | "name": "Sebastian Bergmann", 1704 | "email": "sebastian@phpunit.de", 1705 | "role": "Developer" 1706 | } 1707 | ], 1708 | "description": "Library for handling version information and constraints", 1709 | "time": "2020-06-27T14:39:04+00:00" 1710 | }, 1711 | { 1712 | "name": "phpdocumentor/reflection-common", 1713 | "version": "2.2.0", 1714 | "source": { 1715 | "type": "git", 1716 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 1717 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 1718 | }, 1719 | "dist": { 1720 | "type": "zip", 1721 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 1722 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 1723 | "shasum": "" 1724 | }, 1725 | "require": { 1726 | "php": "^7.2 || ^8.0" 1727 | }, 1728 | "type": "library", 1729 | "extra": { 1730 | "branch-alias": { 1731 | "dev-2.x": "2.x-dev" 1732 | } 1733 | }, 1734 | "autoload": { 1735 | "psr-4": { 1736 | "phpDocumentor\\Reflection\\": "src/" 1737 | } 1738 | }, 1739 | "notification-url": "https://packagist.org/downloads/", 1740 | "license": [ 1741 | "MIT" 1742 | ], 1743 | "authors": [ 1744 | { 1745 | "name": "Jaap van Otterdijk", 1746 | "email": "opensource@ijaap.nl" 1747 | } 1748 | ], 1749 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 1750 | "homepage": "http://www.phpdoc.org", 1751 | "keywords": [ 1752 | "FQSEN", 1753 | "phpDocumentor", 1754 | "phpdoc", 1755 | "reflection", 1756 | "static analysis" 1757 | ], 1758 | "time": "2020-06-27T09:03:43+00:00" 1759 | }, 1760 | { 1761 | "name": "phpdocumentor/reflection-docblock", 1762 | "version": "5.2.2", 1763 | "source": { 1764 | "type": "git", 1765 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 1766 | "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" 1767 | }, 1768 | "dist": { 1769 | "type": "zip", 1770 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", 1771 | "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", 1772 | "shasum": "" 1773 | }, 1774 | "require": { 1775 | "ext-filter": "*", 1776 | "php": "^7.2 || ^8.0", 1777 | "phpdocumentor/reflection-common": "^2.2", 1778 | "phpdocumentor/type-resolver": "^1.3", 1779 | "webmozart/assert": "^1.9.1" 1780 | }, 1781 | "require-dev": { 1782 | "mockery/mockery": "~1.3.2" 1783 | }, 1784 | "type": "library", 1785 | "extra": { 1786 | "branch-alias": { 1787 | "dev-master": "5.x-dev" 1788 | } 1789 | }, 1790 | "autoload": { 1791 | "psr-4": { 1792 | "phpDocumentor\\Reflection\\": "src" 1793 | } 1794 | }, 1795 | "notification-url": "https://packagist.org/downloads/", 1796 | "license": [ 1797 | "MIT" 1798 | ], 1799 | "authors": [ 1800 | { 1801 | "name": "Mike van Riel", 1802 | "email": "me@mikevanriel.com" 1803 | }, 1804 | { 1805 | "name": "Jaap van Otterdijk", 1806 | "email": "account@ijaap.nl" 1807 | } 1808 | ], 1809 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 1810 | "time": "2020-09-03T19:13:55+00:00" 1811 | }, 1812 | { 1813 | "name": "phpdocumentor/type-resolver", 1814 | "version": "1.4.0", 1815 | "source": { 1816 | "type": "git", 1817 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 1818 | "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" 1819 | }, 1820 | "dist": { 1821 | "type": "zip", 1822 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", 1823 | "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", 1824 | "shasum": "" 1825 | }, 1826 | "require": { 1827 | "php": "^7.2 || ^8.0", 1828 | "phpdocumentor/reflection-common": "^2.0" 1829 | }, 1830 | "require-dev": { 1831 | "ext-tokenizer": "*" 1832 | }, 1833 | "type": "library", 1834 | "extra": { 1835 | "branch-alias": { 1836 | "dev-1.x": "1.x-dev" 1837 | } 1838 | }, 1839 | "autoload": { 1840 | "psr-4": { 1841 | "phpDocumentor\\Reflection\\": "src" 1842 | } 1843 | }, 1844 | "notification-url": "https://packagist.org/downloads/", 1845 | "license": [ 1846 | "MIT" 1847 | ], 1848 | "authors": [ 1849 | { 1850 | "name": "Mike van Riel", 1851 | "email": "me@mikevanriel.com" 1852 | } 1853 | ], 1854 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 1855 | "time": "2020-09-17T18:55:26+00:00" 1856 | }, 1857 | { 1858 | "name": "phpspec/prophecy", 1859 | "version": "1.12.1", 1860 | "source": { 1861 | "type": "git", 1862 | "url": "https://github.com/phpspec/prophecy.git", 1863 | "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d" 1864 | }, 1865 | "dist": { 1866 | "type": "zip", 1867 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/8ce87516be71aae9b956f81906aaf0338e0d8a2d", 1868 | "reference": "8ce87516be71aae9b956f81906aaf0338e0d8a2d", 1869 | "shasum": "" 1870 | }, 1871 | "require": { 1872 | "doctrine/instantiator": "^1.2", 1873 | "php": "^7.2 || ~8.0, <8.1", 1874 | "phpdocumentor/reflection-docblock": "^5.2", 1875 | "sebastian/comparator": "^3.0 || ^4.0", 1876 | "sebastian/recursion-context": "^3.0 || ^4.0" 1877 | }, 1878 | "require-dev": { 1879 | "phpspec/phpspec": "^6.0", 1880 | "phpunit/phpunit": "^8.0 || ^9.0 <9.3" 1881 | }, 1882 | "type": "library", 1883 | "extra": { 1884 | "branch-alias": { 1885 | "dev-master": "1.11.x-dev" 1886 | } 1887 | }, 1888 | "autoload": { 1889 | "psr-4": { 1890 | "Prophecy\\": "src/Prophecy" 1891 | } 1892 | }, 1893 | "notification-url": "https://packagist.org/downloads/", 1894 | "license": [ 1895 | "MIT" 1896 | ], 1897 | "authors": [ 1898 | { 1899 | "name": "Konstantin Kudryashov", 1900 | "email": "ever.zet@gmail.com", 1901 | "homepage": "http://everzet.com" 1902 | }, 1903 | { 1904 | "name": "Marcello Duarte", 1905 | "email": "marcello.duarte@gmail.com" 1906 | } 1907 | ], 1908 | "description": "Highly opinionated mocking framework for PHP 5.3+", 1909 | "homepage": "https://github.com/phpspec/prophecy", 1910 | "keywords": [ 1911 | "Double", 1912 | "Dummy", 1913 | "fake", 1914 | "mock", 1915 | "spy", 1916 | "stub" 1917 | ], 1918 | "time": "2020-09-29T09:10:42+00:00" 1919 | }, 1920 | { 1921 | "name": "phpunit/php-code-coverage", 1922 | "version": "9.2.5", 1923 | "source": { 1924 | "type": "git", 1925 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 1926 | "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" 1927 | }, 1928 | "dist": { 1929 | "type": "zip", 1930 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", 1931 | "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", 1932 | "shasum": "" 1933 | }, 1934 | "require": { 1935 | "ext-dom": "*", 1936 | "ext-libxml": "*", 1937 | "ext-xmlwriter": "*", 1938 | "nikic/php-parser": "^4.10.2", 1939 | "php": ">=7.3", 1940 | "phpunit/php-file-iterator": "^3.0.3", 1941 | "phpunit/php-text-template": "^2.0.2", 1942 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 1943 | "sebastian/complexity": "^2.0", 1944 | "sebastian/environment": "^5.1.2", 1945 | "sebastian/lines-of-code": "^1.0.3", 1946 | "sebastian/version": "^3.0.1", 1947 | "theseer/tokenizer": "^1.2.0" 1948 | }, 1949 | "require-dev": { 1950 | "phpunit/phpunit": "^9.3" 1951 | }, 1952 | "suggest": { 1953 | "ext-pcov": "*", 1954 | "ext-xdebug": "*" 1955 | }, 1956 | "type": "library", 1957 | "extra": { 1958 | "branch-alias": { 1959 | "dev-master": "9.2-dev" 1960 | } 1961 | }, 1962 | "autoload": { 1963 | "classmap": [ 1964 | "src/" 1965 | ] 1966 | }, 1967 | "notification-url": "https://packagist.org/downloads/", 1968 | "license": [ 1969 | "BSD-3-Clause" 1970 | ], 1971 | "authors": [ 1972 | { 1973 | "name": "Sebastian Bergmann", 1974 | "email": "sebastian@phpunit.de", 1975 | "role": "lead" 1976 | } 1977 | ], 1978 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 1979 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 1980 | "keywords": [ 1981 | "coverage", 1982 | "testing", 1983 | "xunit" 1984 | ], 1985 | "funding": [ 1986 | { 1987 | "url": "https://github.com/sebastianbergmann", 1988 | "type": "github" 1989 | } 1990 | ], 1991 | "time": "2020-11-28T06:44:49+00:00" 1992 | }, 1993 | { 1994 | "name": "phpunit/php-file-iterator", 1995 | "version": "3.0.5", 1996 | "source": { 1997 | "type": "git", 1998 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 1999 | "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" 2000 | }, 2001 | "dist": { 2002 | "type": "zip", 2003 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", 2004 | "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", 2005 | "shasum": "" 2006 | }, 2007 | "require": { 2008 | "php": ">=7.3" 2009 | }, 2010 | "require-dev": { 2011 | "phpunit/phpunit": "^9.3" 2012 | }, 2013 | "type": "library", 2014 | "extra": { 2015 | "branch-alias": { 2016 | "dev-master": "3.0-dev" 2017 | } 2018 | }, 2019 | "autoload": { 2020 | "classmap": [ 2021 | "src/" 2022 | ] 2023 | }, 2024 | "notification-url": "https://packagist.org/downloads/", 2025 | "license": [ 2026 | "BSD-3-Clause" 2027 | ], 2028 | "authors": [ 2029 | { 2030 | "name": "Sebastian Bergmann", 2031 | "email": "sebastian@phpunit.de", 2032 | "role": "lead" 2033 | } 2034 | ], 2035 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 2036 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 2037 | "keywords": [ 2038 | "filesystem", 2039 | "iterator" 2040 | ], 2041 | "funding": [ 2042 | { 2043 | "url": "https://github.com/sebastianbergmann", 2044 | "type": "github" 2045 | } 2046 | ], 2047 | "time": "2020-09-28T05:57:25+00:00" 2048 | }, 2049 | { 2050 | "name": "phpunit/php-invoker", 2051 | "version": "3.1.1", 2052 | "source": { 2053 | "type": "git", 2054 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 2055 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 2056 | }, 2057 | "dist": { 2058 | "type": "zip", 2059 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 2060 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 2061 | "shasum": "" 2062 | }, 2063 | "require": { 2064 | "php": ">=7.3" 2065 | }, 2066 | "require-dev": { 2067 | "ext-pcntl": "*", 2068 | "phpunit/phpunit": "^9.3" 2069 | }, 2070 | "suggest": { 2071 | "ext-pcntl": "*" 2072 | }, 2073 | "type": "library", 2074 | "extra": { 2075 | "branch-alias": { 2076 | "dev-master": "3.1-dev" 2077 | } 2078 | }, 2079 | "autoload": { 2080 | "classmap": [ 2081 | "src/" 2082 | ] 2083 | }, 2084 | "notification-url": "https://packagist.org/downloads/", 2085 | "license": [ 2086 | "BSD-3-Clause" 2087 | ], 2088 | "authors": [ 2089 | { 2090 | "name": "Sebastian Bergmann", 2091 | "email": "sebastian@phpunit.de", 2092 | "role": "lead" 2093 | } 2094 | ], 2095 | "description": "Invoke callables with a timeout", 2096 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 2097 | "keywords": [ 2098 | "process" 2099 | ], 2100 | "funding": [ 2101 | { 2102 | "url": "https://github.com/sebastianbergmann", 2103 | "type": "github" 2104 | } 2105 | ], 2106 | "time": "2020-09-28T05:58:55+00:00" 2107 | }, 2108 | { 2109 | "name": "phpunit/php-text-template", 2110 | "version": "2.0.4", 2111 | "source": { 2112 | "type": "git", 2113 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 2114 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 2115 | }, 2116 | "dist": { 2117 | "type": "zip", 2118 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 2119 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 2120 | "shasum": "" 2121 | }, 2122 | "require": { 2123 | "php": ">=7.3" 2124 | }, 2125 | "require-dev": { 2126 | "phpunit/phpunit": "^9.3" 2127 | }, 2128 | "type": "library", 2129 | "extra": { 2130 | "branch-alias": { 2131 | "dev-master": "2.0-dev" 2132 | } 2133 | }, 2134 | "autoload": { 2135 | "classmap": [ 2136 | "src/" 2137 | ] 2138 | }, 2139 | "notification-url": "https://packagist.org/downloads/", 2140 | "license": [ 2141 | "BSD-3-Clause" 2142 | ], 2143 | "authors": [ 2144 | { 2145 | "name": "Sebastian Bergmann", 2146 | "email": "sebastian@phpunit.de", 2147 | "role": "lead" 2148 | } 2149 | ], 2150 | "description": "Simple template engine.", 2151 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 2152 | "keywords": [ 2153 | "template" 2154 | ], 2155 | "funding": [ 2156 | { 2157 | "url": "https://github.com/sebastianbergmann", 2158 | "type": "github" 2159 | } 2160 | ], 2161 | "time": "2020-10-26T05:33:50+00:00" 2162 | }, 2163 | { 2164 | "name": "phpunit/php-timer", 2165 | "version": "5.0.3", 2166 | "source": { 2167 | "type": "git", 2168 | "url": "https://github.com/sebastianbergmann/php-timer.git", 2169 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 2170 | }, 2171 | "dist": { 2172 | "type": "zip", 2173 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 2174 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 2175 | "shasum": "" 2176 | }, 2177 | "require": { 2178 | "php": ">=7.3" 2179 | }, 2180 | "require-dev": { 2181 | "phpunit/phpunit": "^9.3" 2182 | }, 2183 | "type": "library", 2184 | "extra": { 2185 | "branch-alias": { 2186 | "dev-master": "5.0-dev" 2187 | } 2188 | }, 2189 | "autoload": { 2190 | "classmap": [ 2191 | "src/" 2192 | ] 2193 | }, 2194 | "notification-url": "https://packagist.org/downloads/", 2195 | "license": [ 2196 | "BSD-3-Clause" 2197 | ], 2198 | "authors": [ 2199 | { 2200 | "name": "Sebastian Bergmann", 2201 | "email": "sebastian@phpunit.de", 2202 | "role": "lead" 2203 | } 2204 | ], 2205 | "description": "Utility class for timing", 2206 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 2207 | "keywords": [ 2208 | "timer" 2209 | ], 2210 | "funding": [ 2211 | { 2212 | "url": "https://github.com/sebastianbergmann", 2213 | "type": "github" 2214 | } 2215 | ], 2216 | "time": "2020-10-26T13:16:10+00:00" 2217 | }, 2218 | { 2219 | "name": "phpunit/phpunit", 2220 | "version": "9.4.3", 2221 | "source": { 2222 | "type": "git", 2223 | "url": "https://github.com/sebastianbergmann/phpunit.git", 2224 | "reference": "9fa359ff5ddaa5eb2be2bedb08a6a5787a5807ab" 2225 | }, 2226 | "dist": { 2227 | "type": "zip", 2228 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9fa359ff5ddaa5eb2be2bedb08a6a5787a5807ab", 2229 | "reference": "9fa359ff5ddaa5eb2be2bedb08a6a5787a5807ab", 2230 | "shasum": "" 2231 | }, 2232 | "require": { 2233 | "doctrine/instantiator": "^1.3.1", 2234 | "ext-dom": "*", 2235 | "ext-json": "*", 2236 | "ext-libxml": "*", 2237 | "ext-mbstring": "*", 2238 | "ext-xml": "*", 2239 | "ext-xmlwriter": "*", 2240 | "myclabs/deep-copy": "^1.10.1", 2241 | "phar-io/manifest": "^2.0.1", 2242 | "phar-io/version": "^3.0.2", 2243 | "php": ">=7.3", 2244 | "phpspec/prophecy": "^1.12.1", 2245 | "phpunit/php-code-coverage": "^9.2", 2246 | "phpunit/php-file-iterator": "^3.0.5", 2247 | "phpunit/php-invoker": "^3.1.1", 2248 | "phpunit/php-text-template": "^2.0.3", 2249 | "phpunit/php-timer": "^5.0.2", 2250 | "sebastian/cli-parser": "^1.0.1", 2251 | "sebastian/code-unit": "^1.0.6", 2252 | "sebastian/comparator": "^4.0.5", 2253 | "sebastian/diff": "^4.0.3", 2254 | "sebastian/environment": "^5.1.3", 2255 | "sebastian/exporter": "^4.0.3", 2256 | "sebastian/global-state": "^5.0.1", 2257 | "sebastian/object-enumerator": "^4.0.3", 2258 | "sebastian/resource-operations": "^3.0.3", 2259 | "sebastian/type": "^2.3", 2260 | "sebastian/version": "^3.0.2" 2261 | }, 2262 | "require-dev": { 2263 | "ext-pdo": "*", 2264 | "phpspec/prophecy-phpunit": "^2.0.1" 2265 | }, 2266 | "suggest": { 2267 | "ext-soap": "*", 2268 | "ext-xdebug": "*" 2269 | }, 2270 | "bin": [ 2271 | "phpunit" 2272 | ], 2273 | "type": "library", 2274 | "extra": { 2275 | "branch-alias": { 2276 | "dev-master": "9.4-dev" 2277 | } 2278 | }, 2279 | "autoload": { 2280 | "classmap": [ 2281 | "src/" 2282 | ], 2283 | "files": [ 2284 | "src/Framework/Assert/Functions.php" 2285 | ] 2286 | }, 2287 | "notification-url": "https://packagist.org/downloads/", 2288 | "license": [ 2289 | "BSD-3-Clause" 2290 | ], 2291 | "authors": [ 2292 | { 2293 | "name": "Sebastian Bergmann", 2294 | "email": "sebastian@phpunit.de", 2295 | "role": "lead" 2296 | } 2297 | ], 2298 | "description": "The PHP Unit Testing framework.", 2299 | "homepage": "https://phpunit.de/", 2300 | "keywords": [ 2301 | "phpunit", 2302 | "testing", 2303 | "xunit" 2304 | ], 2305 | "funding": [ 2306 | { 2307 | "url": "https://phpunit.de/donate.html", 2308 | "type": "custom" 2309 | }, 2310 | { 2311 | "url": "https://github.com/sebastianbergmann", 2312 | "type": "github" 2313 | } 2314 | ], 2315 | "time": "2020-11-10T12:53:30+00:00" 2316 | }, 2317 | { 2318 | "name": "sebastian/cli-parser", 2319 | "version": "1.0.1", 2320 | "source": { 2321 | "type": "git", 2322 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 2323 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 2324 | }, 2325 | "dist": { 2326 | "type": "zip", 2327 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 2328 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 2329 | "shasum": "" 2330 | }, 2331 | "require": { 2332 | "php": ">=7.3" 2333 | }, 2334 | "require-dev": { 2335 | "phpunit/phpunit": "^9.3" 2336 | }, 2337 | "type": "library", 2338 | "extra": { 2339 | "branch-alias": { 2340 | "dev-master": "1.0-dev" 2341 | } 2342 | }, 2343 | "autoload": { 2344 | "classmap": [ 2345 | "src/" 2346 | ] 2347 | }, 2348 | "notification-url": "https://packagist.org/downloads/", 2349 | "license": [ 2350 | "BSD-3-Clause" 2351 | ], 2352 | "authors": [ 2353 | { 2354 | "name": "Sebastian Bergmann", 2355 | "email": "sebastian@phpunit.de", 2356 | "role": "lead" 2357 | } 2358 | ], 2359 | "description": "Library for parsing CLI options", 2360 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 2361 | "funding": [ 2362 | { 2363 | "url": "https://github.com/sebastianbergmann", 2364 | "type": "github" 2365 | } 2366 | ], 2367 | "time": "2020-09-28T06:08:49+00:00" 2368 | }, 2369 | { 2370 | "name": "sebastian/code-unit", 2371 | "version": "1.0.8", 2372 | "source": { 2373 | "type": "git", 2374 | "url": "https://github.com/sebastianbergmann/code-unit.git", 2375 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 2376 | }, 2377 | "dist": { 2378 | "type": "zip", 2379 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 2380 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 2381 | "shasum": "" 2382 | }, 2383 | "require": { 2384 | "php": ">=7.3" 2385 | }, 2386 | "require-dev": { 2387 | "phpunit/phpunit": "^9.3" 2388 | }, 2389 | "type": "library", 2390 | "extra": { 2391 | "branch-alias": { 2392 | "dev-master": "1.0-dev" 2393 | } 2394 | }, 2395 | "autoload": { 2396 | "classmap": [ 2397 | "src/" 2398 | ] 2399 | }, 2400 | "notification-url": "https://packagist.org/downloads/", 2401 | "license": [ 2402 | "BSD-3-Clause" 2403 | ], 2404 | "authors": [ 2405 | { 2406 | "name": "Sebastian Bergmann", 2407 | "email": "sebastian@phpunit.de", 2408 | "role": "lead" 2409 | } 2410 | ], 2411 | "description": "Collection of value objects that represent the PHP code units", 2412 | "homepage": "https://github.com/sebastianbergmann/code-unit", 2413 | "funding": [ 2414 | { 2415 | "url": "https://github.com/sebastianbergmann", 2416 | "type": "github" 2417 | } 2418 | ], 2419 | "time": "2020-10-26T13:08:54+00:00" 2420 | }, 2421 | { 2422 | "name": "sebastian/code-unit-reverse-lookup", 2423 | "version": "2.0.3", 2424 | "source": { 2425 | "type": "git", 2426 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 2427 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 2428 | }, 2429 | "dist": { 2430 | "type": "zip", 2431 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 2432 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 2433 | "shasum": "" 2434 | }, 2435 | "require": { 2436 | "php": ">=7.3" 2437 | }, 2438 | "require-dev": { 2439 | "phpunit/phpunit": "^9.3" 2440 | }, 2441 | "type": "library", 2442 | "extra": { 2443 | "branch-alias": { 2444 | "dev-master": "2.0-dev" 2445 | } 2446 | }, 2447 | "autoload": { 2448 | "classmap": [ 2449 | "src/" 2450 | ] 2451 | }, 2452 | "notification-url": "https://packagist.org/downloads/", 2453 | "license": [ 2454 | "BSD-3-Clause" 2455 | ], 2456 | "authors": [ 2457 | { 2458 | "name": "Sebastian Bergmann", 2459 | "email": "sebastian@phpunit.de" 2460 | } 2461 | ], 2462 | "description": "Looks up which function or method a line of code belongs to", 2463 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 2464 | "funding": [ 2465 | { 2466 | "url": "https://github.com/sebastianbergmann", 2467 | "type": "github" 2468 | } 2469 | ], 2470 | "time": "2020-09-28T05:30:19+00:00" 2471 | }, 2472 | { 2473 | "name": "sebastian/comparator", 2474 | "version": "4.0.6", 2475 | "source": { 2476 | "type": "git", 2477 | "url": "https://github.com/sebastianbergmann/comparator.git", 2478 | "reference": "55f4261989e546dc112258c7a75935a81a7ce382" 2479 | }, 2480 | "dist": { 2481 | "type": "zip", 2482 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", 2483 | "reference": "55f4261989e546dc112258c7a75935a81a7ce382", 2484 | "shasum": "" 2485 | }, 2486 | "require": { 2487 | "php": ">=7.3", 2488 | "sebastian/diff": "^4.0", 2489 | "sebastian/exporter": "^4.0" 2490 | }, 2491 | "require-dev": { 2492 | "phpunit/phpunit": "^9.3" 2493 | }, 2494 | "type": "library", 2495 | "extra": { 2496 | "branch-alias": { 2497 | "dev-master": "4.0-dev" 2498 | } 2499 | }, 2500 | "autoload": { 2501 | "classmap": [ 2502 | "src/" 2503 | ] 2504 | }, 2505 | "notification-url": "https://packagist.org/downloads/", 2506 | "license": [ 2507 | "BSD-3-Clause" 2508 | ], 2509 | "authors": [ 2510 | { 2511 | "name": "Sebastian Bergmann", 2512 | "email": "sebastian@phpunit.de" 2513 | }, 2514 | { 2515 | "name": "Jeff Welch", 2516 | "email": "whatthejeff@gmail.com" 2517 | }, 2518 | { 2519 | "name": "Volker Dusch", 2520 | "email": "github@wallbash.com" 2521 | }, 2522 | { 2523 | "name": "Bernhard Schussek", 2524 | "email": "bschussek@2bepublished.at" 2525 | } 2526 | ], 2527 | "description": "Provides the functionality to compare PHP values for equality", 2528 | "homepage": "https://github.com/sebastianbergmann/comparator", 2529 | "keywords": [ 2530 | "comparator", 2531 | "compare", 2532 | "equality" 2533 | ], 2534 | "funding": [ 2535 | { 2536 | "url": "https://github.com/sebastianbergmann", 2537 | "type": "github" 2538 | } 2539 | ], 2540 | "time": "2020-10-26T15:49:45+00:00" 2541 | }, 2542 | { 2543 | "name": "sebastian/complexity", 2544 | "version": "2.0.2", 2545 | "source": { 2546 | "type": "git", 2547 | "url": "https://github.com/sebastianbergmann/complexity.git", 2548 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" 2549 | }, 2550 | "dist": { 2551 | "type": "zip", 2552 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", 2553 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", 2554 | "shasum": "" 2555 | }, 2556 | "require": { 2557 | "nikic/php-parser": "^4.7", 2558 | "php": ">=7.3" 2559 | }, 2560 | "require-dev": { 2561 | "phpunit/phpunit": "^9.3" 2562 | }, 2563 | "type": "library", 2564 | "extra": { 2565 | "branch-alias": { 2566 | "dev-master": "2.0-dev" 2567 | } 2568 | }, 2569 | "autoload": { 2570 | "classmap": [ 2571 | "src/" 2572 | ] 2573 | }, 2574 | "notification-url": "https://packagist.org/downloads/", 2575 | "license": [ 2576 | "BSD-3-Clause" 2577 | ], 2578 | "authors": [ 2579 | { 2580 | "name": "Sebastian Bergmann", 2581 | "email": "sebastian@phpunit.de", 2582 | "role": "lead" 2583 | } 2584 | ], 2585 | "description": "Library for calculating the complexity of PHP code units", 2586 | "homepage": "https://github.com/sebastianbergmann/complexity", 2587 | "funding": [ 2588 | { 2589 | "url": "https://github.com/sebastianbergmann", 2590 | "type": "github" 2591 | } 2592 | ], 2593 | "time": "2020-10-26T15:52:27+00:00" 2594 | }, 2595 | { 2596 | "name": "sebastian/diff", 2597 | "version": "4.0.4", 2598 | "source": { 2599 | "type": "git", 2600 | "url": "https://github.com/sebastianbergmann/diff.git", 2601 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" 2602 | }, 2603 | "dist": { 2604 | "type": "zip", 2605 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", 2606 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", 2607 | "shasum": "" 2608 | }, 2609 | "require": { 2610 | "php": ">=7.3" 2611 | }, 2612 | "require-dev": { 2613 | "phpunit/phpunit": "^9.3", 2614 | "symfony/process": "^4.2 || ^5" 2615 | }, 2616 | "type": "library", 2617 | "extra": { 2618 | "branch-alias": { 2619 | "dev-master": "4.0-dev" 2620 | } 2621 | }, 2622 | "autoload": { 2623 | "classmap": [ 2624 | "src/" 2625 | ] 2626 | }, 2627 | "notification-url": "https://packagist.org/downloads/", 2628 | "license": [ 2629 | "BSD-3-Clause" 2630 | ], 2631 | "authors": [ 2632 | { 2633 | "name": "Sebastian Bergmann", 2634 | "email": "sebastian@phpunit.de" 2635 | }, 2636 | { 2637 | "name": "Kore Nordmann", 2638 | "email": "mail@kore-nordmann.de" 2639 | } 2640 | ], 2641 | "description": "Diff implementation", 2642 | "homepage": "https://github.com/sebastianbergmann/diff", 2643 | "keywords": [ 2644 | "diff", 2645 | "udiff", 2646 | "unidiff", 2647 | "unified diff" 2648 | ], 2649 | "funding": [ 2650 | { 2651 | "url": "https://github.com/sebastianbergmann", 2652 | "type": "github" 2653 | } 2654 | ], 2655 | "time": "2020-10-26T13:10:38+00:00" 2656 | }, 2657 | { 2658 | "name": "sebastian/environment", 2659 | "version": "5.1.3", 2660 | "source": { 2661 | "type": "git", 2662 | "url": "https://github.com/sebastianbergmann/environment.git", 2663 | "reference": "388b6ced16caa751030f6a69e588299fa09200ac" 2664 | }, 2665 | "dist": { 2666 | "type": "zip", 2667 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", 2668 | "reference": "388b6ced16caa751030f6a69e588299fa09200ac", 2669 | "shasum": "" 2670 | }, 2671 | "require": { 2672 | "php": ">=7.3" 2673 | }, 2674 | "require-dev": { 2675 | "phpunit/phpunit": "^9.3" 2676 | }, 2677 | "suggest": { 2678 | "ext-posix": "*" 2679 | }, 2680 | "type": "library", 2681 | "extra": { 2682 | "branch-alias": { 2683 | "dev-master": "5.1-dev" 2684 | } 2685 | }, 2686 | "autoload": { 2687 | "classmap": [ 2688 | "src/" 2689 | ] 2690 | }, 2691 | "notification-url": "https://packagist.org/downloads/", 2692 | "license": [ 2693 | "BSD-3-Clause" 2694 | ], 2695 | "authors": [ 2696 | { 2697 | "name": "Sebastian Bergmann", 2698 | "email": "sebastian@phpunit.de" 2699 | } 2700 | ], 2701 | "description": "Provides functionality to handle HHVM/PHP environments", 2702 | "homepage": "http://www.github.com/sebastianbergmann/environment", 2703 | "keywords": [ 2704 | "Xdebug", 2705 | "environment", 2706 | "hhvm" 2707 | ], 2708 | "funding": [ 2709 | { 2710 | "url": "https://github.com/sebastianbergmann", 2711 | "type": "github" 2712 | } 2713 | ], 2714 | "time": "2020-09-28T05:52:38+00:00" 2715 | }, 2716 | { 2717 | "name": "sebastian/exporter", 2718 | "version": "4.0.3", 2719 | "source": { 2720 | "type": "git", 2721 | "url": "https://github.com/sebastianbergmann/exporter.git", 2722 | "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" 2723 | }, 2724 | "dist": { 2725 | "type": "zip", 2726 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", 2727 | "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", 2728 | "shasum": "" 2729 | }, 2730 | "require": { 2731 | "php": ">=7.3", 2732 | "sebastian/recursion-context": "^4.0" 2733 | }, 2734 | "require-dev": { 2735 | "ext-mbstring": "*", 2736 | "phpunit/phpunit": "^9.3" 2737 | }, 2738 | "type": "library", 2739 | "extra": { 2740 | "branch-alias": { 2741 | "dev-master": "4.0-dev" 2742 | } 2743 | }, 2744 | "autoload": { 2745 | "classmap": [ 2746 | "src/" 2747 | ] 2748 | }, 2749 | "notification-url": "https://packagist.org/downloads/", 2750 | "license": [ 2751 | "BSD-3-Clause" 2752 | ], 2753 | "authors": [ 2754 | { 2755 | "name": "Sebastian Bergmann", 2756 | "email": "sebastian@phpunit.de" 2757 | }, 2758 | { 2759 | "name": "Jeff Welch", 2760 | "email": "whatthejeff@gmail.com" 2761 | }, 2762 | { 2763 | "name": "Volker Dusch", 2764 | "email": "github@wallbash.com" 2765 | }, 2766 | { 2767 | "name": "Adam Harvey", 2768 | "email": "aharvey@php.net" 2769 | }, 2770 | { 2771 | "name": "Bernhard Schussek", 2772 | "email": "bschussek@gmail.com" 2773 | } 2774 | ], 2775 | "description": "Provides the functionality to export PHP variables for visualization", 2776 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 2777 | "keywords": [ 2778 | "export", 2779 | "exporter" 2780 | ], 2781 | "funding": [ 2782 | { 2783 | "url": "https://github.com/sebastianbergmann", 2784 | "type": "github" 2785 | } 2786 | ], 2787 | "time": "2020-09-28T05:24:23+00:00" 2788 | }, 2789 | { 2790 | "name": "sebastian/global-state", 2791 | "version": "5.0.2", 2792 | "source": { 2793 | "type": "git", 2794 | "url": "https://github.com/sebastianbergmann/global-state.git", 2795 | "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" 2796 | }, 2797 | "dist": { 2798 | "type": "zip", 2799 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", 2800 | "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", 2801 | "shasum": "" 2802 | }, 2803 | "require": { 2804 | "php": ">=7.3", 2805 | "sebastian/object-reflector": "^2.0", 2806 | "sebastian/recursion-context": "^4.0" 2807 | }, 2808 | "require-dev": { 2809 | "ext-dom": "*", 2810 | "phpunit/phpunit": "^9.3" 2811 | }, 2812 | "suggest": { 2813 | "ext-uopz": "*" 2814 | }, 2815 | "type": "library", 2816 | "extra": { 2817 | "branch-alias": { 2818 | "dev-master": "5.0-dev" 2819 | } 2820 | }, 2821 | "autoload": { 2822 | "classmap": [ 2823 | "src/" 2824 | ] 2825 | }, 2826 | "notification-url": "https://packagist.org/downloads/", 2827 | "license": [ 2828 | "BSD-3-Clause" 2829 | ], 2830 | "authors": [ 2831 | { 2832 | "name": "Sebastian Bergmann", 2833 | "email": "sebastian@phpunit.de" 2834 | } 2835 | ], 2836 | "description": "Snapshotting of global state", 2837 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 2838 | "keywords": [ 2839 | "global state" 2840 | ], 2841 | "funding": [ 2842 | { 2843 | "url": "https://github.com/sebastianbergmann", 2844 | "type": "github" 2845 | } 2846 | ], 2847 | "time": "2020-10-26T15:55:19+00:00" 2848 | }, 2849 | { 2850 | "name": "sebastian/lines-of-code", 2851 | "version": "1.0.3", 2852 | "source": { 2853 | "type": "git", 2854 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 2855 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" 2856 | }, 2857 | "dist": { 2858 | "type": "zip", 2859 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", 2860 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", 2861 | "shasum": "" 2862 | }, 2863 | "require": { 2864 | "nikic/php-parser": "^4.6", 2865 | "php": ">=7.3" 2866 | }, 2867 | "require-dev": { 2868 | "phpunit/phpunit": "^9.3" 2869 | }, 2870 | "type": "library", 2871 | "extra": { 2872 | "branch-alias": { 2873 | "dev-master": "1.0-dev" 2874 | } 2875 | }, 2876 | "autoload": { 2877 | "classmap": [ 2878 | "src/" 2879 | ] 2880 | }, 2881 | "notification-url": "https://packagist.org/downloads/", 2882 | "license": [ 2883 | "BSD-3-Clause" 2884 | ], 2885 | "authors": [ 2886 | { 2887 | "name": "Sebastian Bergmann", 2888 | "email": "sebastian@phpunit.de", 2889 | "role": "lead" 2890 | } 2891 | ], 2892 | "description": "Library for counting the lines of code in PHP source code", 2893 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 2894 | "funding": [ 2895 | { 2896 | "url": "https://github.com/sebastianbergmann", 2897 | "type": "github" 2898 | } 2899 | ], 2900 | "time": "2020-11-28T06:42:11+00:00" 2901 | }, 2902 | { 2903 | "name": "sebastian/object-enumerator", 2904 | "version": "4.0.4", 2905 | "source": { 2906 | "type": "git", 2907 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 2908 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 2909 | }, 2910 | "dist": { 2911 | "type": "zip", 2912 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 2913 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 2914 | "shasum": "" 2915 | }, 2916 | "require": { 2917 | "php": ">=7.3", 2918 | "sebastian/object-reflector": "^2.0", 2919 | "sebastian/recursion-context": "^4.0" 2920 | }, 2921 | "require-dev": { 2922 | "phpunit/phpunit": "^9.3" 2923 | }, 2924 | "type": "library", 2925 | "extra": { 2926 | "branch-alias": { 2927 | "dev-master": "4.0-dev" 2928 | } 2929 | }, 2930 | "autoload": { 2931 | "classmap": [ 2932 | "src/" 2933 | ] 2934 | }, 2935 | "notification-url": "https://packagist.org/downloads/", 2936 | "license": [ 2937 | "BSD-3-Clause" 2938 | ], 2939 | "authors": [ 2940 | { 2941 | "name": "Sebastian Bergmann", 2942 | "email": "sebastian@phpunit.de" 2943 | } 2944 | ], 2945 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 2946 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 2947 | "funding": [ 2948 | { 2949 | "url": "https://github.com/sebastianbergmann", 2950 | "type": "github" 2951 | } 2952 | ], 2953 | "time": "2020-10-26T13:12:34+00:00" 2954 | }, 2955 | { 2956 | "name": "sebastian/object-reflector", 2957 | "version": "2.0.4", 2958 | "source": { 2959 | "type": "git", 2960 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 2961 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 2962 | }, 2963 | "dist": { 2964 | "type": "zip", 2965 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 2966 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 2967 | "shasum": "" 2968 | }, 2969 | "require": { 2970 | "php": ">=7.3" 2971 | }, 2972 | "require-dev": { 2973 | "phpunit/phpunit": "^9.3" 2974 | }, 2975 | "type": "library", 2976 | "extra": { 2977 | "branch-alias": { 2978 | "dev-master": "2.0-dev" 2979 | } 2980 | }, 2981 | "autoload": { 2982 | "classmap": [ 2983 | "src/" 2984 | ] 2985 | }, 2986 | "notification-url": "https://packagist.org/downloads/", 2987 | "license": [ 2988 | "BSD-3-Clause" 2989 | ], 2990 | "authors": [ 2991 | { 2992 | "name": "Sebastian Bergmann", 2993 | "email": "sebastian@phpunit.de" 2994 | } 2995 | ], 2996 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 2997 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 2998 | "funding": [ 2999 | { 3000 | "url": "https://github.com/sebastianbergmann", 3001 | "type": "github" 3002 | } 3003 | ], 3004 | "time": "2020-10-26T13:14:26+00:00" 3005 | }, 3006 | { 3007 | "name": "sebastian/recursion-context", 3008 | "version": "4.0.4", 3009 | "source": { 3010 | "type": "git", 3011 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 3012 | "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" 3013 | }, 3014 | "dist": { 3015 | "type": "zip", 3016 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", 3017 | "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", 3018 | "shasum": "" 3019 | }, 3020 | "require": { 3021 | "php": ">=7.3" 3022 | }, 3023 | "require-dev": { 3024 | "phpunit/phpunit": "^9.3" 3025 | }, 3026 | "type": "library", 3027 | "extra": { 3028 | "branch-alias": { 3029 | "dev-master": "4.0-dev" 3030 | } 3031 | }, 3032 | "autoload": { 3033 | "classmap": [ 3034 | "src/" 3035 | ] 3036 | }, 3037 | "notification-url": "https://packagist.org/downloads/", 3038 | "license": [ 3039 | "BSD-3-Clause" 3040 | ], 3041 | "authors": [ 3042 | { 3043 | "name": "Sebastian Bergmann", 3044 | "email": "sebastian@phpunit.de" 3045 | }, 3046 | { 3047 | "name": "Jeff Welch", 3048 | "email": "whatthejeff@gmail.com" 3049 | }, 3050 | { 3051 | "name": "Adam Harvey", 3052 | "email": "aharvey@php.net" 3053 | } 3054 | ], 3055 | "description": "Provides functionality to recursively process PHP variables", 3056 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 3057 | "funding": [ 3058 | { 3059 | "url": "https://github.com/sebastianbergmann", 3060 | "type": "github" 3061 | } 3062 | ], 3063 | "time": "2020-10-26T13:17:30+00:00" 3064 | }, 3065 | { 3066 | "name": "sebastian/resource-operations", 3067 | "version": "3.0.3", 3068 | "source": { 3069 | "type": "git", 3070 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 3071 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 3072 | }, 3073 | "dist": { 3074 | "type": "zip", 3075 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 3076 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 3077 | "shasum": "" 3078 | }, 3079 | "require": { 3080 | "php": ">=7.3" 3081 | }, 3082 | "require-dev": { 3083 | "phpunit/phpunit": "^9.0" 3084 | }, 3085 | "type": "library", 3086 | "extra": { 3087 | "branch-alias": { 3088 | "dev-master": "3.0-dev" 3089 | } 3090 | }, 3091 | "autoload": { 3092 | "classmap": [ 3093 | "src/" 3094 | ] 3095 | }, 3096 | "notification-url": "https://packagist.org/downloads/", 3097 | "license": [ 3098 | "BSD-3-Clause" 3099 | ], 3100 | "authors": [ 3101 | { 3102 | "name": "Sebastian Bergmann", 3103 | "email": "sebastian@phpunit.de" 3104 | } 3105 | ], 3106 | "description": "Provides a list of PHP built-in functions that operate on resources", 3107 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 3108 | "funding": [ 3109 | { 3110 | "url": "https://github.com/sebastianbergmann", 3111 | "type": "github" 3112 | } 3113 | ], 3114 | "time": "2020-09-28T06:45:17+00:00" 3115 | }, 3116 | { 3117 | "name": "sebastian/type", 3118 | "version": "2.3.1", 3119 | "source": { 3120 | "type": "git", 3121 | "url": "https://github.com/sebastianbergmann/type.git", 3122 | "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" 3123 | }, 3124 | "dist": { 3125 | "type": "zip", 3126 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", 3127 | "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", 3128 | "shasum": "" 3129 | }, 3130 | "require": { 3131 | "php": ">=7.3" 3132 | }, 3133 | "require-dev": { 3134 | "phpunit/phpunit": "^9.3" 3135 | }, 3136 | "type": "library", 3137 | "extra": { 3138 | "branch-alias": { 3139 | "dev-master": "2.3-dev" 3140 | } 3141 | }, 3142 | "autoload": { 3143 | "classmap": [ 3144 | "src/" 3145 | ] 3146 | }, 3147 | "notification-url": "https://packagist.org/downloads/", 3148 | "license": [ 3149 | "BSD-3-Clause" 3150 | ], 3151 | "authors": [ 3152 | { 3153 | "name": "Sebastian Bergmann", 3154 | "email": "sebastian@phpunit.de", 3155 | "role": "lead" 3156 | } 3157 | ], 3158 | "description": "Collection of value objects that represent the types of the PHP type system", 3159 | "homepage": "https://github.com/sebastianbergmann/type", 3160 | "funding": [ 3161 | { 3162 | "url": "https://github.com/sebastianbergmann", 3163 | "type": "github" 3164 | } 3165 | ], 3166 | "time": "2020-10-26T13:18:59+00:00" 3167 | }, 3168 | { 3169 | "name": "sebastian/version", 3170 | "version": "3.0.2", 3171 | "source": { 3172 | "type": "git", 3173 | "url": "https://github.com/sebastianbergmann/version.git", 3174 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 3175 | }, 3176 | "dist": { 3177 | "type": "zip", 3178 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 3179 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 3180 | "shasum": "" 3181 | }, 3182 | "require": { 3183 | "php": ">=7.3" 3184 | }, 3185 | "type": "library", 3186 | "extra": { 3187 | "branch-alias": { 3188 | "dev-master": "3.0-dev" 3189 | } 3190 | }, 3191 | "autoload": { 3192 | "classmap": [ 3193 | "src/" 3194 | ] 3195 | }, 3196 | "notification-url": "https://packagist.org/downloads/", 3197 | "license": [ 3198 | "BSD-3-Clause" 3199 | ], 3200 | "authors": [ 3201 | { 3202 | "name": "Sebastian Bergmann", 3203 | "email": "sebastian@phpunit.de", 3204 | "role": "lead" 3205 | } 3206 | ], 3207 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 3208 | "homepage": "https://github.com/sebastianbergmann/version", 3209 | "funding": [ 3210 | { 3211 | "url": "https://github.com/sebastianbergmann", 3212 | "type": "github" 3213 | } 3214 | ], 3215 | "time": "2020-09-28T06:39:44+00:00" 3216 | }, 3217 | { 3218 | "name": "theseer/tokenizer", 3219 | "version": "1.2.0", 3220 | "source": { 3221 | "type": "git", 3222 | "url": "https://github.com/theseer/tokenizer.git", 3223 | "reference": "75a63c33a8577608444246075ea0af0d052e452a" 3224 | }, 3225 | "dist": { 3226 | "type": "zip", 3227 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", 3228 | "reference": "75a63c33a8577608444246075ea0af0d052e452a", 3229 | "shasum": "" 3230 | }, 3231 | "require": { 3232 | "ext-dom": "*", 3233 | "ext-tokenizer": "*", 3234 | "ext-xmlwriter": "*", 3235 | "php": "^7.2 || ^8.0" 3236 | }, 3237 | "type": "library", 3238 | "autoload": { 3239 | "classmap": [ 3240 | "src/" 3241 | ] 3242 | }, 3243 | "notification-url": "https://packagist.org/downloads/", 3244 | "license": [ 3245 | "BSD-3-Clause" 3246 | ], 3247 | "authors": [ 3248 | { 3249 | "name": "Arne Blankerts", 3250 | "email": "arne@blankerts.de", 3251 | "role": "Developer" 3252 | } 3253 | ], 3254 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 3255 | "time": "2020-07-12T23:59:07+00:00" 3256 | }, 3257 | { 3258 | "name": "webmozart/assert", 3259 | "version": "1.9.1", 3260 | "source": { 3261 | "type": "git", 3262 | "url": "https://github.com/webmozart/assert.git", 3263 | "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" 3264 | }, 3265 | "dist": { 3266 | "type": "zip", 3267 | "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", 3268 | "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", 3269 | "shasum": "" 3270 | }, 3271 | "require": { 3272 | "php": "^5.3.3 || ^7.0 || ^8.0", 3273 | "symfony/polyfill-ctype": "^1.8" 3274 | }, 3275 | "conflict": { 3276 | "phpstan/phpstan": "<0.12.20", 3277 | "vimeo/psalm": "<3.9.1" 3278 | }, 3279 | "require-dev": { 3280 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" 3281 | }, 3282 | "type": "library", 3283 | "autoload": { 3284 | "psr-4": { 3285 | "Webmozart\\Assert\\": "src/" 3286 | } 3287 | }, 3288 | "notification-url": "https://packagist.org/downloads/", 3289 | "license": [ 3290 | "MIT" 3291 | ], 3292 | "authors": [ 3293 | { 3294 | "name": "Bernhard Schussek", 3295 | "email": "bschussek@gmail.com" 3296 | } 3297 | ], 3298 | "description": "Assertions to validate method input/output with nice error messages.", 3299 | "keywords": [ 3300 | "assert", 3301 | "check", 3302 | "validate" 3303 | ], 3304 | "time": "2020-07-08T17:02:28+00:00" 3305 | } 3306 | ], 3307 | "aliases": [], 3308 | "minimum-stability": "stable", 3309 | "stability-flags": [], 3310 | "prefer-stable": false, 3311 | "prefer-lowest": false, 3312 | "platform": [], 3313 | "platform-dev": [], 3314 | "plugin-api-version": "1.1.0" 3315 | } 3316 | --------------------------------------------------------------------------------