├── .github
├── dependabot.yml
└── workflows
│ ├── code-quality.yml
│ ├── code-style.yml
│ ├── codecov.yml
│ ├── deploy-docs.yml
│ ├── tests.yml
│ └── type-coverage.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── config
└── config.php
├── docs
├── 01-installation.md
├── 02-getting-started.md
├── 03-select.md
├── 04-search.md
├── 05-fuzzy-search.md
├── 06-where-clauses.md
├── 07-order-limit-offset.md
├── 08-cache.md
├── 09-relationships.md
├── 10-fetch-results.md
├── 11-properties.md
├── 12-images.md
├── 90-webhooks.md
├── art
│ ├── .gitkeep
│ └── cover.png
└── index.md
├── phpstan-baseline.neon
├── phpstan.neon
├── phpunit.xml
├── pint.json
├── rector.php
├── routes
└── web.php
├── src
├── ApiHelper.php
├── Builder.php
├── Client.php
├── Console
│ ├── CreateWebhook.php
│ ├── DeleteWebhook.php
│ ├── ListWebhooks.php
│ ├── PublishCommand.php
│ └── ReactivateWebhook.php
├── Enums
│ ├── AgeRating
│ │ ├── Category.php
│ │ └── Rating.php
│ ├── AgeRatingContentDescription
│ │ └── Category.php
│ ├── Character
│ │ ├── Gender.php
│ │ └── Species.php
│ ├── Company
│ │ ├── ChangeDateCategory.php
│ │ └── StartDateCategory.php
│ ├── CompanyWebsite
│ │ └── Category.php
│ ├── ExternalGame
│ │ ├── Category.php
│ │ └── Media.php
│ ├── Game
│ │ ├── Category.php
│ │ └── Status.php
│ ├── GameVersionFeature
│ │ └── Category.php
│ ├── GameVersionFeatureValue
│ │ └── IncludedFeature.php
│ ├── Image
│ │ └── Size.php
│ ├── Platform
│ │ └── Category.php
│ ├── PlatformVersionReleaseDate
│ │ ├── Category.php
│ │ └── Region.php
│ ├── PlatformWebsite
│ │ └── Category.php
│ ├── Popularity
│ │ └── Source.php
│ ├── ReleaseDate
│ │ ├── Category.php
│ │ └── Region.php
│ ├── TagNumbers.php
│ ├── Webhook
│ │ ├── Category.php
│ │ └── Method.php
│ └── Website
│ │ └── Category.php
├── Events
│ ├── AgeRatingContentDescriptionCreated.php
│ ├── AgeRatingContentDescriptionDeleted.php
│ ├── AgeRatingContentDescriptionUpdated.php
│ ├── AgeRatingCreated.php
│ ├── AgeRatingDeleted.php
│ ├── AgeRatingUpdated.php
│ ├── AlternativeNameCreated.php
│ ├── AlternativeNameDeleted.php
│ ├── AlternativeNameUpdated.php
│ ├── ArtworkCreated.php
│ ├── ArtworkDeleted.php
│ ├── ArtworkUpdated.php
│ ├── CharacterCreated.php
│ ├── CharacterDeleted.php
│ ├── CharacterMugShotCreated.php
│ ├── CharacterMugShotDeleted.php
│ ├── CharacterMugShotUpdated.php
│ ├── CharacterUpdated.php
│ ├── CollectionCreated.php
│ ├── CollectionDeleted.php
│ ├── CollectionMembershipCreated.php
│ ├── CollectionMembershipDeleted.php
│ ├── CollectionMembershipTypeCreated.php
│ ├── CollectionMembershipTypeDeleted.php
│ ├── CollectionMembershipTypeUpdated.php
│ ├── CollectionMembershipUpdated.php
│ ├── CollectionRelationCreated.php
│ ├── CollectionRelationDeleted.php
│ ├── CollectionRelationTypeCreated.php
│ ├── CollectionRelationTypeDeleted.php
│ ├── CollectionRelationTypeUpdated.php
│ ├── CollectionRelationUpdated.php
│ ├── CollectionTypeCreated.php
│ ├── CollectionTypeDeleted.php
│ ├── CollectionTypeUpdated.php
│ ├── CollectionUpdated.php
│ ├── CompanyCreated.php
│ ├── CompanyDeleted.php
│ ├── CompanyLogoCreated.php
│ ├── CompanyLogoDeleted.php
│ ├── CompanyLogoUpdated.php
│ ├── CompanyUpdated.php
│ ├── CompanyWebsiteCreated.php
│ ├── CompanyWebsiteDeleted.php
│ ├── CompanyWebsiteUpdated.php
│ ├── CoverCreated.php
│ ├── CoverDeleted.php
│ ├── CoverUpdated.php
│ ├── Event.php
│ ├── EventCreated.php
│ ├── EventDeleted.php
│ ├── EventLogoCreated.php
│ ├── EventLogoDeleted.php
│ ├── EventLogoUpdated.php
│ ├── EventNetworkCreated.php
│ ├── EventNetworkDeleted.php
│ ├── EventNetworkUpdated.php
│ ├── EventUpdated.php
│ ├── ExternalGameCreated.php
│ ├── ExternalGameDeleted.php
│ ├── ExternalGameUpdated.php
│ ├── FranchiseCreated.php
│ ├── FranchiseDeleted.php
│ ├── FranchiseUpdated.php
│ ├── GameCreated.php
│ ├── GameDeleted.php
│ ├── GameEngineCreated.php
│ ├── GameEngineDeleted.php
│ ├── GameEngineLogoCreated.php
│ ├── GameEngineLogoDeleted.php
│ ├── GameEngineLogoUpdated.php
│ ├── GameEngineUpdated.php
│ ├── GameLocalizationCreated.php
│ ├── GameLocalizationDeleted.php
│ ├── GameLocalizationUpdated.php
│ ├── GameModeCreated.php
│ ├── GameModeDeleted.php
│ ├── GameModeUpdated.php
│ ├── GameTimeToBeatCreated.php
│ ├── GameTimeToBeatDeleted.php
│ ├── GameTimeToBeatUpdated.php
│ ├── GameUpdated.php
│ ├── GameVersionCreated.php
│ ├── GameVersionDeleted.php
│ ├── GameVersionFeatureCreated.php
│ ├── GameVersionFeatureDeleted.php
│ ├── GameVersionFeatureUpdated.php
│ ├── GameVersionFeatureValueCreated.php
│ ├── GameVersionFeatureValueDeleted.php
│ ├── GameVersionFeatureValueUpdated.php
│ ├── GameVersionUpdated.php
│ ├── GameVideoCreated.php
│ ├── GameVideoDeleted.php
│ ├── GameVideoUpdated.php
│ ├── GenreCreated.php
│ ├── GenreDeleted.php
│ ├── GenreUpdated.php
│ ├── InvolvedCompanyCreated.php
│ ├── InvolvedCompanyDeleted.php
│ ├── InvolvedCompanyUpdated.php
│ ├── KeywordCreated.php
│ ├── KeywordDeleted.php
│ ├── KeywordUpdated.php
│ ├── LanguageCreated.php
│ ├── LanguageDeleted.php
│ ├── LanguageSupportCreated.php
│ ├── LanguageSupportDeleted.php
│ ├── LanguageSupportTypeCreated.php
│ ├── LanguageSupportTypeDeleted.php
│ ├── LanguageSupportTypeUpdated.php
│ ├── LanguageSupportUpdated.php
│ ├── LanguageUpdated.php
│ ├── MultiplayerModeCreated.php
│ ├── MultiplayerModeDeleted.php
│ ├── MultiplayerModeUpdated.php
│ ├── NetworkTypeCreated.php
│ ├── NetworkTypeDeleted.php
│ ├── NetworkTypeUpdated.php
│ ├── PlatformCreated.php
│ ├── PlatformDeleted.php
│ ├── PlatformFamilyCreated.php
│ ├── PlatformFamilyDeleted.php
│ ├── PlatformFamilyUpdated.php
│ ├── PlatformLogoCreated.php
│ ├── PlatformLogoDeleted.php
│ ├── PlatformLogoUpdated.php
│ ├── PlatformUpdated.php
│ ├── PlatformVersionCompanyCreated.php
│ ├── PlatformVersionCompanyDeleted.php
│ ├── PlatformVersionCompanyUpdated.php
│ ├── PlatformVersionCreated.php
│ ├── PlatformVersionDeleted.php
│ ├── PlatformVersionReleaseDateCreated.php
│ ├── PlatformVersionReleaseDateDeleted.php
│ ├── PlatformVersionReleaseDateUpdated.php
│ ├── PlatformVersionUpdated.php
│ ├── PlatformWebsiteCreated.php
│ ├── PlatformWebsiteDeleted.php
│ ├── PlatformWebsiteUpdated.php
│ ├── PlayerPerspectiveCreated.php
│ ├── PlayerPerspectiveDeleted.php
│ ├── PlayerPerspectiveUpdated.php
│ ├── PopularityTypeCreated.php
│ ├── PopularityTypeDeleted.php
│ ├── PopularityTypeUpdated.php
│ ├── RegionCreated.php
│ ├── RegionDeleted.php
│ ├── RegionUpdated.php
│ ├── ReleaseDateCreated.php
│ ├── ReleaseDateDeleted.php
│ ├── ReleaseDateStatusCreated.php
│ ├── ReleaseDateStatusDeleted.php
│ ├── ReleaseDateStatusUpdated.php
│ ├── ReleaseDateUpdated.php
│ ├── ScreenshotCreated.php
│ ├── ScreenshotDeleted.php
│ ├── ScreenshotUpdated.php
│ ├── ThemeCreated.php
│ ├── ThemeDeleted.php
│ ├── ThemeUpdated.php
│ ├── WebsiteCreated.php
│ ├── WebsiteDeleted.php
│ └── WebsiteUpdated.php
├── Exceptions
│ ├── AuthenticationException.php
│ ├── InvalidParamsException.php
│ ├── InvalidWebhookMethodException.php
│ ├── InvalidWebhookSecretException.php
│ ├── MissingEndpointException.php
│ ├── ModelNotFoundException.php
│ ├── PropertyDoesNotExist.php
│ ├── ServiceException.php
│ ├── ServiceUnavailableException.php
│ ├── UnauthorizedException.php
│ └── WebhookSecretMissingException.php
├── IGDBLaravelServiceProvider.php
├── Models
│ ├── AgeRating.php
│ ├── AgeRatingContentDescription.php
│ ├── AlternativeName.php
│ ├── Artwork.php
│ ├── Character.php
│ ├── CharacterMugShot.php
│ ├── Collection.php
│ ├── CollectionMembership.php
│ ├── CollectionMembershipType.php
│ ├── CollectionRelation.php
│ ├── CollectionRelationType.php
│ ├── CollectionType.php
│ ├── Company.php
│ ├── CompanyLogo.php
│ ├── CompanyWebsite.php
│ ├── Cover.php
│ ├── Event.php
│ ├── EventLogo.php
│ ├── EventNetwork.php
│ ├── ExternalGame.php
│ ├── Franchise.php
│ ├── Game.php
│ ├── GameEngine.php
│ ├── GameEngineLogo.php
│ ├── GameLocalization.php
│ ├── GameMode.php
│ ├── GameTimeToBeat.php
│ ├── GameVersion.php
│ ├── GameVersionFeature.php
│ ├── GameVersionFeatureValue.php
│ ├── GameVideo.php
│ ├── Genre.php
│ ├── Image.php
│ ├── InvolvedCompany.php
│ ├── Keyword.php
│ ├── Language.php
│ ├── LanguageSupport.php
│ ├── LanguageSupportType.php
│ ├── Model.php
│ ├── MultiplayerMode.php
│ ├── NetworkType.php
│ ├── Platform.php
│ ├── PlatformFamily.php
│ ├── PlatformLogo.php
│ ├── PlatformVersion.php
│ ├── PlatformVersionCompany.php
│ ├── PlatformVersionReleaseDate.php
│ ├── PlatformWebsite.php
│ ├── PlayerPerspective.php
│ ├── PopularityPrimitive.php
│ ├── PopularityType.php
│ ├── Region.php
│ ├── ReleaseDate.php
│ ├── ReleaseDateStatus.php
│ ├── Screenshot.php
│ ├── Search.php
│ ├── Theme.php
│ ├── Webhook.php
│ └── Website.php
└── Traits
│ ├── DateCasts.php
│ ├── HasLimits.php
│ ├── HasNestedWhere.php
│ ├── HasSearch.php
│ ├── HasSelect.php
│ ├── HasWhere.php
│ ├── HasWhereBetween.php
│ ├── HasWhereDate.php
│ ├── HasWhereHas.php
│ ├── HasWhereIn.php
│ ├── HasWhereLike.php
│ ├── Operators.php
│ └── ValuePreparer.php
└── tests
├── ApiHelperTest.php
├── BuilderTest.php
├── ConsoleTest.php
├── EventsTest.php
├── ImageTest.php
├── ModelTest.php
├── Pest.php
├── TestCase.php
└── WebhookTest.php
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "composer"
9 | directory: "/"
10 | schedule:
11 | interval: "daily"
12 | assignees:
13 | - "marcreichel"
14 | commit-message:
15 | prefix: "⬆️"
16 | prefix_development: "⬆️"
17 | include_scope: false
18 |
--------------------------------------------------------------------------------
/.github/workflows/code-quality.yml:
--------------------------------------------------------------------------------
1 | name: PHPStan
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | phpstan:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Install dependencies
15 | run: composer install --prefer-dist --no-progress --dev
16 | - run: composer stan -- --error-format=github
17 |
--------------------------------------------------------------------------------
/.github/workflows/code-style.yml:
--------------------------------------------------------------------------------
1 | name: Pint
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | pint:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Install dependencies
15 | run: composer install --prefer-dist --no-progress --dev
16 | - run: composer pint
17 |
--------------------------------------------------------------------------------
/.github/workflows/codecov.yml:
--------------------------------------------------------------------------------
1 | name: Code coverage
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | runs-on: ${{ matrix.os }}
11 |
12 | strategy:
13 | fail-fast: true
14 | matrix:
15 | os: [ubuntu-latest]
16 | php: ['8.2', '8.3', 8.4, '8.4']
17 | laravel: ['11.*', '12.*']
18 | stability: [prefer-lowest]
19 | include:
20 | - laravel: 11.*
21 | testbench: 9.*
22 | - laravel: 12.*
23 | testbench: 10.*
24 |
25 | steps:
26 | - uses: actions/checkout@v2
27 |
28 | - name: Setup PHP
29 | uses: shivammathur/setup-php@v2
30 | with:
31 | php-version: ${{ matrix.php }}
32 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
33 | coverage: xdebug
34 |
35 | - name: Setup problem matchers
36 | run: |
37 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
38 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
39 |
40 | - name: Install dependencies
41 | run: |
42 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
43 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
44 |
45 | - run: composer test:coverage
46 |
47 | - name: Upload
48 | uses: codecov/codecov-action@v1
49 | with:
50 | files: ./build/clover.xml
51 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-docs.yml:
--------------------------------------------------------------------------------
1 | name: Deploy docs
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths: [ docs/**/* ]
7 | workflow_dispatch:
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Deploy documentation
14 | uses: wei/curl@master
15 | with:
16 | args: -X GET ${{ secrets.DEPLOYMENT_URL }}
17 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | run-tests:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | fail-fast: true
17 | matrix:
18 | php: [8.4, 8.3, 8.2]
19 | laravel: ['11.*', '12.*']
20 | stability: [prefer-stable]
21 | include:
22 | - laravel: 11.*
23 | testbench: 9.*
24 | - laravel: 12.*
25 | testbench: 10.*
26 |
27 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - ${{ matrix.stability }}
28 |
29 | steps:
30 | - name: Checkout code
31 | uses: actions/checkout@v2
32 |
33 | - name: Setup PHP
34 | uses: shivammathur/setup-php@v2
35 | with:
36 | php-version: ${{ matrix.php }}
37 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
38 | coverage: xdebug
39 |
40 | - name: Setup problem matchers
41 | run: |
42 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
43 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
44 |
45 | - name: Install dependencies
46 | run: |
47 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
48 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
49 |
50 | - run: composer test
51 |
--------------------------------------------------------------------------------
/.github/workflows/type-coverage.yml:
--------------------------------------------------------------------------------
1 | name: Type Coverage
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | type-coverage:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: Setup PHP
15 | uses: shivammathur/setup-php@v2
16 | with:
17 | php-version: 8.4
18 | coverage: xdebug
19 | - name: Install dependencies
20 | run: composer install --prefer-dist --no-progress --dev
21 | - run: composer test:type-coverage
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | vendor/
3 | build/
4 | report/
5 | composer.lock
6 | .phpunit.cache
7 | .phpunit.result.cache
8 | cghooks.lock
9 | coverage.xml
10 |
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Marc Reichel
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Laravel IGDB Wrapper
2 |
3 |
4 | This is a Laravel wrapper for version 4 of the IGDB API (Apicalypse)
5 | including webhook handling.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 
36 |
37 | ## Basic installation
38 |
39 | You can install this package via composer using:
40 |
41 | ```bash
42 | composer require marcreichel/igdb-laravel
43 | ```
44 |
45 | The package will automatically register its service provider.
46 |
47 | To publish the config file to `config/igdb.php` run:
48 |
49 | ```bash
50 | php artisan igdb:publish
51 | ```
52 |
53 | This is the default content of the config file:
54 |
55 | ```php
56 | return [
57 | /*
58 | * These are the credentials you got from https://dev.twitch.tv/console/apps
59 | */
60 | 'credentials' => [
61 | 'client_id' => env('TWITCH_CLIENT_ID', ''),
62 | 'client_secret' => env('TWITCH_CLIENT_SECRET', ''),
63 | ],
64 |
65 | /*
66 | * This package caches queries automatically (for 1 hour per default).
67 | * Here you can set how long each query should be cached (in seconds).
68 | *
69 | * To turn cache off set this value to 0
70 | */
71 | 'cache_lifetime' => env('IGDB_CACHE_LIFETIME', 3600),
72 |
73 | /**
74 | * The prefix used to cache the results.
75 | *
76 | * E.g.: `[CACHE_PREFIX].75170fc230cd88f32e475ff4087f81d9`
77 | */
78 | 'cache_prefix' => 'igdb_cache',
79 |
80 | /*
81 | * Path where the webhooks should be handled.
82 | */
83 | 'webhook_path' => 'igdb-webhook/handle',
84 |
85 | /*
86 | * The webhook secret.
87 | *
88 | * This needs to be a string of your choice in order to use the webhook
89 | * functionality.
90 | */
91 | 'webhook_secret' => env('IGDB_WEBHOOK_SECRET', null),
92 | ];
93 | ```
94 |
95 | ## Documentation
96 |
97 | You will find the full documentation on [the dedicated documentation site](https://marcreichel.dev/docs/igdb-laravel).
98 |
99 | ## Testing
100 |
101 | Run the tests with:
102 |
103 | ```bash
104 | composer test
105 | ```
106 |
107 | ## Contribution
108 |
109 | Pull requests are welcome :)
110 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "marcreichel/igdb-laravel",
3 | "description": "A Laravel wrapper for version 4 of the IGDB API (Apicalypse) including webhook handling",
4 | "keywords": [
5 | "laravel",
6 | "api-wrapper",
7 | "igdb",
8 | "igdb-api",
9 | "apicalypse",
10 | "wrapper"
11 | ],
12 | "type": "library",
13 | "minimum-stability": "stable",
14 | "require": {
15 | "php": "^8.2",
16 | "ext-json": "*",
17 | "illuminate/support": "^11.0|^12.0",
18 | "guzzlehttp/guzzle": "~6.0|~7.0",
19 | "nesbot/carbon": "^2.53.1|^3.0"
20 | },
21 | "require-dev": {
22 | "orchestra/testbench": "^9.0|^10.0",
23 | "nunomaduro/collision": "^8.0",
24 | "roave/security-advisories": "dev-latest",
25 | "larastan/larastan": "^3.0.2",
26 | "laravel/pint": "^1.13",
27 | "pestphp/pest": "^3.7.4",
28 | "pestphp/pest-plugin-type-coverage": "^3.2.3",
29 | "rector/rector": "^2.0.7"
30 | },
31 | "license": "MIT",
32 | "authors": [
33 | {
34 | "name": "Marc Reichel",
35 | "email": "mail@marcreichel.de"
36 | }
37 | ],
38 | "scripts": {
39 | "pint": "./vendor/bin/pint --test -v",
40 | "test": "./vendor/bin/pest --parallel",
41 | "stan": "./vendor/bin/phpstan --memory-limit=2G",
42 | "test:coverage": [
43 | "@putenv XDEBUG_MODE=coverage",
44 | "@test --coverage --min=90 --coverage-clover build/clover.xml"
45 | ],
46 | "test:coverage-html": [
47 | "@putenv XDEBUG_MODE=coverage",
48 | "@test --coverage --min=90 --coverage-html build/coverage"
49 | ],
50 | "test:type-coverage": [
51 | "@putenv XDEBUG_MODE=coverage",
52 | "@test -- --type-coverage --min=100"
53 | ]
54 | },
55 | "autoload": {
56 | "psr-4": {
57 | "MarcReichel\\IGDBLaravel\\": "src"
58 | }
59 | },
60 | "autoload-dev": {
61 | "psr-4": {
62 | "MarcReichel\\IGDBLaravel\\Tests\\": "tests"
63 | }
64 | },
65 | "extra": {
66 | "laravel": {
67 | "providers": [
68 | "MarcReichel\\IGDBLaravel\\IGDBLaravelServiceProvider"
69 | ]
70 | }
71 | },
72 | "config": {
73 | "allow-plugins": {
74 | "pestphp/pest-plugin": true
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/config/config.php:
--------------------------------------------------------------------------------
1 | [
10 | 'client_id' => env('TWITCH_CLIENT_ID', ''),
11 | 'client_secret' => env('TWITCH_CLIENT_SECRET', ''),
12 | ],
13 |
14 | /**
15 | * This package caches queries automatically (for 1 hour per default).
16 | * Here you can set how long each query should be cached (in seconds).
17 | *
18 | * To turn cache off set this value to 0.
19 | */
20 | 'cache_lifetime' => env('IGDB_CACHE_LIFETIME', 3600),
21 |
22 | /**
23 | * The prefix used to cache the results.
24 | *
25 | * E.g.: `[CACHE_PREFIX].75170fc230cd88f32e475ff4087f81d9`
26 | */
27 | 'cache_prefix' => 'igdb_cache',
28 |
29 | /**
30 | * Path where the webhooks should be handled.
31 | */
32 | 'webhook_path' => 'igdb-webhook/handle',
33 |
34 | /**
35 | * The webhook secret.
36 | *
37 | * This needs to be a string of your choice in order to use the webhook
38 | * functionality.
39 | */
40 | 'webhook_secret' => env('IGDB_WEBHOOK_SECRET'),
41 | ];
42 |
--------------------------------------------------------------------------------
/docs/01-installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Create Twitch Developer App
4 |
5 | [Create](https://dev.twitch.tv/console/apps/create) a new Twitch Developer App and set the `Client ID` and
6 | `Client Secret` as described below.
7 |
8 | ## Install the package
9 |
10 | You can install this package via composer using:
11 |
12 | ```bash
13 | // torchlight! {"lineNumbers": false}
14 | composer require marcreichel/igdb-laravel
15 | ```
16 |
17 | The package will automatically register its service provider.
18 |
19 | To publish the config file to `config/igdb.php` run:
20 |
21 | ```bash
22 | // torchlight! {"lineNumbers": false}
23 | php artisan igdb:publish
24 | ```
25 |
26 | This is the default content of the config file:
27 |
28 | ```php
29 | // torchlight! {"lineNumbers": false}
30 | [
37 | 'client_id' => env('TWITCH_CLIENT_ID', ''),
38 | 'client_secret' => env('TWITCH_CLIENT_SECRET', ''),
39 | ],
40 |
41 | /*
42 | * This package caches queries automatically (for 1 hour per default).
43 | * Here you can set how long each query should be cached (in seconds).
44 | *
45 | * To turn cache off set this value to 0
46 | */
47 | 'cache_lifetime' => env('IGDB_CACHE_LIFETIME', 3600),
48 |
49 | /**
50 | * The prefix used to cache the results.
51 | *
52 | * E.g.: `[CACHE_PREFIX].75170fc230cd88f32e475ff4087f81d9`
53 | */
54 | 'cache_prefix' => 'igdb_cache',
55 |
56 | /*
57 | * Path where the webhooks should be handled.
58 | */
59 | 'webhook_path' => 'igdb-webhook/handle',
60 |
61 | /*
62 | * The webhook secret.
63 | *
64 | * This needs to be a string of your choice in order to use the webhook
65 | * functionality.
66 | */
67 | 'webhook_secret' => env('IGDB_WEBHOOK_SECRET'),
68 | ];
69 | ```
70 |
--------------------------------------------------------------------------------
/docs/02-getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | If you're familiar with the [Eloquent System](https://laravel.com/docs/eloquent) and
4 | the [Query Builder](https://laravel.com/docs/queries) of Laravel you will love this package as it uses a similar
5 | approach.
6 |
7 | ## Models
8 |
9 | Each endpoint of the API is mapped to its own model.
10 |
11 | To get a list of games you simply call something like this:
12 |
13 | ```php
14 | // torchlight! {"lineNumbers": false}
15 | use MarcReichel\IGDBLaravel\Models\Game;
16 |
17 | $games = Game::where('name', 'Fortnite')->get();
18 | ```
19 |
20 | [Here](https://github.com/marcreichel/igdb-laravel/tree/main/src/Models) you can find a list of all available Models.
21 |
22 | When you use one of these models the query results will be mapped into the used model automatically.
23 |
24 | _This method is used in the examples in the documentation._
25 |
26 | ## Query Builder
27 |
28 | You can also use the Query Builder (which is used under the hood) directly if you want to:
29 |
30 | ```php
31 | // torchlight! {"lineNumbers": false}
32 | use MarcReichel\IGDBLaravel\Builder as IGDB;
33 |
34 | $igdb = new IGDB('games'); // 'games' is the endpoint
35 |
36 | $games = $igdb->get();
37 | ```
38 |
39 |
--------------------------------------------------------------------------------
/docs/03-select.md:
--------------------------------------------------------------------------------
1 | # Select (Fields)
2 |
3 | Select which fields should be in the response. If you want to have all available fields in the response you can also
4 | skip this method as the query builder will select `*` by default. (**Attention**: This is the opposite behaviour from
5 | the Apicalypse API)
6 |
7 | ```php
8 | // torchlight! {"lineNumbers": false}
9 | use MarcReichel\IGDBLaravel\Models\Game;
10 |
11 | $games = Game::select(['*'])->get();
12 |
13 | $games = Game::select(['name', 'first_release_date'])->get();
14 | ```
15 |
--------------------------------------------------------------------------------
/docs/04-search.md:
--------------------------------------------------------------------------------
1 | # Search
2 |
3 | ```php
4 | // torchlight! {"lineNumbers": false}
5 | use MarcReichel\IGDBLaravel\Models\Game;
6 |
7 | $games = Game::search('Fortnite')->get();
8 | ```
9 |
10 | ## Searchable models
11 |
12 | - `Character`
13 | - `Collection`
14 | - `Game`
15 | - `Platform`
16 | - `Theme`
17 |
--------------------------------------------------------------------------------
/docs/05-fuzzy-search.md:
--------------------------------------------------------------------------------
1 | # Fuzzy Search
2 |
3 | The fuzzy search (since v3.1.0) acts like a "where like" chain under the hood.
4 |
5 | ```php
6 | // torchlight! {"lineNumbers": false}
7 | use MarcReichel\IGDBLaravel\Models\Game;
8 |
9 | $games = Game::fuzzySearch(
10 | // fields to search in
11 | [
12 | 'name',
13 | 'involved_companies.company.name', // you can search for nested values as well
14 | ],
15 | // the query to search for
16 | 'Call of Duty',
17 | // enable/disable case sensitivity (disabled by default)
18 | false,
19 | )->get();
20 | ```
21 |
22 | **Attention**: Keep in mind you have to do the sorting of the results yourself. They are not ordered by relevance or
23 | any other way.
24 |
--------------------------------------------------------------------------------
/docs/06-where-clauses.md:
--------------------------------------------------------------------------------
1 | # Where clauses
2 |
3 | ## Simple where clause
4 |
5 | ```php
6 | // torchlight! {"lineNumbers": false}
7 | use MarcReichel\IGDBLaravel\Models\Game;
8 |
9 | $games = Game::where('first_release_date', '>=', now()->subMonth())
10 | ->get();
11 | ```
12 |
13 | > **Please note**: `Carbon` objects are supported since v3.4.0.
14 |
15 | For convenience, if you want to verify that a column is equal to a given value, you may pass the value directly as the
16 | second argument to the `where` method:
17 |
18 | ```php
19 | // torchlight! {"lineNumbers": false}
20 | use MarcReichel\IGDBLaravel\Models\Game;
21 |
22 | $games = Game::where('name', 'Fortnite')->get();
23 |
24 | // this is the same as
25 |
26 | $games = Game::where('name', '=', 'Fortnite')->get();
27 | ```
28 |
29 | ## OR statements
30 |
31 | You may chain where constraints together as well as add `or` clauses to the query. The `orWhere` method accepts the same
32 | arguments as the where method:
33 |
34 | ```php
35 | // torchlight! {"lineNumbers": false}
36 | use MarcReichel\IGDBLaravel\Models\Game;
37 |
38 | $games = Game::where('name', 'Fortnite')
39 | ->orWhere('name', 'Borderlands 2')
40 | ->get();
41 | ```
42 |
43 | ## Additional Where Clauses
44 |
45 | ### whereBetween
46 |
47 | The `whereBetween` method verifies that a fields's value is between two values:
48 |
49 | ```php
50 | // torchlight! {"lineNumbers": false}
51 | use MarcReichel\IGDBLaravel\Models\Game;
52 |
53 | $games = Game::whereBetween('first_release_date', now()->subYear(), now())
54 | ->get();
55 | ```
56 |
57 | > **Please note**: `Carbon` objects are supported since v3.4.0.
58 |
59 | ### whereNotBetween
60 |
61 | The `whereNotBetween` method verifies that a field's value lies outside of two
62 | values:
63 |
64 | ```php
65 | // torchlight! {"lineNumbers": false}
66 | use MarcReichel\IGDBLaravel\Models\Game;
67 |
68 | $games = Game::whereNotBetween('first_release_date', now()->subYear(), now())
69 | ->get();
70 | ```
71 |
72 | > **Please note**: `Carbon` objects are supported since v3.4.0.
73 |
74 | ### whereIn
75 |
76 | The `whereIn` method verifies that a given field's value is contained within the
77 | given array:
78 |
79 | ```php
80 | // torchlight! {"lineNumbers": false}
81 | use MarcReichel\IGDBLaravel\Models\Game;
82 |
83 | $games = Game::whereIn('category', [0,4])->get();
84 | ```
85 |
86 | ### whereNotIn
87 |
88 | The `whereNotIn` method verifies that the given field's value is **not**
89 | contained in the given array:
90 |
91 | ```php
92 | // torchlight! {"lineNumbers": false}
93 | use MarcReichel\IGDBLaravel\Models\Game;
94 |
95 | $games = Game::whereNotIn('category', [0,4])->get();
96 | ```
97 |
98 | ### whereInAll / whereNotInAll / whereInExact / whereNotInExact
99 |
100 | Alternatively you could use one of these methods to match against **all** or **exactly** the given array.
101 |
102 | ### whereNull
103 |
104 | The `whereNull` method verifies that the value of the given field is `NULL`:
105 |
106 | ```php
107 | // torchlight! {"lineNumbers": false}
108 | use MarcReichel\IGDBLaravel\Models\Game;
109 |
110 | $games = Game::whereNull('first_release_date')->get();
111 | ```
112 |
113 | ### whereNotNull
114 |
115 | The `whereNotNull` method verifies that the field's value is **not** `NULL`:
116 |
117 | ```php
118 | // torchlight! {"lineNumbers": false}
119 | use MarcReichel\IGDBLaravel\Models\Game;
120 |
121 | $games = Game::whereNotNull('first_release_date')->get();
122 | ```
123 |
124 | ### whereDate
125 |
126 | The `whereDate` method may be used to compare a field's value against a date:
127 |
128 | ```php
129 | // torchlight! {"lineNumbers": false}
130 | use MarcReichel\IGDBLaravel\Models\Game;
131 |
132 | $games = Game::whereDate('first_release_date', '2019-01-01')
133 | ->get();
134 | ```
135 |
136 | ### whereYear
137 |
138 | The `whereYear` method may be used to compare a fields's value against a specific
139 | year:
140 |
141 | ```php
142 | // torchlight! {"lineNumbers": false}
143 | use MarcReichel\IGDBLaravel\Models\Game;
144 |
145 | $games = Game::whereYear('first_release_date', 2019)
146 | ->get();
147 | ```
148 |
149 | ### whereHas / whereHasNot
150 |
151 | These methods have the same syntax as `whereNull` and `whereNotNull` and literally
152 | do the exact same thing.
153 |
154 | ## Parameter Grouping
155 |
156 | ```php
157 | // torchlight! {"lineNumbers": false}
158 | use MarcReichel\IGDBLaravel\Models\Game;
159 |
160 | $games = Game::where('name', 'Fortnite')
161 | ->orWhere(function($query) {
162 | $query->where('aggregated_rating', '>=', 90)
163 | ->where('aggregated_rating_count', '>=', 3000);
164 | })
165 | ->get();
166 | ```
167 |
--------------------------------------------------------------------------------
/docs/07-order-limit-offset.md:
--------------------------------------------------------------------------------
1 | # Ordering, Limit, & Offset
2 |
3 | ## orderBy
4 |
5 | The `orderBy` method allows you to sort the result of the query by a given field.
6 | The first argument to the `orderBy` method should be the field you wish to sort
7 | by, while the second argument controls the direction of the sort and may be either
8 | `asc` or `desc`:
9 |
10 | ```php
11 | // torchlight! {"lineNumbers": false}
12 | use MarcReichel\IGDBLaravel\Models\Game;
13 |
14 | $games = Game::orderBy('first_release_date', 'asc')->get();
15 | ```
16 |
17 | ## skip / take (limit / offset)
18 |
19 | To limit the number of results returned from the query, or to skip a given
20 | number of results in the query, you may use the `skip` and `take` methods (`take` is limited to a maximum of 500):
21 |
22 | ```php
23 | // torchlight! {"lineNumbers": false}
24 | use MarcReichel\IGDBLaravel\Models\Game;
25 |
26 | $games = Game::skip(10)->take(5)->get();
27 | ```
28 |
29 | Alternatively, you may use the `limit` and `offset` methods:
30 |
31 | ```php
32 | // torchlight! {"lineNumbers": false}
33 | use MarcReichel\IGDBLaravel\Models\Game;
34 |
35 | $games = Game::offset(10)->limit(5)->get();
36 | ```
37 |
--------------------------------------------------------------------------------
/docs/08-cache.md:
--------------------------------------------------------------------------------
1 | # Cache
2 |
3 | You can overwrite the default cache time for one specific query. So you can for
4 | example turn off caching for a query:
5 |
6 | ```php
7 | // torchlight! {"lineNumbers": false}
8 | use MarcReichel\IGDBLaravel\Models\Game;
9 |
10 | $games = Game::cache(0)->get();
11 | ```
12 |
--------------------------------------------------------------------------------
/docs/09-relationships.md:
--------------------------------------------------------------------------------
1 | # Relationships (Extends)
2 |
3 | To extend your result use the `with`-method:
4 |
5 | ```php
6 | // torchlight! {"lineNumbers": false}
7 | use MarcReichel\IGDBLaravel\Models\Game;
8 |
9 | $game = Game::with(['cover', 'artworks'])->get();
10 | ```
11 |
12 | By default, every field (`*`) of the relationship is selected.
13 | If you want to define the fields of the relationship yourself you have to define
14 | the relationship as the array-key and the fields as an array:
15 |
16 | ```php
17 | // torchlight! {"lineNumbers": false}
18 | use MarcReichel\IGDBLaravel\Models\Game;
19 |
20 | $game = Game::with(['cover' => ['url', 'image_id']])->get();
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/10-fetch-results.md:
--------------------------------------------------------------------------------
1 | # Fetch results
2 |
3 | ## Get
4 |
5 | To finally get results for the query, simply call `get`:
6 |
7 | ```php
8 | // torchlight! {"lineNumbers": false}
9 | use MarcReichel\IGDBLaravel\Models\Game;
10 |
11 | $games = Game::get();
12 | ```
13 |
14 | ## All
15 |
16 | If you just want to get "all" results (limited to a maximum of 500)
17 | just call the `all`-Method directly on your model:
18 |
19 | ```php
20 | // torchlight! {"lineNumbers": false}
21 | use MarcReichel\IGDBLaravel\Models\Game;
22 |
23 | $games = Game::all();
24 | ```
25 |
26 | ## First
27 |
28 | If you only want one result call the `first`-method after your query:
29 |
30 | ```php
31 | // torchlight! {"lineNumbers": false}
32 | use MarcReichel\IGDBLaravel\Models\Game;
33 |
34 | $game = Game::first();
35 | ```
36 |
37 | ## Find
38 |
39 | If you know the Identifier of the model you can simply call the `find`-method
40 | with the identifier as a parameter:
41 |
42 | ```php
43 | // torchlight! {"lineNumbers": false}
44 | use MarcReichel\IGDBLaravel\Models\Game;
45 |
46 | $game = Game::find(1905);
47 | ```
48 |
49 | ### FindOrFail
50 |
51 | `find` returns `null` if no result were found. If you want to throw an Exception
52 | instead use `findOrFail`. This will throw an
53 | `MarcReichel\IGDBLaravel\Exceptions\ModelNotFoundException` if no result were
54 | found.
55 |
--------------------------------------------------------------------------------
/docs/11-properties.md:
--------------------------------------------------------------------------------
1 | # Reading properties
2 |
3 | ## Model-based approach
4 |
5 | If you used the Model-based approach you can simply get a property:
6 |
7 | ```php
8 | // torchlight! {"lineNumbers": false}
9 | use MarcReichel\IGDBLaravel\Models\Game;
10 |
11 | $game = Game::find(1905);
12 |
13 | if ($game) {
14 | echo $game->name; // Will output "Fortnite"
15 | }
16 | ```
17 |
18 | If you want to access a property which does not exist `null` is returned:
19 |
20 | ```php
21 | // torchlight! {"lineNumbers": false}
22 | use MarcReichel\IGDBLaravel\Models\Game;
23 |
24 | $game = Game::find(1905);
25 |
26 | if ($game) {
27 | echo $game->foo; // Will output nothing
28 | }
29 | ```
30 |
31 | ## Query Builder-based approach
32 |
33 | If you used the Query Builder itself you must check if a property exists
34 | yourself.
35 |
--------------------------------------------------------------------------------
/docs/12-images.md:
--------------------------------------------------------------------------------
1 | # Images
2 |
3 | Since version 3.5.0 it is possible to generate image URLs for
4 | the [different available sizes](https://api-docs.igdb.com/#images).
5 |
6 | This is supported for:
7 |
8 | - `Artwork`
9 | - `CharacterMugShot`
10 | - `CompanyLogo`
11 | - `Cover`
12 | - `EventLogo`
13 | - `GameEngineLogo`
14 | - `PlatformLogo`
15 | - `Screenshot`
16 |
17 | ## Basic Usage
18 |
19 | ### Default image
20 |
21 | ```php
22 | // torchlight! {"lineNumbers": false}
23 | use MarcReichel\IGDBLaravel\Enums\Image\Size;
24 | use MarcReichel\IGDBLaravel\Models\Game;
25 |
26 | $game = Game::where('name', 'Fortnite')
27 | ->with(['cover'])
28 | ->first();
29 |
30 | $game->cover->getUrl();
31 | ```
32 |
33 | ### Other sizes
34 |
35 | As the first parameter the method receives your desired image size. Simply use the available enum values
36 | of the `MarcReichel\IGDBLaravel\Enums\Image\Size` enum.
37 |
38 | ```php
39 | // torchlight! {"lineNumbers": false}
40 | use MarcReichel\IGDBLaravel\Enums\Image\Size;
41 | use MarcReichel\IGDBLaravel\Models\Game;
42 |
43 | $game = Game::where('name', 'Fortnite')
44 | ->with(['cover'])
45 | ->first();
46 |
47 | $game->cover->getUrl(Size::COVER_BIG);
48 | ```
49 |
50 | ### Retina images
51 |
52 | If you want to get retina images simply set the second parameter to `true`.
53 |
54 | ```php
55 | // torchlight! {"lineNumbers": false}
56 | use MarcReichel\IGDBLaravel\Enums\Image\Size;
57 | use MarcReichel\IGDBLaravel\Models\Game;
58 |
59 | $game = Game::where('name', 'Fortnite')
60 | ->with(['cover'])
61 | ->first();
62 |
63 | $game->cover->getUrl(Size::COVER_BIG, true);
64 | ```
65 |
--------------------------------------------------------------------------------
/docs/90-webhooks.md:
--------------------------------------------------------------------------------
1 | # Webhooks
2 |
3 | Since version 2.3.0 of this package you can create webhooks and handle their requests with ease. 🎉
4 |
5 | ## Initial Setup
6 |
7 | ### Configuration
8 |
9 | Inside your `config/igdb.php` file you need to have a `webhook_path` and `webhook_secret` of your choice like so:
10 |
11 | ```php
12 | // torchlight! {"lineNumbers": false}
13 | // torchlight! {"summaryCollapsedIndicator": "⌄"}
14 | [
22 | 'client_id' => env('TWITCH_CLIENT_ID', ''),
23 | 'client_secret' => env('TWITCH_CLIENT_SECRET', ''),
24 | ],
25 |
26 | /*
27 | * This package caches queries automatically (for 1 hour per default).
28 | * Here you can set how long each query should be cached (in seconds).
29 | *
30 | * To turn cache off set this value to 0
31 | */
32 | 'cache_lifetime' => env('IGDB_CACHE_LIFETIME', 3600), // [tl! collapse:end]
33 |
34 | /*
35 | * Path where the webhooks should be handled.
36 | */
37 | 'webhook_path' => 'igdb-webhook/handle',
38 |
39 | /*
40 | * The webhook secret.
41 | *
42 | * This needs to be a string of your choice in order to use the webhook
43 | * functionality.
44 | */
45 | 'webhook_secret' => env('IGDB_WEBHOOK_SECRET'),
46 | ];
47 | ```
48 |
49 | _**Please note**: You only need to add this part to your config if you have upgraded from a prior version of this
50 | package. New installations have this configured automatically._
51 |
52 | And then set a secret inside your `.env` file:
53 |
54 | ```dotenv
55 | // torchlight! {"lineNumbers": false}
56 | IGDB_WEBHOOK_SECRET=yoursecret
57 | ```
58 |
59 | > Make sure your `APP_URL` (inside your `.env`) is something different than `localhost` or `127.0.0.1`. Otherwise webhooks can
60 | > not be created.
61 |
62 | That's it!
63 |
64 | ## Create a webhook
65 |
66 | Let's say we want to be informed whenever a new game is created on https://igdb.com.
67 |
68 | First of all we need to inform IGDB that we want to be informed.
69 |
70 | For this we create a webhook like so (for example inside a controller):
71 |
72 | ```php
73 | // torchlight! {"lineNumbers": false}
74 | use MarcReichel\IGDBLaravel\Enums\Webhook\Method;
75 | use MarcReichel\IGDBLaravel\Models\Game;
76 | use Illuminate\Routing\Controller;
77 |
78 | class ExampleController extends Controller
79 | {
80 | public function createWebhook()
81 | {
82 | Game::createWebhook(Method::CREATE)
83 | }
84 | }
85 | ```
86 |
87 | ## Listen for events
88 |
89 | Now that we have created our webhook we can listen for a specific event - in our case when a game is created.
90 |
91 | For this we create a Laravel EventListener or for sake of simplicity we just listen for an event inside the `boot()`
92 | method of our `app/providers/EventServiceProvider.php`:
93 |
94 | ```php
95 | // torchlight! {"lineNumbers": false}
96 | use MarcReichel\IGDBLaravel\Events\GameCreated;
97 | use Illuminate\Support\Facades\Event;
98 |
99 | public function boot()
100 | {
101 | Event::listen(function (GameCreated $event) {
102 | // $event->data holds the (unexpanded!) data (of the game in this case)
103 | });
104 | }
105 | ```
106 |
107 | [Here](https://github.com/marcreichel/igdb-laravel/tree/main/src/Events) you can find a list of all available events.
108 |
109 | Further information on how to set up event listeners can be found on
110 | the [official docs](https://laravel.com/docs/events).
111 |
112 | ## Manage webhooks via CLI
113 |
114 | ### List your webhooks
115 |
116 | ```bash
117 | // torchlight! {"lineNumbers": false}
118 | $ php artisan igdb:webhooks
119 | ```
120 |
121 | ### Create a webhook
122 |
123 | ```bash
124 | // torchlight! {"lineNumbers": false}
125 | $ php artisan igdb:webhooks:create {model?} {--method=}
126 | ```
127 |
128 | You can also just call `php artisan igdb:webhooks:create` without any arguments. The command will then ask for the
129 | required data interactively.
130 |
131 | The `model` parameter needs to be the (studly cased) class name of a model (e.g. `Game`).
132 |
133 | The `--method` option needs to be one of `create`, `update` or `delete` accordingly for which event you want to listen.
134 |
135 | ### Reactivate a webhook
136 |
137 | ```bash
138 | // torchlight! {"lineNumbers": false}
139 | $ php artisan igdb:webhooks:reactivate {id}
140 | ```
141 |
142 | For `{id}` insert the id of the (inactive) webhook.
143 |
144 | ### Delete a webhook
145 |
146 | ```bash
147 | // torchlight! {"lineNumbers": false}
148 | $ php artisan igdb:webhooks:delete {id?} {--A|all}
149 | ```
150 |
151 | You may provide the `id` of a webhook to delete it or use the `-A`/`--all` flag to delete all your registered webhooks.
152 |
--------------------------------------------------------------------------------
/docs/art/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcreichel/igdb-laravel/59632c0c76a20731dd40a4399c1ed0a8cea8aa0c/docs/art/.gitkeep
--------------------------------------------------------------------------------
/docs/art/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcreichel/igdb-laravel/59632c0c76a20731dd40a4399c1ed0a8cea8aa0c/docs/art/cover.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | {.inline-images}
4 | [](https://packagist.org/packages/marcreichel/igdb-laravel)
5 | [](https://packagist.org/packages/marcreichel/igdb-laravel)
6 | [](https://github.com/marcreichel/igdb-laravel/actions/workflows/tests.yml)
7 | [](https://github.com/marcreichel/igdb-laravel/actions/workflows/pint.yml)
8 | [](https://github.com/marcreichel/igdb-laravel/actions/workflows/code-quality.yml)
9 | [](https://www.codefactor.io/repository/github/marcreichel/igdb-laravel)
10 | [](https://codecov.io/gh/marcreichel/igdb-laravel)
11 | [](https://packagist.org/packages/marcreichel/igdb-laravel)
12 |
13 | {style="width: 100%"}
14 |
15 | This is a Laravel wrapper for version 4 of the [IGDB API](https://api-docs.igdb.com/) (Apicalypse) including [webhook handling](90-webhooks.md).
16 |
17 | It handles authentication and caching of the IGDB API automatically.
18 |
19 | ## Example
20 |
21 | ```php
22 | // torchlight! {"lineNumbers": false}
23 | use MarcReichel\IGDBLaravel\Models\Game;
24 |
25 | $game = Game::where('name', 'Fortnite')->first();
26 | ```
27 |
--------------------------------------------------------------------------------
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | ignoreErrors:
3 | -
4 | message: "#^Unable to resolve the template type TMapValue in call to method Illuminate\\\\Support\\\\Collection\\<\\(int\\|string\\),mixed\\>\\:\\:map\\(\\)$#"
5 | count: 1
6 | path: src/Models/Model.php
7 |
8 | -
9 | message: "#^Cannot call method assertExitCode\\(\\) on Illuminate\\\\Testing\\\\PendingCommand\\|int\\.$#"
10 | count: 12
11 | path: tests/ConsoleTest.php
12 |
13 | -
14 | message: "#^Cannot call method expectsQuestion\\(\\) on Illuminate\\\\Testing\\\\PendingCommand\\|int\\.$#"
15 | count: 7
16 | path: tests/ConsoleTest.php
17 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - ./vendor/larastan/larastan/extension.neon
3 | - phpstan-baseline.neon
4 |
5 | parameters:
6 | paths:
7 | - src
8 | - tests
9 |
10 | # The level 9 is the highest level
11 | level: 8
12 | ignoreErrors:
13 | - '#Call to an undefined static method MarcReichel\\IGDBLaravel\\Models\\Game::foo\(\).#'
14 | - '#Unable to resolve the template type TValue in call to function collect#'
15 | - '#Unable to resolve the template type TKey in call to function collect#'
16 | - identifier: missingType.iterableValue
17 | - identifier: missingType.generics
18 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | tests
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ./src
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/pint.json:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "psr12",
3 | "rules": {
4 | "align_multiline_comment": true,
5 | "array_indentation": true,
6 | "array_push": true,
7 | "array_syntax": {
8 | "syntax": "short"
9 | },
10 | "assign_null_coalescing_to_coalesce_equal": true,
11 | "binary_operator_spaces": true,
12 | "blank_line_before_statement": true,
13 | "cast_spaces": true,
14 | "clean_namespace": true,
15 | "combine_consecutive_issets": true,
16 | "combine_consecutive_unsets": true,
17 | "compact_nullable_typehint": true,
18 | "concat_space": {
19 | "spacing": "one"
20 | },
21 | "declare_strict_types": true,
22 | "fully_qualified_strict_types": true,
23 | "function_to_constant": true,
24 | "general_phpdoc_annotation_remove": {
25 | "annotations": [
26 | "author",
27 | "package",
28 | "since"
29 | ],
30 | "case_sensitive": false
31 | },
32 | "get_class_to_class_keyword": true,
33 | "is_null": true,
34 | "lambda_not_used_import": true,
35 | "logical_operators": true,
36 | "method_chaining_indentation": true,
37 | "modernize_types_casting": true,
38 | "multiline_whitespace_before_semicolons": true,
39 | "no_empty_comment": true,
40 | "no_empty_phpdoc": true,
41 | "no_empty_statement": true,
42 | "no_extra_blank_lines": {
43 | "tokens": ["attribute", "break", "case", "continue", "curly_brace_block", "default", "extra", "parenthesis_brace_block", "return", "square_brace_block", "switch", "throw", "use", "use_trait"]
44 | },
45 | "no_multiline_whitespace_around_double_arrow": true,
46 | "no_short_bool_cast": true,
47 | "no_singleline_whitespace_before_semicolons": true,
48 | "no_superfluous_elseif": true,
49 | "no_superfluous_phpdoc_tags": true,
50 | "no_trailing_comma_in_singleline": true,
51 | "no_unneeded_control_parentheses": true,
52 | "no_useless_concat_operator": true,
53 | "no_useless_else": true,
54 | "no_useless_nullsafe_operator": true,
55 | "no_useless_return": true,
56 | "no_whitespace_before_comma_in_array": true,
57 | "nullable_type_declaration": true,
58 | "object_operator_without_whitespace": true,
59 | "ordered_imports": {
60 | "imports_order": [
61 | "class",
62 | "function",
63 | "const"
64 | ],
65 | "sort_algorithm": "alpha"
66 | },
67 | "ordered_interfaces": true,
68 | "ordered_types": {
69 | "null_adjustment": "always_last"
70 | },
71 | "phpdoc_align": {
72 | "align": "left"
73 | },
74 | "phpdoc_indent": true,
75 | "phpdoc_no_useless_inheritdoc": true,
76 | "phpdoc_order": true,
77 | "phpdoc_scalar": true,
78 | "phpdoc_single_line_var_spacing": true,
79 | "phpdoc_separation": {
80 | "groups": [
81 | [
82 | "deprecated",
83 | "link",
84 | "see"
85 | ],
86 | [
87 | "template",
88 | "template-extends",
89 | "template-implements"
90 | ]
91 | ]
92 | },
93 | "phpdoc_summary": true,
94 | "phpdoc_tag_casing": true,
95 | "phpdoc_trim": true,
96 | "phpdoc_trim_consecutive_blank_line_separation": true,
97 | "phpdoc_var_without_name": true,
98 | "php_unit_construct": true,
99 | "php_unit_dedicate_assert": true,
100 | "php_unit_dedicate_assert_internal_type": true,
101 | "php_unit_internal_class": true,
102 | "php_unit_method_casing": true,
103 | "return_assignment": true,
104 | "return_type_declaration": true,
105 | "short_scalar_cast": true,
106 | "single_line_comment_spacing": true,
107 | "single_line_comment_style": true,
108 | "single_quote": true,
109 | "single_space_around_construct": true,
110 | "ternary_to_null_coalescing": true,
111 | "trailing_comma_in_multiline": {
112 | "elements": [
113 | "arguments",
114 | "arrays",
115 | "match",
116 | "parameters"
117 | ]
118 | },
119 | "trim_array_spaces": true,
120 | "type_declaration_spaces": true,
121 | "types_spaces": {
122 | "space": "single"
123 | },
124 | "use_arrow_functions": true,
125 | "void_return": true,
126 | "whitespace_after_comma_in_array": {
127 | "ensure_single_space": true
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | withPhpSets(php84: true)
9 | ->withAttributesSets(phpunit: true)
10 | ->withRules([
11 | Rector\CodeQuality\Rector\Ternary\ArrayKeyExistsTernaryThenValueToCoalescingRector::class,
12 | Rector\CodeQuality\Rector\NullsafeMethodCall\CleanupUnneededNullsafeOperatorRector::class,
13 | Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector::class,
14 | Rector\CodeQuality\Rector\Ternary\UnnecessaryTernaryExpressionRector::class,
15 | Rector\DeadCode\Rector\Foreach_\RemoveUnusedForeachKeyRector::class,
16 | Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictFluentReturnRector::class,
17 | Rector\Php80\Rector\Class_\StringableForToStringRector::class,
18 | Rector\CodingStyle\Rector\ArrowFunction\StaticArrowFunctionRector::class,
19 | Rector\CodingStyle\Rector\Closure\StaticClosureRector::class,
20 | Rector\DeadCode\Rector\Node\RemoveNonExistingVarAnnotationRector::class,
21 | Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodParameterRector::class,
22 | Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanStrictReturnsRector::class,
23 | Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnNewRector::class,
24 | Rector\TypeDeclaration\Rector\ClassMethod\ParamTypeByMethodCallTypeRector::class,
25 | Rector\TypeDeclaration\Rector\ClassMethod\NumericReturnTypeFromStrictScalarReturnsRector::class,
26 | Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector::class,
27 | Rector\CodeQuality\Rector\If_\ExplicitBoolCompareRector::class,
28 | Rector\CodeQuality\Rector\Foreach_\ForeachItemsAssignToEmptyArrayToAssignRector::class,
29 | Rector\CodeQuality\Rector\Foreach_\ForeachToInArrayRector::class,
30 | Rector\CodeQuality\Rector\BooleanAnd\RemoveUselessIsObjectCheckRector::class,
31 | ])
32 | ->withPaths([
33 | __DIR__ . '/src',
34 | __DIR__ . '/tests',
35 | ])
36 | ->withTypeCoverageLevel(0);
37 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | Webhook::handle($request));
13 |
14 | Route::post(substr(md5(config('igdb.credentials.client_id')), 0, 8) . '/{model}/{method}', static fn (Request $request) => Webhook::handle($request))->name('handle-igdb-webhook');
15 |
--------------------------------------------------------------------------------
/src/ApiHelper.php:
--------------------------------------------------------------------------------
1 | config('igdb.credentials.client_id'),
34 | 'client_secret' => config('igdb.credentials.client_secret'),
35 | 'grant_type' => 'client_credentials',
36 | ]);
37 | $response = Http::post('https://id.twitch.tv/oauth2/token?' . $query)
38 | ->throw()
39 | ->json();
40 |
41 | if (is_array($response) && isset($response['access_token']) && $response['expires_in']) {
42 | Cache::put($accessTokenCacheKey, (string) $response['access_token'], (int) $response['expires_in'] - 60);
43 |
44 | $accessToken = $response['access_token'];
45 | }
46 | } catch (Exception) {
47 | throw new AuthenticationException('Access Token could not be retrieved from Twitch.');
48 | }
49 |
50 | return (string) $accessToken;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Client.php:
--------------------------------------------------------------------------------
1 | self::request($endpoint, $query));
23 | }
24 |
25 | public static function count(string $endpoint, string $query, int $cacheLifetime): int
26 | {
27 | $endpoint = Str::finish($endpoint, '/count');
28 | $cacheKey = self::handleCache($endpoint, $query, $cacheLifetime);
29 |
30 | return Cache::remember($cacheKey, $cacheLifetime, static function () use ($endpoint, $query): int {
31 | $response = self::request($endpoint, $query);
32 | if (is_array($response)) {
33 | return (int) $response['count'];
34 | }
35 |
36 | return 0;
37 | });
38 | }
39 |
40 | private static function handleCache(string $endpoint, string $query, int $cacheLifetime): string
41 | {
42 | $key = config('igdb.cache_prefix', 'igdb_cache') . '.' . md5($endpoint . $query);
43 |
44 | if ($cacheLifetime === 0) {
45 | Cache::forget($key);
46 | }
47 |
48 | return $key;
49 | }
50 |
51 | /**
52 | * @throws AuthenticationException
53 | * @throws RequestException
54 | */
55 | private static function request(string $endpoint, string $query): mixed
56 | {
57 | $client = Http::withOptions([
58 | 'base_uri' => ApiHelper::IGDB_BASE_URI,
59 | ])->withHeaders([
60 | 'Accept' => 'application/json',
61 | 'Client-ID' => config('igdb.credentials.client_id'),
62 | ]);
63 |
64 | return $client->withHeaders([
65 | 'Authorization' => 'Bearer ' . ApiHelper::retrieveAccessToken(),
66 | ])
67 | ->withBody($query, 'plain/text')
68 | ->retry(3, 100)
69 | ->post($endpoint)
70 | ->throw()
71 | ->json();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Console/CreateWebhook.php:
--------------------------------------------------------------------------------
1 | argument('model') ?? $this->choice($modelQuestionString, $this->getModels());
31 |
32 | if (!is_string($model)) {
33 | throw new InvalidArgumentException(
34 | 'Argument model has to be of type string. ' . gettype($model) . ' given.',
35 | );
36 | }
37 |
38 | $namespace = 'MarcReichel\IGDBLaravel\Models\\';
39 | $fullQualifiedName = $namespace . $model;
40 |
41 | if (!class_exists($fullQualifiedName)) {
42 | $this->line('');
43 | $this->error('Model "' . $model . '" does not exist.');
44 | $closestModel = $this->getClosestModel($model);
45 | if (!$closestModel) {
46 | return self::FAILURE;
47 | }
48 | if (!$this->confirm('Did you mean ' . $closestModel . '?')) {
49 | return self::FAILURE;
50 | }
51 | $fullQualifiedName = $namespace . $closestModel;
52 | }
53 |
54 | /** @var class-string $class */
55 | $class = $fullQualifiedName;
56 |
57 | $methods = ['create', 'update', 'delete'];
58 |
59 | $method = $this->option('method') ?? $this->choice(
60 | 'For which event do you want to create the webhook?',
61 | $methods,
62 | 'update',
63 | );
64 |
65 | if (!in_array($method, $methods, true)) {
66 | $this->error((new InvalidWebhookMethodException())->getMessage());
67 |
68 | return self::FAILURE;
69 | }
70 |
71 | $mappedMethod = match ($method) {
72 | 'create' => Method::CREATE,
73 | 'update' => Method::UPDATE,
74 | 'delete' => Method::DELETE,
75 | };
76 |
77 | try {
78 | $class::createWebhook($mappedMethod);
79 | } catch (Exception $e) {
80 | $this->error($e->getMessage());
81 |
82 | return self::FAILURE;
83 | }
84 |
85 | $this->info('Webhook created successfully!');
86 |
87 | return self::SUCCESS;
88 | }
89 |
90 | private function getModels(): array
91 | {
92 | $glob = glob(__DIR__ . '/../Models/*.php') ?: [];
93 |
94 | $pattern = '/\/(?:Model|PopularityPrimitive|Search|Webhook|Image)\.php$/';
95 | $grep = preg_grep($pattern, $glob, PREG_GREP_INVERT);
96 |
97 | return collect($grep ?: [])
98 | ->map(static fn (string $path): string => basename($path, '.php'))
99 | ->toArray();
100 | }
101 |
102 | private function getClosestModel(string $model): ?string
103 | {
104 | return collect($this->getModels())->map(static fn (string $m): array => [
105 | 'model' => $m,
106 | 'levenshtein' => levenshtein($m, $model),
107 | ])
108 | ->filter(static fn (array $m) => $m['levenshtein'] <= 5)
109 | ->sortBy(static fn (array $m) => $m['levenshtein'])
110 | ->map(static fn (array $m) => $m['model'])
111 | ->first();
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Console/DeleteWebhook.php:
--------------------------------------------------------------------------------
1 | argument('id');
25 |
26 | if ($id !== 0) {
27 | return $this->deleteOne($id);
28 | }
29 |
30 | if ($this->option('all')) {
31 | return $this->deleteAll();
32 | }
33 |
34 | return self::FAILURE;
35 | }
36 |
37 | private function deleteOne(int $id): int
38 | {
39 | $webhook = Webhook::find($id);
40 |
41 | if (!$webhook instanceof Webhook) {
42 | $this->error('Webhook not found.');
43 |
44 | return self::FAILURE;
45 | }
46 |
47 | if (!$webhook->delete()) {
48 | $this->error('Webhook could not be deleted.');
49 |
50 | return self::FAILURE;
51 | }
52 |
53 | $this->info('Webhook deleted.');
54 |
55 | return self::SUCCESS;
56 | }
57 |
58 | private function deleteAll(): int
59 | {
60 | $webhooks = Webhook::all();
61 |
62 | if ($webhooks->count() === 0) {
63 | $this->info('You do not have any registered webhooks.');
64 |
65 | return self::SUCCESS;
66 | }
67 |
68 | $this->comment('Deleting all your registered webhooks ...');
69 |
70 | $this->withProgressBar($webhooks, static function (Webhook $webhook): void {
71 | $webhook->delete();
72 | });
73 |
74 | $this->info('');
75 |
76 | $this->info('All Webhooks deleted.');
77 |
78 | return self::SUCCESS;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Console/ListWebhooks.php:
--------------------------------------------------------------------------------
1 | count() === 0) {
21 | $this->warn('You do not have any registered webhooks.');
22 |
23 | return self::FAILURE;
24 | }
25 | $this->table(
26 | ['ID', 'URL', 'Model', 'Method', 'Retries', 'Active'],
27 | $webhooks->map(static function (Webhook $webhook) {
28 | $data = $webhook->toArray();
29 |
30 | $data['active'] = $data['active'] ? ' ✅ ' : ' ❌ ';
31 |
32 | return $data;
33 | })->sortBy('id')->toArray(),
34 | );
35 |
36 | return self::SUCCESS;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Console/PublishCommand.php:
--------------------------------------------------------------------------------
1 | call('vendor:publish', ['--tag' => 'igdb:config', '--force' => true]);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Console/ReactivateWebhook.php:
--------------------------------------------------------------------------------
1 | argument('id'));
29 |
30 | if (!$webhook) {
31 | $this->error('Webhook not found.');
32 |
33 | return self::FAILURE;
34 | }
35 |
36 | if ($webhook->active) {
37 | $this->info('Webhook does not need to be reactivated.');
38 |
39 | return self::SUCCESS;
40 | }
41 |
42 | $model = $webhook->getModel();
43 | $method = $webhook->getMethod();
44 |
45 | $fullQualifiedName = 'MarcReichel\\IGDBLaravel\\Models\\' . $model;
46 |
47 | if (!class_exists($fullQualifiedName)) {
48 | $this->error('Model not found.');
49 |
50 | return self::FAILURE;
51 | }
52 |
53 | /** @var class-string $class */
54 | $class = $fullQualifiedName;
55 |
56 | try {
57 | $class::createWebhook($method);
58 | } catch (AuthenticationException | WebhookSecretMissingException $e) {
59 | $this->error($e->getMessage());
60 |
61 | return self::FAILURE;
62 | }
63 |
64 | $this->info('Webhook reactivated.');
65 |
66 | return self::SUCCESS;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Enums/AgeRating/Category.php:
--------------------------------------------------------------------------------
1 | class = static::class;
27 | $this->url = $request->fullUrl();
28 | /** @var string $method */
29 | $method = $request->route('method');
30 | $this->method = $method;
31 | $this->created_at = new Carbon();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Events/EventCreated.php:
--------------------------------------------------------------------------------
1 | publishes([
20 | __DIR__ . '/../config/config.php' => config_path('igdb.php'),
21 | ], 'igdb:config');
22 |
23 | Route::group($this->routeConfiguration(), function (): void {
24 | $this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
25 | });
26 |
27 | if ($this->app->runningInConsole()) {
28 | $this->commands([
29 | PublishCommand::class,
30 | CreateWebhook::class,
31 | ListWebhooks::class,
32 | DeleteWebhook::class,
33 | ReactivateWebhook::class,
34 | ]);
35 | }
36 | }
37 |
38 | public function register(): void
39 | {
40 | $this->mergeConfigFrom(
41 | __DIR__ . '/../config/config.php',
42 | 'igdb',
43 | );
44 | }
45 |
46 | protected function routeConfiguration(): array
47 | {
48 | return [
49 | 'prefix' => config('igdb.webhook_path'),
50 | ];
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Models/AgeRating.php:
--------------------------------------------------------------------------------
1 | AgeRatingContentDescription::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/AgeRatingContentDescription.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/Artwork.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/Character.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | 'mug_shot' => CharacterMugShot::class,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/CharacterMugShot.php:
--------------------------------------------------------------------------------
1 | CollectionRelation::class,
11 | 'as_parent_relations' => CollectionRelation::class,
12 | 'type' => CollectionType::class,
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/CollectionMembership.php:
--------------------------------------------------------------------------------
1 | Collection::class,
11 | 'game' => Game::class,
12 | 'type' => CollectionMembershipType::class,
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/CollectionMembershipType.php:
--------------------------------------------------------------------------------
1 | CollectionType::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/CollectionRelation.php:
--------------------------------------------------------------------------------
1 | Collection::class,
11 | 'parent_collection' => Collection::class,
12 | 'type' => CollectionRelationType::class,
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/CollectionRelationType.php:
--------------------------------------------------------------------------------
1 | CollectionType::class,
11 | 'allowed_parent_type' => CollectionType::class,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/CollectionType.php:
--------------------------------------------------------------------------------
1 | self::class,
11 | 'developed' => Game::class,
12 | 'logo' => CompanyLogo::class,
13 | 'parent' => self::class,
14 | 'published' => Game::class,
15 | 'websites' => CompanyWebsite::class,
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/src/Models/CompanyLogo.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | 'game_localization' => GameLocalization::class,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/Event.php:
--------------------------------------------------------------------------------
1 | EventLogo::class,
11 | 'games' => Game::class,
12 | 'videos' => GameVideo::class,
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/EventLogo.php:
--------------------------------------------------------------------------------
1 | Event::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/EventNetwork.php:
--------------------------------------------------------------------------------
1 | Event::class,
11 | 'network_type' => NetworkType::class,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/ExternalGame.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | 'platform' => Platform::class,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/Franchise.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/Game.php:
--------------------------------------------------------------------------------
1 | AgeRating::class,
11 | 'alternative_names' => AlternativeName::class,
12 | 'artwork' => Artwork::class,
13 | 'bundles' => self::class,
14 | 'collection' => Collection::class,
15 | 'collections' => Collection::class,
16 | 'cover' => Cover::class,
17 | 'dlcs' => self::class,
18 | 'expanded_games' => self::class,
19 | 'expansions' => self::class,
20 | 'external_games' => ExternalGame::class,
21 | 'forks' => self::class,
22 | 'franchise' => Franchise::class,
23 | 'franchises' => Franchise::class,
24 | 'game_engines' => GameEngine::class,
25 | 'game_localizations' => GameLocalization::class,
26 | 'game_modes' => GameMode::class,
27 | 'genres' => Genre::class,
28 | 'involved_companies' => InvolvedCompany::class,
29 | 'keywords' => Keyword::class,
30 | 'language_supports' => LanguageSupport::class,
31 | 'multiplayer_modes' => MultiplayerMode::class,
32 | 'parent_game' => self::class,
33 | 'platforms' => Platform::class,
34 | 'player_perspectives' => PlayerPerspective::class,
35 | 'ports' => self::class,
36 | 'release_dates' => ReleaseDate::class,
37 | 'remakes' => self::class,
38 | 'remasters' => self::class,
39 | 'screenshots' => Screenshot::class,
40 | 'similar_games' => self::class,
41 | 'standalone_expansions' => self::class,
42 | 'tags' => null,
43 | 'themes' => Theme::class,
44 | 'version_parent' => self::class,
45 | 'videos' => GameVideo::class,
46 | 'websites' => Website::class,
47 | ];
48 | }
49 |
--------------------------------------------------------------------------------
/src/Models/GameEngine.php:
--------------------------------------------------------------------------------
1 | Company::class,
11 | 'logo' => GameEngineLogo::class,
12 | 'platforms' => Platform::class,
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/GameEngineLogo.php:
--------------------------------------------------------------------------------
1 | Cover::class,
11 | 'game' => Game::class,
12 | 'region' => Region::class,
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/GameMode.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/GameVersion.php:
--------------------------------------------------------------------------------
1 | GameVersionFeature::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/GameVersionFeature.php:
--------------------------------------------------------------------------------
1 | GameVersionFeatureValue::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/GameVersionFeatureValue.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | 'game_feature' => GameVersionFeature::class,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/GameVideo.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/Genre.php:
--------------------------------------------------------------------------------
1 | getAttribute('image_id');
22 |
23 | if ($size instanceof Size) {
24 | $parsedSize = $size->value;
25 | } else {
26 | $parsedSize = $size;
27 | }
28 |
29 | $cases = collect(Size::cases())
30 | ->map(static fn (Size $s) => $s->value)
31 | ->values()
32 | ->toArray();
33 |
34 | if (!in_array($parsedSize, $cases, true)) {
35 | throw new InvalidArgumentException('Size must be one of ' . implode(', ', $cases));
36 | }
37 |
38 | if ($retina) {
39 | $parsedSize = Str::finish($parsedSize, '_2x');
40 | }
41 |
42 | return "$basePath/t_$parsedSize/$id.jpg";
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Models/InvolvedCompany.php:
--------------------------------------------------------------------------------
1 | Company::class,
11 | 'game' => Game::class,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/Keyword.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | 'language' => Language::class,
12 | 'language_support_type' => LanguageSupportType::class,
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/LanguageSupportType.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | 'platform' => Platform::class,
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/src/Models/NetworkType.php:
--------------------------------------------------------------------------------
1 | EventNetwork::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/Platform.php:
--------------------------------------------------------------------------------
1 | PlatformFamily::class,
11 | 'platform_logo' => PlatformLogo::class,
12 | 'versions' => PlatformVersion::class,
13 | 'websites' => PlatformWebsite::class,
14 | ];
15 | }
16 |
--------------------------------------------------------------------------------
/src/Models/PlatformFamily.php:
--------------------------------------------------------------------------------
1 | PlatformVersionCompany::class,
11 | 'main_manufacturer' => PlatformVersionCompany::class,
12 | 'platform_logo' => PlatformLogo::class,
13 | 'platform_version_release_dates' => PlatformVersionReleaseDate::class,
14 | ];
15 | }
16 |
--------------------------------------------------------------------------------
/src/Models/PlatformVersionCompany.php:
--------------------------------------------------------------------------------
1 | Company::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/PlatformVersionReleaseDate.php:
--------------------------------------------------------------------------------
1 | PlatformVersion::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/PlatformWebsite.php:
--------------------------------------------------------------------------------
1 | PopularityType::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/PopularityType.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | 'platform' => Platform::class,
12 | 'status' => ReleaseDateStatus::class,
13 | ];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Models/ReleaseDateStatus.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Models/Search.php:
--------------------------------------------------------------------------------
1 | Character::class,
11 | 'collection' => Collection::class,
12 | 'company' => Company::class,
13 | 'game' => Game::class,
14 | 'platform' => Platform::class,
15 | 'theme' => Theme::class,
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/src/Models/Theme.php:
--------------------------------------------------------------------------------
1 | Game::class,
11 | ];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Traits/DateCasts.php:
--------------------------------------------------------------------------------
1 | 'date',
17 | 'updated_at' => 'date',
18 | 'change_date' => 'date',
19 | 'start_date' => 'date',
20 | 'published_at' => 'date',
21 | 'first_release_date' => 'date',
22 | ];
23 | }
24 |
--------------------------------------------------------------------------------
/src/Traits/HasLimits.php:
--------------------------------------------------------------------------------
1 | query->put('limit', $limit);
19 |
20 | return $this;
21 | }
22 |
23 | /**
24 | * Alias to set the "limit" value of the query.
25 | */
26 | public function take(int $limit): self
27 | {
28 | return $this->limit($limit);
29 | }
30 |
31 | /**
32 | * Set the "offset" value of the query.
33 | */
34 | public function offset(int $offset): self
35 | {
36 | $this->query->put('offset', $offset);
37 |
38 | return $this;
39 | }
40 |
41 | /**
42 | * Alias to set the "offset" value of the query.
43 | */
44 | public function skip(int $offset): self
45 | {
46 | return $this->offset($offset);
47 | }
48 |
49 | /**
50 | * Set the limit and offset for a given page.
51 | */
52 | public function forPage(int $page, int $perPage = 10): self
53 | {
54 | return $this->skip(($page - 1) * $perPage)->take($perPage);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Traits/HasNestedWhere.php:
--------------------------------------------------------------------------------
1 | class) && $this->class) {
27 | $class = $this->class;
28 | $callback($query = new Builder(new $class()));
29 | } else {
30 | $callback($query = new Builder($this->endpoint));
31 | }
32 |
33 | return $this->addNestedWhereQuery($query, $boolean);
34 | }
35 |
36 | /**
37 | * Add another query builder as a nested where to the query builder.
38 | */
39 | protected function addNestedWhereQuery(Builder $query, string $boolean): self
40 | {
41 | $where = $this->query->get('where', new Collection());
42 |
43 | $nested = '(' . $query->query->get('where', new Collection())->implode(' ') . ')';
44 |
45 | $where->push(($where->count() ? $boolean . ' ' : '') . $nested);
46 |
47 | $this->query->put('where', $where);
48 |
49 | return $this;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Traits/HasSearch.php:
--------------------------------------------------------------------------------
1 | query->put('search', '"' . $query . '"');
23 |
24 | return $this;
25 | }
26 |
27 | /**
28 | * Add a fuzzy search to the query.
29 | *
30 | * @throws ReflectionException
31 | * @throws InvalidParamsException
32 | * @throws JsonException
33 | */
34 | public function fuzzySearch(
35 | mixed $key,
36 | string $query,
37 | bool $caseSensitive = false,
38 | string $boolean = '&',
39 | ): self {
40 | $tokenizedQuery = explode(' ', $query);
41 | $keys = collect($key)->crossJoin($tokenizedQuery)->toArray();
42 |
43 | return $this->whereNested(static function (Builder $query) use ($keys, $caseSensitive): void {
44 | foreach ($keys as $v) {
45 | if (is_array($v)) {
46 | $query->whereLike($v[0], $v[1], $caseSensitive, '|');
47 | }
48 | }
49 | }, $boolean);
50 | }
51 |
52 | /**
53 | * Add an "or fuzzy search" to the query.
54 | *
55 | * @throws ReflectionException|InvalidParamsException|JsonException
56 | */
57 | public function orFuzzySearch(
58 | mixed $key,
59 | string $query,
60 | bool $caseSensitive = false,
61 | string $boolean = '|',
62 | ): self {
63 | return $this->fuzzySearch($key, $query, $caseSensitive, $boolean);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Traits/HasSelect.php:
--------------------------------------------------------------------------------
1 | isEmpty()) {
21 | $collection->push('*');
22 | }
23 |
24 | $collection = $collection->filter(static fn (string $field) => !strpos($field, '.'))->flatten();
25 |
26 | if ($collection->isEmpty()) {
27 | $collection->push('*');
28 | }
29 |
30 | $this->query->put('fields', $collection);
31 |
32 | return $this;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Traits/HasWhere.php:
--------------------------------------------------------------------------------
1 | whereNested($key, $boolean);
35 | }
36 |
37 | if (is_array($key)) {
38 | return $this->addArrayOfWheres($key, $boolean);
39 | }
40 |
41 | if (!is_string($key)) {
42 | throw new InvalidArgumentException('Parameter #1 $key needs to be string. ' . gettype($key) . ' given.');
43 | }
44 |
45 | [$value, $operator] = $this->prepareValueAndOperator(
46 | $value,
47 | $operator,
48 | func_num_args() === 2,
49 | );
50 |
51 | $select = $this->query->get('fields', new Collection());
52 | if (!$select->contains($key) && !$select->contains('*')) {
53 | $this->query->put('fields', $select->push($key));
54 | }
55 |
56 | $where = $this->query->get('where', new Collection());
57 |
58 | if (collect($this->dates)->has($key) && $this->dates[$key] === 'date') {
59 | $value = $this->castDate($value);
60 | }
61 |
62 | if (is_string($value)) {
63 | if ($operator === 'like') {
64 | $this->whereLike($key, $value, true, $boolean);
65 | } elseif ($operator === 'ilike') {
66 | $this->whereLike($key, $value, false, $boolean);
67 | } elseif ($operator === 'not like') {
68 | $this->whereNotLike($key, $value, true, $boolean);
69 | } elseif ($operator === 'not ilike') {
70 | $this->whereNotLike($key, $value, false, $boolean);
71 | } else {
72 | $where->push(($where->count() ? $boolean . ' ' : '') . $key . ' ' . $operator . ' "' . $value . '"');
73 | $this->query->put('where', $where);
74 | }
75 | } else {
76 | $value = !is_int($value) ? json_encode($value, JSON_THROW_ON_ERROR) : $value;
77 | $where->push(($where->count() ? $boolean . ' ' : '') . $key . ' ' . $operator . ' ' . $value);
78 | $this->query->put('where', $where);
79 | }
80 |
81 | return $this;
82 | }
83 |
84 | /**
85 | * Add an "or where" clause to the query.
86 | *
87 | * @throws ReflectionException
88 | * @throws JsonException
89 | * @throws InvalidParamsException
90 | */
91 | public function orWhere(
92 | mixed $key,
93 | ?string $operator = null,
94 | mixed $value = null,
95 | string $boolean = '|',
96 | ): self {
97 | if ($key instanceof Closure) {
98 | return $this->whereNested($key, $boolean);
99 | }
100 |
101 | if (is_array($key)) {
102 | return $this->addArrayOfWheres($key, $boolean);
103 | }
104 |
105 | [$value, $operator] = $this->prepareValueAndOperator(
106 | $value,
107 | $operator,
108 | func_num_args() === 2,
109 | );
110 |
111 | return $this->where($key, $operator, $value, $boolean);
112 | }
113 |
114 | /**
115 | * Add an array of where clauses to the query.
116 | *
117 | * @throws ReflectionException
118 | * @throws InvalidParamsException
119 | */
120 | protected function addArrayOfWheres(
121 | array $arrayOfWheres,
122 | string $boolean,
123 | string $method = 'where',
124 | ): self {
125 | return $this->whereNested(static function (Builder $query) use (
126 | $arrayOfWheres,
127 | $method,
128 | $boolean
129 | ): void {
130 | foreach ($arrayOfWheres as $key => $value) {
131 | if (is_numeric($key) && is_array($value)) {
132 | $query->$method(...array_values($value));
133 | } else {
134 | $query->$method($key, '=', $value, $boolean);
135 | }
136 | }
137 | }, $boolean);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Traits/HasWhereBetween.php:
--------------------------------------------------------------------------------
1 | dates)->has($key) && $this->dates[$key] === 'date') {
29 | $first = $this->castDate($first);
30 | $second = $this->castDate($second);
31 | }
32 |
33 | $this->whereNested(static function (Builder $query) use (
34 | $key,
35 | $first,
36 | $second,
37 | $withBoundaries,
38 | ): void {
39 | $operator = ($withBoundaries ? '=' : '');
40 | $query->where($key, '>' . $operator, $first)->where($key, '<' . $operator, $second);
41 | }, $boolean);
42 |
43 | return $this;
44 | }
45 |
46 | /**
47 | * Add a or where between statement to the query.
48 | *
49 | * @throws ReflectionException
50 | * @throws InvalidParamsException
51 | * @throws JsonException
52 | */
53 | public function orWhereBetween(
54 | string $key,
55 | mixed $first,
56 | mixed $second,
57 | bool $withBoundaries = true,
58 | string $boolean = '|',
59 | ): self {
60 | return $this->whereBetween($key, $first, $second, $withBoundaries, $boolean);
61 | }
62 |
63 | /**
64 | * Add a where not between statement to the query.
65 | *
66 | * @throws ReflectionException
67 | * @throws InvalidParamsException
68 | * @throws JsonException
69 | */
70 | public function whereNotBetween(
71 | string $key,
72 | mixed $first,
73 | mixed $second,
74 | bool $withBoundaries = false,
75 | string $boolean = '&',
76 | ): self {
77 | if (collect($this->dates)->has($key) && $this->dates[$key] === 'date') {
78 | $first = $this->castDate($first);
79 | $second = $this->castDate($second);
80 | }
81 |
82 | $this->whereNested(static function (Builder $query) use (
83 | $key,
84 | $first,
85 | $second,
86 | $withBoundaries,
87 | ): void {
88 | $operator = ($withBoundaries ? '=' : '');
89 | $query->where($key, '<' . $operator, $first)->orWhere($key, '>' . $operator, $second);
90 | }, $boolean);
91 |
92 | return $this;
93 | }
94 |
95 | /**
96 | * Add a or where not between statement to the query.
97 | *
98 | * @throws ReflectionException
99 | * @throws InvalidParamsException
100 | * @throws JsonException
101 | */
102 | public function orWhereNotBetween(
103 | string $key,
104 | mixed $first,
105 | mixed $second,
106 | bool $withBoundaries = false,
107 | string $boolean = '|',
108 | ): self {
109 | return $this->whereNotBetween($key, $first, $second, $withBoundaries, $boolean);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Traits/HasWhereDate.php:
--------------------------------------------------------------------------------
1 | prepareValueAndOperator(
28 | $value,
29 | $operator,
30 | func_num_args() === 2,
31 | );
32 |
33 | $start = Carbon::parse($value)->startOfDay()->timestamp;
34 | $end = Carbon::parse($value)->endOfDay()->timestamp;
35 |
36 | return match ($operator) {
37 | '>' => $this->whereDateGreaterThan($key, $operator, $value, $boolean),
38 | '>=' => $this->whereDateGreaterThanOrEquals($key, $operator, $value, $boolean),
39 | '<' => $this->whereDateLowerThan($key, $operator, $value, $boolean),
40 | '<=' => $this->whereDateLowerThanOrEquals($key, $operator, $value, $boolean),
41 | '!=' => $this->whereNotBetween($key, $start, $end, false, $boolean),
42 | default => $this->whereBetween($key, $start, $end, true, $boolean),
43 | };
44 | }
45 |
46 | /**
47 | * @throws JsonException
48 | * @throws ReflectionException
49 | * @throws InvalidParamsException
50 | */
51 | private function whereDateGreaterThan(string $key, mixed $operator, mixed $value, string $boolean): self
52 | {
53 | if (is_string($value) || $value instanceof DateTimeInterface) {
54 | $value = Carbon::parse($value)->addDay()->startOfDay()->timestamp;
55 | }
56 |
57 | return $this->where($key, $operator, $value, $boolean);
58 | }
59 |
60 | /**
61 | * @throws JsonException
62 | * @throws ReflectionException
63 | * @throws InvalidParamsException
64 | */
65 | private function whereDateGreaterThanOrEquals(string $key, mixed $operator, mixed $value, string $boolean): self
66 | {
67 | if (is_string($value) || $value instanceof DateTimeInterface) {
68 | $value = Carbon::parse($value)->startOfDay()->timestamp;
69 | }
70 |
71 | return $this->where($key, $operator, $value, $boolean);
72 | }
73 |
74 | /**
75 | * @throws JsonException
76 | * @throws ReflectionException
77 | * @throws InvalidParamsException
78 | */
79 | private function whereDateLowerThan(string $key, mixed $operator, mixed $value, string $boolean): self
80 | {
81 | if (is_string($value) || $value instanceof DateTimeInterface) {
82 | $value = Carbon::parse($value)->subDay()->endOfDay()->timestamp;
83 | }
84 |
85 | return $this->where($key, $operator, $value, $boolean);
86 | }
87 |
88 | /**
89 | * @throws JsonException
90 | * @throws ReflectionException
91 | * @throws InvalidParamsException
92 | */
93 | private function whereDateLowerThanOrEquals(string $key, mixed $operator, mixed $value, string $boolean): self
94 | {
95 | if (is_string($value) || $value instanceof DateTimeInterface) {
96 | $value = Carbon::parse($value)->endOfDay()->timestamp;
97 | }
98 |
99 | return $this->where($key, $operator, $value, $boolean);
100 | }
101 |
102 | /**
103 | * Add an "or where date" statement to the query.
104 | *
105 | * @throws ReflectionException
106 | * @throws JsonException
107 | * @throws InvalidParamsException
108 | */
109 | public function orWhereDate(string $key, mixed $operator, mixed $value = null, string $boolean = '|'): self
110 | {
111 | return $this->whereDate($key, $operator, $value, $boolean);
112 | }
113 |
114 | /**
115 | * Add a "where year" statement to the query.
116 | *
117 | * @throws ReflectionException
118 | * @throws JsonException
119 | * @throws InvalidParamsException
120 | */
121 | public function whereYear(string $key, mixed $operator, mixed $value = null, string $boolean = '&'): self
122 | {
123 | [$value, $operator] = $this->prepareValueAndOperator(
124 | $value,
125 | $operator,
126 | func_num_args() === 2,
127 | );
128 |
129 | $value = Carbon::now()->setYear($value)->startOfYear();
130 |
131 | if ($operator === '=') {
132 | $start = $value->clone()->startOfYear()->timestamp;
133 | $end = $value->clone()->endOfYear()->timestamp;
134 |
135 | return $this->whereBetween($key, $start, $end, true, $boolean);
136 | }
137 |
138 | if ($operator === '>' || $operator === '<=') {
139 | $value = $value->clone()->endOfYear()->timestamp;
140 | } elseif ($operator === '>=' || $operator === '<') {
141 | $value = $value->clone()->startOfYear()->timestamp;
142 | }
143 |
144 | return $this->where($key, $operator, $value, $boolean);
145 | }
146 |
147 | /**
148 | * Add an "or where year" statement to the query.
149 | *
150 | * @throws ReflectionException
151 | * @throws JsonException
152 | * @throws InvalidParamsException
153 | */
154 | public function orWhereYear(string $key, mixed $operator, mixed $value = null, string $boolean = '|'): self
155 | {
156 | [$value, $operator] = $this->prepareValueAndOperator(
157 | $value,
158 | $operator,
159 | func_num_args() === 2,
160 | );
161 |
162 | return $this->whereYear($key, $operator, $value, $boolean);
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/Traits/HasWhereHas.php:
--------------------------------------------------------------------------------
1 | query->get('where', new Collection());
20 |
21 | $currentWhere = $where;
22 |
23 | $where->push(($currentWhere->count() ? $boolean . ' ' : '') . $relationship . ' != null');
24 |
25 | $this->query->put('where', $where);
26 |
27 | return $this;
28 | }
29 |
30 | /**
31 | * Add an "or where has" statement to the query.
32 | */
33 | public function orWhereHas(string $relationship, string $boolean = '|'): self
34 | {
35 | return $this->whereHas($relationship, $boolean);
36 | }
37 |
38 | /**
39 | * Add a "where has not" statement to the query.
40 | */
41 | public function whereHasNot(string $relationship, string $boolean = '&'): self
42 | {
43 | $where = $this->query->get('where', new Collection());
44 |
45 | $currentWhere = $where;
46 |
47 | $where->push(($currentWhere->count() ? $boolean . ' ' : '') . $relationship . ' = null');
48 |
49 | $this->query->put('where', $where);
50 |
51 | return $this;
52 | }
53 |
54 | /**
55 | * Add a "where has not" statement to the query.
56 | */
57 | public function orWhereHasNot(string $relationship, string $boolean = '|'): self
58 | {
59 | return $this->whereHasNot($relationship, $boolean);
60 | }
61 |
62 | /**
63 | * Add a "where null" clause to the query.
64 | */
65 | public function whereNull(string $key, string $boolean = '&'): self
66 | {
67 | return $this->whereHasNot($key, $boolean);
68 | }
69 |
70 | /**
71 | * Add an "or where null" clause to the query.
72 | */
73 | public function orWhereNull(string $key, string $boolean = '|'): self
74 | {
75 | return $this->whereNull($key, $boolean);
76 | }
77 |
78 | /**
79 | * Add a "where not null" clause to the query.
80 | */
81 | public function whereNotNull(string $key, string $boolean = '&'): self
82 | {
83 | return $this->whereHas($key, $boolean);
84 | }
85 |
86 | /**
87 | * Add an "or where not null" clause to the query.
88 | */
89 | public function orWhereNotNull(string $key, string $boolean = '|'): self
90 | {
91 | return $this->whereNotNull($key, $boolean);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Traits/HasWhereLike.php:
--------------------------------------------------------------------------------
1 | query->get('where', new Collection());
28 |
29 | $clause = $this->generateWhereLikeClause($key, $value, $caseSensitive, '=', '~');
30 |
31 | $where->push(($where->count() ? $boolean . ' ' : '') . $clause);
32 |
33 | $this->query->put('where', $where);
34 |
35 | return $this;
36 | }
37 |
38 | /**
39 | * Add an "or where like" clause to the query.
40 | *
41 | * @throws JsonException
42 | */
43 | public function orWhereLike(
44 | string $key,
45 | string $value,
46 | bool $caseSensitive = true,
47 | string $boolean = '|',
48 | ): self {
49 | return $this->whereLike($key, $value, $caseSensitive, $boolean);
50 | }
51 |
52 | /**
53 | * Add a "where not like" clause to the query.
54 | *
55 | * @throws JsonException
56 | */
57 | public function whereNotLike(
58 | string $key,
59 | string $value,
60 | bool $caseSensitive = true,
61 | string $boolean = '&',
62 | ): self {
63 | $where = $this->query->get('where', new Collection());
64 |
65 | $clause = $this->generateWhereLikeClause($key, $value, $caseSensitive, '!=', '!~');
66 |
67 | $where->push(($where->count() ? $boolean . ' ' : '') . $clause);
68 |
69 | $this->query->put('where', $where);
70 |
71 | return $this;
72 | }
73 |
74 | /**
75 | * Add an "or where not like" clause to the query.
76 | *
77 | * @throws JsonException
78 | */
79 | public function orWhereNotLike(
80 | string $key,
81 | string $value,
82 | bool $caseSensitive = true,
83 | string $boolean = '|',
84 | ): self {
85 | return $this->whereNotLike($key, $value, $caseSensitive, $boolean);
86 | }
87 |
88 | /**
89 | * @throws JsonException
90 | */
91 | private function generateWhereLikeClause(
92 | string $key,
93 | string $value,
94 | bool $caseSensitive,
95 | string $operator,
96 | string $insensitiveOperator,
97 | ): string {
98 | $hasPrefix = Str::startsWith($value, ['%', '*']);
99 | $hasSuffix = Str::endsWith($value, ['%', '*']);
100 |
101 | if ($hasPrefix) {
102 | $value = substr($value, 1);
103 | }
104 | if ($hasSuffix) {
105 | $value = substr($value, 0, -1);
106 | }
107 |
108 | $operator = $caseSensitive ? $operator : $insensitiveOperator;
109 | $prefix = $hasPrefix || !$hasSuffix ? '*' : '';
110 | $suffix = $hasSuffix || !$hasPrefix ? '*' : '';
111 | $value = json_encode($value, JSON_THROW_ON_ERROR);
112 | $value = Str::start(Str::finish($value, $suffix), $prefix);
113 |
114 | return implode(' ', [$key, $operator, $value]);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/Traits/Operators.php:
--------------------------------------------------------------------------------
1 | ',
19 | '<=',
20 | '>=',
21 | '!=',
22 | '!=',
23 | '~',
24 | 'like',
25 | 'ilike',
26 | 'not like',
27 | 'not ilike',
28 | ];
29 | }
30 |
--------------------------------------------------------------------------------
/src/Traits/ValuePreparer.php:
--------------------------------------------------------------------------------
1 | invalidOperatorAndValue($operator, $value)) {
31 | throw new InvalidArgumentException('Illegal operator and value combination.');
32 | }
33 |
34 | return [$value, $operator];
35 | }
36 |
37 | /**
38 | * Determine if the given operator and value combination is legal.
39 | *
40 | * Prevents using Null values with invalid operators.
41 | */
42 | private function invalidOperatorAndValue(?string $operator, mixed $value): bool
43 | {
44 | return null === $value && in_array($operator, $this->operators, true) && !in_array($operator, ['=', '!=']);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/ApiHelperTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('some-token', $token);
28 | }
29 |
30 | /**
31 | * @throws AuthenticationException
32 | */
33 | public function testItShouldRetrieveAccessTokenFromTwitch(): void
34 | {
35 | Cache::forget('igdb_cache.access_token');
36 |
37 | Http::fake([
38 | '*/oauth2/token*' => Http::response([
39 | 'access_token' => 'test-suite-token',
40 | 'expires_in' => 3600,
41 | ]),
42 | ]);
43 |
44 | $token = ApiHelper::retrieveAccessToken();
45 |
46 | $this->assertEquals('test-suite-token', $token);
47 | }
48 |
49 | /**
50 | * @throws AuthenticationException
51 | */
52 | public function testItShouldThrowAuthenticationException(): void
53 | {
54 | $this->expectException(AuthenticationException::class);
55 |
56 | Cache::forget('igdb_cache.access_token');
57 |
58 | Http::fake([
59 | '*/oauth2/token*' => Http::response([], Response::HTTP_INTERNAL_SERVER_ERROR),
60 | ]);
61 |
62 | ApiHelper::retrieveAccessToken();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/EventsTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(class_exists($eventClassString));
19 | }
20 |
21 | #[DataProvider('modelsDataProvider')]
22 | public function testItShouldHaveUpdatedEventForEveryModel(string $className): void
23 | {
24 | $eventClassString = 'MarcReichel\IGDBLaravel\Events\\' . $className . 'Updated';
25 | $this->assertTrue(class_exists($eventClassString));
26 | }
27 |
28 | #[DataProvider('modelsDataProvider')]
29 | public function testItShouldHaveDeletedEventForEveryModel(string $className): void
30 | {
31 | $eventClassString = 'MarcReichel\IGDBLaravel\Events\\' . $className . 'Deleted';
32 | $this->assertTrue(class_exists($eventClassString));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/ImageTest.php:
--------------------------------------------------------------------------------
1 | Http::response([
34 | [
35 | 'id' => 1,
36 | 'alpha_channel' => false,
37 | 'animated' => false,
38 | 'checksum' => 'abc',
39 | 'height' => 100,
40 | 'image_id' => 'abc',
41 | 'url' => '//images.igdb.com/igdb/image/upload/t_thumb/abc.jpg',
42 | 'width' => 100,
43 | ],
44 | ]),
45 | ]);
46 | }
47 |
48 | public static function imageSizeDataProvider(): array
49 | {
50 | $enumCases = collect(Size::cases())
51 | ->map(static fn (Size $size) => [$size, $size->value])
52 | ->toArray();
53 | $stringCases = collect(Size::cases())
54 | ->map(static fn (Size $size) => [$size->value, $size->value])
55 | ->toArray();
56 |
57 | return array_merge($enumCases, $stringCases);
58 | }
59 |
60 | public function testArtworkShouldBeMappedAsInstanceOfImage(): void
61 | {
62 | self::assertInstanceOf(Image::class, Artwork::first());
63 | }
64 |
65 | public function testCharacterMugShotShouldBeMappedAsInstanceOfImage(): void
66 | {
67 | self::assertInstanceOf(Image::class, CharacterMugShot::first());
68 | }
69 |
70 | public function testCompanyLogoShouldBeMappedAsInstanceOfImage(): void
71 | {
72 | self::assertInstanceOf(Image::class, CompanyLogo::first());
73 | }
74 |
75 | public function testCoverShouldBeMappedAsInstanceOfImage(): void
76 | {
77 | self::assertInstanceOf(Image::class, Cover::first());
78 | }
79 |
80 | public function testGameEngineLogoShouldBeMappedAsInstanceOfImage(): void
81 | {
82 | self::assertInstanceOf(Image::class, GameEngineLogo::first());
83 | }
84 |
85 | public function testPlatformLogoShouldBeMappedAsInstanceOfImage(): void
86 | {
87 | self::assertInstanceOf(Image::class, PlatformLogo::first());
88 | }
89 |
90 | public function testScreenshotShouldBeMappedAsInstanceOfImage(): void
91 | {
92 | self::assertInstanceOf(Image::class, Screenshot::first());
93 | }
94 |
95 | public function testItShouldGenerateDefaultImageUrlWithoutAttributes(): void
96 | {
97 | $url = Artwork::first()?->getUrl();
98 |
99 | self::assertEquals('//images.igdb.com/igdb/image/upload/t_thumb/abc.jpg', $url);
100 | }
101 |
102 | #[DataProvider('imageSizeDataProvider')]
103 | public function testItShouldGenerateDesiredImageUrlWithParameter(Size | string $size, string $value): void
104 | {
105 | $url = Artwork::first()?->getUrl($size);
106 |
107 | self::assertEquals('//images.igdb.com/igdb/image/upload/t_' . $value . '/abc.jpg', $url);
108 | }
109 |
110 | #[DataProvider('imageSizeDataProvider')]
111 | public function testItShouldGenerateRetinaImageUrl(Size | string $size, string $value): void
112 | {
113 | $url = Artwork::first()?->getUrl($size, true);
114 |
115 | self::assertEquals('//images.igdb.com/igdb/image/upload/t_' . $value . '_2x/abc.jpg', $url);
116 | }
117 |
118 | public function testItShouldThrowExceptionWithInvalidImageSize(): void
119 | {
120 | $this->expectException(InvalidArgumentException::class);
121 |
122 | Artwork::first()?->getUrl('foo');
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/tests/Pest.php:
--------------------------------------------------------------------------------
1 | in('Feature');
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Expectations
21 | |--------------------------------------------------------------------------
22 | |
23 | | When you're writing tests, you often need to check that values meet certain conditions. The
24 | | "expect()" function gives you access to a set of "expectations" methods that you can use
25 | | to assert different things. Of course, you may extend the Expectation API at any time.
26 | |
27 | */
28 |
29 | /* expect()->extend('toBeOne', function () {
30 | return $this->toBe(1);
31 | }); */
32 |
33 | /*
34 | |--------------------------------------------------------------------------
35 | | Functions
36 | |--------------------------------------------------------------------------
37 | |
38 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your
39 | | project that you don't want to repeat in every file. Here you can also expose helpers as
40 | | global functions to help you to reduce the number of lines of code in your test files.
41 | |
42 | */
43 |
44 | /* function something()
45 | {
46 | // ..
47 | } */
48 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | Webhook::handle($request),
32 | )->name('handle-igdb-webhook');
33 | }
34 |
35 | protected function getPackageProviders($app): array
36 | {
37 | return [
38 | IGDBLaravelServiceProvider::class,
39 | ];
40 | }
41 |
42 | protected function getEnvironmentSetUp($app): void
43 | {
44 | // perform environment setup
45 | }
46 |
47 | protected function isApiCall(Request $request, string $endpoint, string $requestBody): bool
48 | {
49 | return Str::startsWith($request->url(), 'https://api.igdb.com/v4/' . $endpoint)
50 | && Str::of($request->body())->contains($requestBody);
51 | }
52 |
53 | protected function isWebhookCall(Request $request, string $endpoint): bool
54 | {
55 | return $request->url() === 'https://api.igdb.com/v4/' . $endpoint . '/webhooks' && $request->isForm();
56 | }
57 |
58 | protected function createWebhookResponse(Request $request): PromiseInterface
59 | {
60 | $data = $request->data();
61 | $subCategory = null;
62 | switch ($data['method']) {
63 | case 'create':
64 | $subCategory = 0;
65 |
66 | break;
67 | case 'delete':
68 | $subCategory = 1;
69 |
70 | break;
71 | case 'update':
72 | $subCategory = 2;
73 |
74 | break;
75 | }
76 |
77 | return Http::response([
78 | 'id' => 1337,
79 | 'url' => $data['url'],
80 | 'category' => 1,
81 | 'sub_category' => $subCategory,
82 | 'active' => true,
83 | 'secret' => $data['secret'],
84 | 'created_at' => now()->toIso8601String(),
85 | 'updated_at' => now()->toIso8601String(),
86 | ]);
87 | }
88 |
89 | public static function modelsDataProvider(): array
90 | {
91 | $files = glob(__DIR__ . '/../src/Models/*.php');
92 | $classNames = [];
93 | $blackList = ['PopularityPrimitive', 'Search', 'Webhook'];
94 |
95 | if (!$files) {
96 | return $classNames;
97 | }
98 |
99 | foreach ($files as $file) {
100 | $classString = 'MarcReichel\IGDBLaravel\Models\\' . basename($file, '.php');
101 | if (!class_exists($classString)) {
102 | continue;
103 | }
104 | $reflection = new ReflectionClass($classString);
105 | if ($reflection->isAbstract()) {
106 | continue;
107 | }
108 | if (in_array(class_basename($classString), $blackList)) {
109 | continue;
110 | }
111 |
112 | $classBasename = class_basename($classString);
113 |
114 | $classNames[$classBasename] = [$classBasename];
115 | }
116 |
117 | return $classNames;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------