├── .github
└── workflows
│ ├── PHPStan.yml
│ ├── laravel-pint.yml
│ ├── run-tests.yml
│ └── update-changelog.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── config
└── analytics.php
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── phpunit.xml.dist.bak
├── src
├── Analytics.php
├── AnalyticsServiceProvider.php
├── Credentials.php
├── Exceptions
│ ├── InvalidCredentialsArrayException.php
│ ├── InvalidCredentialsFileException.php
│ ├── InvalidCredentialsJsonStringException.php
│ ├── InvalidFilterException.php
│ └── InvalidPropertyIdException.php
├── Reports
│ └── Reports.php
├── Request
│ ├── Dimensions.php
│ ├── Filters
│ │ ├── AndGroup.php
│ │ ├── BetweenFilter.php
│ │ ├── Filter.php
│ │ ├── FilterContract.php
│ │ ├── FilterExpression.php
│ │ ├── FilterExpressionContract.php
│ │ ├── FilterExpressionField.php
│ │ ├── FilterExpressionList.php
│ │ ├── FilterField.php
│ │ ├── InListFilter.php
│ │ ├── NotExpression.php
│ │ ├── NumericFilter.php
│ │ ├── NumericValueType.php
│ │ ├── OrGroup.php
│ │ └── StringFilter.php
│ ├── Metrics.php
│ └── RequestData.php
└── Response
│ ├── DimensionHeader.php
│ ├── DimensionValue.php
│ ├── Metadata.php
│ ├── MetricHeader.php
│ ├── MetricValue.php
│ ├── PropertyQuota.php
│ ├── Quotas
│ ├── ConcurrentRequests.php
│ ├── PotentiallyThresholdedRequestsPerHour.php
│ ├── ServerErrorsPerProjectPerHour.php
│ ├── TokensPerDay.php
│ ├── TokensPerHour.php
│ └── TokensPerProjectPerHour.php
│ ├── ResponseData.php
│ ├── Row.php
│ └── Total.php
└── tests
├── AnalyticsTest.php
├── CredentialsTest.php
├── DimensionsTest.php
├── FiltersTest.php
├── Helpers
├── CustomDimensions.php
└── CustomMetrics.php
├── MetricsTest.php
├── ReportTest.php
└── TestCase.php
/.github/workflows/PHPStan.yml:
--------------------------------------------------------------------------------
1 | name: PHPStan
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | phpstan:
11 | name: phpstan
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 |
16 | - name: Setup PHP
17 | uses: shivammathur/setup-php@v2
18 | with:
19 | php-version: '8.1'
20 | coverage: none
21 |
22 | - name: Install composer dependencies
23 | uses: ramsey/composer-install@v1
24 |
25 | - name: Run PHPStan
26 | run: ./vendor/bin/phpstan --error-format=github
27 |
--------------------------------------------------------------------------------
/.github/workflows/laravel-pint.yml:
--------------------------------------------------------------------------------
1 | name: Run Laravel Pint
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | php-code-styling:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout code
15 | uses: actions/checkout@v4
16 |
17 | - name: Fix PHP code style issues
18 | uses: aglipanci/laravel-pint-action@latest
19 | with:
20 | pintVersion: 1.18.1
21 |
22 | - name: Commit changes
23 | uses: stefanzweifel/git-auto-commit-action@v4
24 | with:
25 | commit_message: Fix styling
26 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | test:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | fail-fast: true
14 | matrix:
15 | os: [ubuntu-latest, windows-latest]
16 | php: [8.1, 8.2, 8.3]
17 | laravel: [10.*, 11.*]
18 | stability: [prefer-stable]
19 | exclude:
20 | - php: 8.1
21 | laravel: 11.*
22 | include:
23 | - laravel: 10.*
24 | testbench: 8.*
25 | - laravel: 11.*
26 | testbench: 9.*
27 |
28 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
29 |
30 | steps:
31 | - name: Checkout code
32 | uses: actions/checkout@v4
33 |
34 | - name: Setup PHP
35 | uses: shivammathur/setup-php@v2
36 | with:
37 | php-version: ${{ matrix.php }}
38 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, pcov
39 | coverage: pcov
40 |
41 | - name: Setup problem matchers
42 | run: |
43 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
44 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
45 |
46 | - name: Install dependencies
47 | run: |
48 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
49 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
50 |
51 | - name: List Installed Dependencies
52 | run: composer show -D
53 |
54 | - name: Execute tests
55 | run: vendor/bin/phpunit
56 |
57 | - name: Check coverage
58 | run: vendor/bin/coverage-check build/logs/clover.xml 100
59 |
--------------------------------------------------------------------------------
/.github/workflows/update-changelog.yml:
--------------------------------------------------------------------------------
1 |
2 | name: "Update Changelog"
3 |
4 | on:
5 | release:
6 | types: [released]
7 |
8 | jobs:
9 | update:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v4
15 | with:
16 | ref: main
17 |
18 | - name: Update Changelog
19 | uses: stefanzweifel/changelog-updater-action@v1
20 | with:
21 | latest-version: ${{ github.event.release.name }}
22 | release-notes: ${{ github.event.release.body }}
23 |
24 | - name: Commit updated CHANGELOG
25 | uses: stefanzweifel/git-auto-commit-action@v4
26 | with:
27 | branch: main
28 | commit_message: Update CHANGELOG
29 | file_pattern: CHANGELOG.md
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | phpunit.xml
3 | /vendor/
4 | .idea
5 | .phpunit.result.cache
6 | .phpunit.cache
7 | /build
8 | /coverage
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `Analytics` will be documented in this file.
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are welcome and will be fully credited.
4 |
5 | Contributions are accepted via Pull Requests on [Github](https://github.com/garrettmassey/analytics).
6 |
7 | Your pull request cannot be merged unless it passes existing tests, and meets the code coverage requirements set by the repo.
8 |
9 | # Things you could do
10 | If you want to contribute but do not know where to start, this list provides some starting points.
11 | - Set up TravisCI, StyleCI, ScrutinizerCI
12 | - Consider backwards compatability with Laravel 6 & 7, and PHP7.4 +
13 | - Suggest new pre-built queries and reports!
14 | - Contribute when the Google Analytics Data API leaves beta
15 |
16 |
17 | ## Pull Requests
18 |
19 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
20 |
21 | - **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date.
22 |
23 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
24 |
25 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
26 |
27 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
28 |
29 |
30 | **Happy coding**!
31 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The license
2 |
3 | Copyright © 2022 [Garrett Massey](https://www.garrettmassey.net/) | [contact@garrettmassey.net](mailto:contact@garrettmassey.net)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Analytics
2 |
3 | [![Latest Version on Packagist][ico-version]][link-packagist]
4 | [![Total Downloads][ico-downloads]][link-downloads]
5 | [![Tests][ico-tests]][link-tests]
6 |
7 | Build Google Analytics Data API queries in Laravel with ease!
8 |
9 | Methods currently return an instance of `Gtmassey\LaravelAnalytics\ResponseData`, containing the dimension and metric headers, and results in `rows`.
10 |
11 | **Table of Contents:**
12 |
13 | * [Installation](#instal)
14 | * [Setup](#setup)
15 | * [As ENV file (default)](#env)
16 | * [Separate JSON File](#json)
17 | * [JSON String](#jsonString)
18 | * [Separate Values](#vals)
19 | * [Usage](#usage)
20 | * [Query Builder](#querybuilder)
21 | * [Filtering](#filtering)
22 | * [Default Reports](#defaultreports)
23 | * [Extensibility](#extensibility)
24 | * [Custom Metrics And Dimensions](#custommetrics)
25 | * [Reusable Filters](#reusablefilters)
26 | * [Changelog](#changelog)
27 | * [Testing](#testing)
28 |
29 | ## Installation
30 |
31 | Via Composer
32 |
33 | ```SHELL
34 | composer require gtmassey/laravel-analytics
35 | ```
36 |
37 | ## Setup
38 |
39 | To use this package, you must have a Google Cloud Service Accounts Credential.
40 |
41 | If you do not have a project set up on Google Cloud Platform, visit [console.cloud.google.com/projectcreate](https://console.cloud.google.com/projectcreate) to create a new project.
42 |
43 | Once you have a project, make sure you have selected that project in the top left corner of the console.
44 |
45 | 
46 |
47 | Select APIs & Services from the quick access cards on the dashboard.
48 |
49 | 
50 |
51 | Make sure you have Google Analytics Data API enabled. NOTE: this is NOT the same API as Google Analytics API. The Data API is the required API for this package. If you do not have the Google Analytics Data API enabled, you can add it to your Cloud Console account by clicking "enable APIs and Services"
52 |
53 | 
54 |
55 | You can search for the Google Analytics Data API and enable it through the Google API Library
56 |
57 | 
58 |
59 | 
60 |
61 | Once enabled, select the Google Analytics Data API from the list of APIs, and click the Credentials tab.
62 |
63 | 
64 |
65 | If you already have a service account set up with this API, you can skip the next step.
66 |
67 | Click the Create Credentials button, and select Service Account.
68 |
69 | 
70 |
71 | Select the role you want to assign to the service account. For this package, the minimum role is the Viewer role.
72 |
73 | Once your service account has been created, click on the account to go to the IAM & Admin section of Google Cloud Console.
74 |
75 | In the Service Accounts section of the IAM & Admin page, select the appropriate service account, and create a new JSON key for the account:
76 |
77 | 
78 |
79 | 
80 |
81 | Once the key is created, download the JSON file and save it somewhere safe. You will need this file to use this package. If you lose this file, you will have to create a new service account. Google does not let you re-issue keys.
82 |
83 | You can use these credentials in several ways:
84 |
85 | ### As ENV value (default)
86 |
87 | This is ideal setup if you're using only one service account for your application.
88 |
89 | Specify the path to the JSON file in your .env file:
90 |
91 | ```dotenv
92 | GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
93 | ```
94 |
95 | ### As a separate JSON file
96 |
97 | If you have multiple service accounts, you can instruct this package to use a specific one:
98 |
99 | ```dotenv
100 | ANALYTICS_CREDENTIALS_USE_ENV=false
101 | ANALYTICS_CREDENTIALS_FILE=/path/to/credentials.json
102 | ```
103 |
104 | ### As a JSON string
105 |
106 | If you don't want to store the credentials in a file, you can specify the JSON string directly in your .env file:
107 |
108 | ```dotenv
109 | ANALYTICS_CREDENTIALS_USE_ENV=false
110 | ANALYTICS_CREDENTIALS_JSON="{type: service_account, project_id: ...}"
111 | ```
112 |
113 | ### As separate values
114 |
115 | You can also specify the credentials as separate values in your .env file:
116 |
117 | ```dotenv
118 | ANALYTICS_CREDENTIALS_USE_ENV=false
119 | ANALYTICS_CREDENTIALS_TYPE=service_account
120 | ANALYTICS_CREDENTIALS_PROJECT_ID=...
121 | ANALYTICS_CREDENTIALS_PRIVATE_KEY_ID=...
122 | ANALYTICS_CREDENTIALS_PRIVATE_KEY=...
123 | ANALYTICS_CREDENTIALS_CLIENT_EMAIL=...
124 | ANALYTICS_CREDENTIALS_CLIENT_ID=...
125 | ANALYTICS_CREDENTIALS_AUTH_URI=...
126 | ANALYTICS_CREDENTIALS_TOKEN_URI=...
127 | ANALYTICS_CREDENTIALS_AUTH_PROVIDER_X509_CERT_URL=...
128 | ANALYTICS_CREDENTIALS_CLIENT_X509_CERT_URL=...
129 | ```
130 |
131 | > **Warning**
132 | > Package will always prioritize `GOOGLE_APPLICATION_CREDENTIALS` env value over other options. If you want to use a separate service account, make sure to set `ANALYTICS_CREDENTIALS_USE_ENV=false`.
133 |
134 | Finally, open Google Analytics, and copy the property ID for the property you want to query. You will need this ID to use this package.
135 |
136 | 
137 |
138 | Set the property ID in your `.env` file.
139 |
140 | ```dotenv
141 | ANALYTICS_PROPERTY_ID="XXXXXXXXX"
142 | ```
143 |
144 | Now you're ready to start!
145 |
146 | ## Usage
147 |
148 | Once installation is complete, you can run Google Analytics Data API queries in your application.
149 |
150 | All Google Analytics Data API queries require a date range to be run. Use the `Period` class to generate a period of time for the query.
151 |
152 | ### Query Builder:
153 |
154 | ```php
155 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
156 | use Gtmassey\LaravelAnalytics\Request\Metrics;
157 | use Gtmassey\LaravelAnalytics\Analytics;
158 | use Gtmassey\Period\Period;
159 | use Carbon\Carbon;
160 |
161 | $report = Analytics::query()
162 | ->setMetrics(fn(Metrics $metrics) => $metrics
163 | ->active1DayUsers()
164 | ->active7DayUsers()
165 | ->active28DayUsers()
166 | )
167 | ->forPeriod(Period::defaultPeriod())
168 | ->run();
169 |
170 | $report2 = Analytics::query()
171 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions())
172 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->pageTitle())
173 | ->forPeriod(Period::create(Carbon::now()->subDays(30), Carbon::now()))
174 | ->run();
175 | ```
176 |
177 | ### Filtering:
178 |
179 | Filtering closely follows [Google Analytics Data API documentation](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/FilterExpression), but is built with a bit of convenience and fluid interface in mind. You can filter your query by using `dimensionFilter()` and `metricFilter()` methods. These methods accept a callback that receives an instance of `Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression` class. The class provides a set of methods to build your filter:
180 |
181 | * `filter()` - generic filter method that accepts a dimension or metric name and a `filter callback`
182 | * `filterDimension()` - filter method that accepts a dimension object via callback and a `filter callback`
183 | * `filterMetric()` - filter method that accepts a metric object via callback and a `filter callback`
184 | * `not()` - negates the filter
185 | * `andGroup()` - creates a group of filters that are combined with AND operator
186 | * `orGroup()` - creates a group of filters that are combined with OR operator
187 |
188 | You can check `Gtmassey\LaravelAnalytics\Request\Filters\Filter` [class](https://github.com/gtmassey/laravel-analytics/tree/main/src/Request/Filters/Filter) for a list of available `filter callback` methods.
189 |
190 | #### Examples:
191 |
192 | ##### `filter()` method:
193 |
194 | ```php
195 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
196 | use Gtmassey\LaravelAnalytics\Request\Filters\Filter;
197 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression;
198 | use Gtmassey\LaravelAnalytics\Request\Metrics;
199 | use Gtmassey\LaravelAnalytics\Analytics;
200 | use Gtmassey\Period\Period;
201 |
202 | $report = Analytics::query()
203 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions())
204 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->pageTitle())
205 | ->forPeriod(Period::defaultPeriod())
206 | ->dimensionFilter(fn(FilterExpression $filterExpression) => $filterExpression
207 | ->filter('pageTitle', fn(Filter $filter) => $filter->exact('Home'))
208 | )
209 | ->run();
210 | ```
211 |
212 | ##### `filterDimension()` method:
213 |
214 | Using this method you can utilize `Dimensions` class to fluently build your filter without having to know the exact dimension name that's used in the API.
215 |
216 | ```php
217 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
218 | use Gtmassey\LaravelAnalytics\Request\Filters\Filter;
219 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression;
220 | use Gtmassey\LaravelAnalytics\Request\Metrics;
221 | use Gtmassey\LaravelAnalytics\Analytics;
222 | use Gtmassey\Period\Period;
223 |
224 | $report = Analytics::query()
225 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions())
226 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->pageTitle())
227 | ->forPeriod(Period::defaultPeriod())
228 | ->dimensionFilter(fn(FilterExpression $filterExpression) => $filterExpression
229 | ->filterDimension(
230 | dimensionsCallback: fn(Dimensions $dimensions) => $dimensions->pageTitle(),
231 | filter: fn(Filter $filter) => $filter->exact('Home')
232 | )
233 | )
234 | ->run();
235 | ```
236 |
237 | ##### `filterMetric()` method:
238 |
239 | Similar to `filterDimension()` method, you can use this method and utilize `Metrics` class to fluently build your filter without having to know the exact metric name that's used in the API.
240 |
241 | ```php
242 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
243 | use Gtmassey\LaravelAnalytics\Request\Filters\Filter;
244 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression;
245 | use Gtmassey\LaravelAnalytics\Request\Metrics;
246 | use Gtmassey\LaravelAnalytics\Analytics;
247 | use Gtmassey\Period\Period;
248 |
249 | $report = Analytics::query()
250 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions())
251 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->pageTitle())
252 | ->forPeriod(Period::defaultPeriod())
253 | ->metricFilter(fn(FilterExpression $filterExpression) => $filterExpression
254 | ->filterMetric(
255 | metricsCallback: fn(Metrics $metrics) => $metrics->sessions(),
256 | filter: fn(Filter $filter) => $filter->greaterThanInt(100)
257 | )
258 | )
259 | ->run();
260 | ```
261 |
262 | ##### `not()` method:
263 |
264 | ```php
265 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
266 | use Gtmassey\LaravelAnalytics\Request\Filters\Filter;
267 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression;
268 | use Gtmassey\LaravelAnalytics\Request\Metrics;
269 | use Gtmassey\LaravelAnalytics\Analytics;
270 | use Gtmassey\Period\Period;
271 |
272 | $report = Analytics::query()
273 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions())
274 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->pageTitle())
275 | ->forPeriod(Period::defaultPeriod())
276 | ->dimensionFilter(fn(FilterExpression $filterExpression) => $filterExpression
277 | ->not(fn(FilterExpression $filterExpression) => $filterExpression
278 | ->filter('pageTitle', fn(Filter $filter) => $filter
279 | ->exact('Home')
280 | )
281 | )
282 | )
283 | ->run();
284 | ```
285 |
286 | ##### `andGroup()` method:
287 |
288 | ```php
289 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
290 | use Gtmassey\LaravelAnalytics\Request\Filters\Filter;
291 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression;
292 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpressionList;
293 | use Gtmassey\LaravelAnalytics\Request\Metrics;
294 | use Gtmassey\LaravelAnalytics\Analytics;
295 | use Gtmassey\Period\Period;
296 |
297 | $report = Analytics::query()
298 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions())
299 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->deviceCategory()->browser())
300 | ->forPeriod(Period::defaultPeriod())
301 | ->dimensionFilter(fn(FilterExpression $filterExpression) => $filterExpression
302 | ->andGroup(fn(FilterExpressionList $filterExpressionList) => $filterExpressionList
303 | ->filter('deviceCategory', fn(Filter $filter) => $filter
304 | ->exact('Mobile')
305 | )
306 | ->filter('browser', fn(Filter $filter) => $filter
307 | ->exact('Chrome')
308 | )
309 | )
310 | )
311 | ->run();
312 | ```
313 |
314 | ##### `orGroup()` method:
315 |
316 | ```php
317 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
318 | use Gtmassey\LaravelAnalytics\Request\Filters\Filter;
319 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression;
320 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpressionList;
321 | use Gtmassey\LaravelAnalytics\Request\Metrics;
322 | use Gtmassey\LaravelAnalytics\Analytics;
323 | use Gtmassey\Period\Period;
324 |
325 | $report = Analytics::query()
326 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions())
327 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->browser())
328 | ->forPeriod(Period::defaultPeriod())
329 | ->dimensionFilter(fn(FilterExpression $filterExpression) => $filterExpression
330 | ->orGroup(fn(FilterExpressionList $filterExpressionList) => $filterExpressionList
331 | ->filter('browser', fn(Filter $filter) => $filter
332 | ->exact('Firefox')
333 | )
334 | ->filter('browser', fn(Filter $filter) => $filter
335 | ->exact('Chrome')
336 | )
337 | )
338 | )
339 | ->run();
340 | ```
341 |
342 | ##### Advanced example:
343 |
344 | You can mix all of the above methods to build a complex filter expression.
345 |
346 | ```php
347 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
348 | use Gtmassey\LaravelAnalytics\Request\Filters\Filter;
349 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression;
350 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpressionList;
351 | use Gtmassey\LaravelAnalytics\Request\Metrics;
352 | use Gtmassey\LaravelAnalytics\Analytics;
353 | use Gtmassey\Period\Period;
354 |
355 | $report = Analytics::query()
356 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions()->screenPageViews())
357 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->browser()->deviceCategory())
358 | ->forPeriod(Period::defaultPeriod())
359 | ->dimensionFilter(fn(FilterExpression $filterExpression) => $filterExpression
360 | ->andGroup(fn(FilterExpressionList $filterExpressionList) => $filterExpressionList
361 | ->filter('browser', fn(Filter $filter) => $filter
362 | ->contains('safari')
363 | )
364 | ->not(fn(FilterExpression $filterExpression) => $filterExpression
365 | ->filterDimension(
366 | dimensionsCallback: fn(Dimensions $dimensions) => $dimensions->deviceCategory(),
367 | filter: fn(Filter $filter) => $filter->contains('mobile')
368 | )
369 | )
370 | )
371 | )
372 | ->metricFilter(fn(FilterExpression $filterExpression) => $filterExpression
373 | ->orGroup(fn(FilterExpressionList $filterExpressionList) => $filterExpressionList
374 | ->filter('sessions', fn(Filter $filter) => $filter
375 | ->greaterThanInt(200)
376 | )
377 | ->filterMetric(
378 | metricsCallback: fn(Metrics $metrics) => $metrics->sessions(),
379 | filter: fn(Filter $filter) => $filter->lessThanInt(100)
380 | )
381 | )
382 | )
383 | ->run();
384 | ```
385 |
386 | ### Default Reports:
387 |
388 | #### getTopEvents()
389 |
390 | ```php
391 | $report = Analytics::getTopEvents();
392 | ```
393 |
394 | This method returns the top events for the given period. It accepts a `Gtmassey\Period\Period` object as an optional parameter.
395 |
396 | If a `Gtmassey\Period\Period` object is not passed, it will use the default period set in `Gtmassey\Period\Period::defaultPeriod()`.
397 |
398 | The method will return an instance of `Gtmassey\LaravelAnalytics\Response\ResponseData`, which contains `DimensionHeaders`, `MetricHeaders`, `Rows`, and additional metadata.
399 |
400 | example output:
401 |
402 | ```bash
403 | Gtmassey\LaravelAnalytics\Response\ResponseData {
404 | +dimensionHeaders: Spatie\LaravelData\DataCollection {
405 | +items: array:1 [
406 | 0 => Gtmassey\LaravelAnalytics\Response\DimensionHeader {
407 | +name: "eventName"
408 | }
409 | ]
410 | }
411 | +metricHeaders: Spatie\LaravelData\DataCollection {
412 | +items: array:1 [
413 | 0 => Gtmassey\LaravelAnalytics\Response\MetricHeader {
414 | +name: "eventCount"
415 | +type: "TYPE_INTEGER"
416 | }
417 | ]
418 | }
419 | +rows: Spatie\LaravelData\DataCollection {
420 | +items: array:6 [
421 | 0 => Gtmassey\LaravelAnalytics\Response\Row {
422 | +dimensionValues: Spatie\LaravelData\DataCollection {
423 | +items: array:1 [
424 | 0 => Gtmassey\LaravelAnalytics\Response\DimensionValue {
425 | +value: "page_view"
426 | }
427 | ]
428 | }
429 | +metricValues: Spatie\LaravelData\DataCollection {
430 | +items: array:1 [
431 | 0 => Gtmassey\LaravelAnalytics\Response\MetricValue {
432 | +value: "1510"
433 | }
434 | ]
435 | }
436 | }
437 | 1 => Gtmassey\LaravelAnalytics\Response\Row {}
438 | 2 => Gtmassey\LaravelAnalytics\Response\Row {}
439 | 3 => Gtmassey\LaravelAnalytics\Response\Row {}
440 | 4 => Gtmassey\LaravelAnalytics\Response\Row {}
441 | 5 => Gtmassey\LaravelAnalytics\Response\Row {}
442 | ]
443 | }
444 | +totals: null
445 | +rowCount: 6
446 | +metadata: Gtmassey\LaravelAnalytics\Response\Metadata {}
447 | +propertyQuota: null
448 | +kind: "analyticsData#runReport"
449 | }
450 | ```
451 |
452 | #### getTopPages()
453 |
454 | ```php
455 | $report = Analytics::getTopPages();
456 | ```
457 |
458 | This method returns the top pages for the given period. It accepts a `Gtmassey\Period\Period` object as an optional parameter.
459 |
460 | The pages along with the sessions for that page are listed in the `Rows` property of the response.
461 |
462 | #### getUserAcquisitionOverview()
463 |
464 | ```php
465 | $report = Analytics::getUserAcquisitionOverview();
466 | ```
467 |
468 | This method returns the user acquisition overview for the given period. It accepts a `Gtmassey\Period\Period` object as an optional parameter.
469 |
470 | The method will return a `ResponseData` object with the number of sessions by the session's primary acquisition source. Primary acquisition sources are either "direct", "Referral", "Organic Search", and "Organic Social".
471 |
472 | #### getUserEngagement()
473 |
474 | ```php
475 | $report = Analytics::getUserEngagement();
476 | ```
477 |
478 | This method returns a `ResponseData` object without dimensions. The query only contains metrics. The `ResponseData` object will contain:
479 |
480 | * average session duration, in seconds
481 | * number of engaged sessions
482 | * number of sessions per user
483 | * total number of sessions
484 |
485 | ## Extensibility:
486 |
487 | ### Custom metrics and dimensions:
488 |
489 | You are not limited to the metrics and dimensions provided by this package. You can use any custom metrics and dimensions you have created in Google Analytics.
490 |
491 | Create a new class that extends `Gtmassey\LaravelAnalytics\Request\CustomMetric` or `Gtmassey\LaravelAnalytics\Request\CustomDimension` and implement methods following this format:.
492 |
493 | ```php
494 | namespace App\Analytics;
495 |
496 | use Google\Analytics\Data\V1beta\Metric;
497 | use Gtmassey\LaravelAnalytics\Request\Metrics;
498 |
499 | class CustomMetrics extends Metrics
500 | {
501 | public function customMetric(): self
502 | {
503 | $this->metrics->push(new Metric(['name' => 'customEvent:parameter_name']));
504 |
505 | return $this;
506 | }
507 | }
508 | ```
509 |
510 | Bind the class in your `AppServiceProvider`:
511 |
512 | ```php
513 | use Gtmassey\LaravelAnalytics\Request\Metrics;
514 | use App\Analytics\CustomMetrics;
515 | //use Gtmassey\LaravelAnalytics\Request\Dimensions;
516 | //use App\Analytics\CustomDimensions;
517 |
518 | public function boot()
519 | {
520 | $this->app->bind(Metrics::class, CustomMetrics::class);
521 | //$this->app->bind(Dimensions::class, CustomDimensions::class);
522 | }
523 | ```
524 |
525 | Now you can use the custom metric in your query:
526 |
527 | ```php
528 | use App\Analytics\CustomMetrics;
529 | use Gtmassey\LaravelAnalytics\Analytics;
530 | use Gtmassey\LaravelAnalytics\Period;
531 |
532 | $report = Analytics::query()
533 | ->setMetrics(fn(CustomMetrics $metrics) => $metrics
534 | ->customMetric()
535 | ->sessions()
536 | )
537 | ->forPeriod(Period::defaultPeriod())
538 | ->run();
539 | ```
540 |
541 | ### Reusable filters:
542 |
543 | You can create reusable filters to use in your queries. Create a new class that extends `Gtmassey\LaravelAnalytics\Analytics` and implement methods following this format:
544 |
545 | ```php
546 | namespace App\Analytics;
547 |
548 | use Gtmassey\LaravelAnalytics\Analytics;
549 | use Gtmassey\LaravelAnalytics\Request\Filters\Filter;
550 | use Gtmassey\LaravelAnalytics\Request\Filters\FilterExpression;
551 | use Gtmassey\LaravelAnalytics\Request\Metrics;
552 |
553 | class CustomAnalytics extends Analytics
554 | {
555 | public function onlySessionsAbove(int $count): static
556 | {
557 | $this->metricFilter(fn(FilterExpression $filterExpression) => $filterExpression
558 | ->filterMetric(
559 | metricsCallback: fn(Metrics $metrics) => $metrics->sessions(),
560 | filter: fn(Filter $filter) => $filter->greaterThanInt($count),
561 | )
562 | );
563 |
564 | return $this;
565 | }
566 | }
567 | ```
568 |
569 | Bind the class in your `AppServiceProvider`:
570 |
571 | ```php
572 | use Gtmassey\LaravelAnalytics\Analytics;
573 | use App\Analytics\CustomAnalytics;
574 |
575 | public function boot()
576 | {
577 | $this->app->bind(Analytics::class, CustomAnalytics::class);
578 | }
579 | ```
580 |
581 | Now you can use the custom filter in your query:
582 |
583 | ```php
584 | use App\Analytics\CustomAnalytics;
585 | use Gtmassey\LaravelAnalytics\Period;
586 | use Gtmassey\LaravelAnalytics\Request\Dimensions;
587 | use Gtmassey\LaravelAnalytics\Request\Metrics;
588 |
589 | $report = CustomAnalytics::query()
590 | ->setMetrics(fn(Metrics $metrics) => $metrics->sessions())
591 | ->setDimensions(fn(Dimensions $dimensions) => $dimensions->browser())
592 | ->forPeriod(Period::defaultPeriod())
593 | ->onlySessionsAbove(100)
594 | ->run();
595 | ```
596 |
597 | ## Change log
598 |
599 | Read [CHANGELOG.md](CHANGELOG.md)
600 |
601 | ## Testing
602 |
603 | To run tests, run:
604 |
605 | ```bash
606 | composer test
607 | ```
608 |
609 | Note that this command also runs code coverage analysis.
610 |
611 | ## Contributing
612 |
613 | Check out [the contributing guide](CONTRIBUTING.md)
614 |
615 | ## Security
616 |
617 | If you discover any security related issues, please email contact@garrettmassey.net instead of using the issue tracker.
618 |
619 | ## Credits
620 |
621 | - [Garrett Massey](https://www.garrettmassey.net/)
622 | - [All Contributors][link-contributors]
623 |
624 | Special thanks to [Plytas](https://github.com/Plytas) for their early and significant contributions to the project. Without their help setting things up and their willingness to teach me new tools and techniques, this project would be dead in its tracks.
625 |
626 | And a huge thanks to the team over at [Spatie](https://github.com/spatie) for their continued contributions to the open source community! Some of their work is used in this project, and I have used their packages as a foundation for projects for years.
627 |
628 | ## License
629 |
630 | MIT. Please see the [license file](LICENSE.md) for more information.
631 |
632 | [ico-version]: https://img.shields.io/packagist/v/gtmassey/laravel-analytics.svg?style=flat-square
633 | [ico-downloads]: https://img.shields.io/packagist/dt/gtmassey/laravel-analytics.svg?style=flat-square
634 | [ico-tests]: https://github.com/gtmassey/Analytics/actions/workflows/run-tests.yml/badge.svg
635 |
636 | [link-packagist]: https://packagist.org/packages/gtmassey/laravel-analytics
637 | [link-downloads]: https://packagist.org/packages/gtmassey/laravel-analytics
638 | [link-tests]: https://github.com/gtmassey/Analytics/actions/workflows/run-tests.yml
639 | [link-author]: https://github.com/gtmassey
640 | [link-contributors]: ../../contributors
641 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gtmassey/laravel-analytics",
3 | "description": "Create and run Google Analytics Data API queries in Laravel",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Garrett Massey",
8 | "email": "contact@garrettmassey.net",
9 | "role": "Creator"
10 | },
11 | {
12 | "name": "Vytautas Smilingis",
13 | "role": "Contributor"
14 | }
15 | ],
16 | "homepage": "https://github.com/gtmassey/laravel-analytics/",
17 | "keywords": [
18 | "Laravel",
19 | "Analytics",
20 | "Google Analytics"
21 | ],
22 | "require": {
23 | "php": "^8.1|^8.2|^8.3",
24 | "google/analytics-data": "^v0.9.0",
25 | "gtmassey/period": "^1.2.0",
26 | "illuminate/support": "^10.0|^11.0",
27 | "nesbot/carbon": "^2.63",
28 | "spatie/laravel-data": "^3.12",
29 | "spatie/laravel-package-tools": "^1.13"
30 | },
31 | "require-dev": {
32 | "larastan/larastan": "^2.9",
33 | "laravel/pint": "^1.6",
34 | "nunomaduro/collision": "^7.11.0|^v8.5.0",
35 | "orchestra/testbench": "^v8.27.2|^9.5",
36 | "phpstan/extension-installer": "^1.2",
37 | "phpstan/phpstan-deprecation-rules": "^1.1.2",
38 | "phpstan/phpstan-mockery": "^1.1.1",
39 | "phpstan/phpstan-phpunit": "^1.3.7",
40 | "phpunit/phpunit": "^10",
41 | "rregeer/phpunit-coverage-check": "^0.3.1"
42 | },
43 | "autoload": {
44 | "psr-4": {
45 | "Gtmassey\\LaravelAnalytics\\": "src/"
46 | }
47 | },
48 | "autoload-dev": {
49 | "psr-4": {
50 | "Gtmassey\\LaravelAnalytics\\Tests\\": "tests"
51 | }
52 | },
53 | "scripts": {
54 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi",
55 | "analyze": "vendor/bin/phpstan analyse --xdebug",
56 | "test": "./vendor/bin/testbench package:test && ./vendor/bin/coverage-check build/logs/clover.xml 100",
57 | "pint": "./vendor/bin/pint"
58 | },
59 | "config": {
60 | "sort-packages": true,
61 | "allow-plugins": {
62 | "phpstan/extension-installer": true
63 | }
64 | },
65 | "extra": {
66 | "laravel": {
67 | "providers": [
68 | "Gtmassey\\LaravelAnalytics\\AnalyticsServiceProvider"
69 | ]
70 | }
71 | },
72 | "minimum-stability": "dev",
73 | "prefer-stable": true
74 | }
75 |
--------------------------------------------------------------------------------
/config/analytics.php:
--------------------------------------------------------------------------------
1 | env('ANALYTICS_YEAR_TYPE', 'fiscal'),
6 | 'property_id' => env('ANALYTICS_PROPERTY_ID'),
7 |
8 | 'credentials' => [
9 | 'use_env' => env('ANALYTICS_CREDENTIALS_USE_ENV', true),
10 |
11 | 'file' => env('ANALYTICS_CREDENTIALS_FILE'),
12 |
13 | 'json' => env('ANALYTICS_CREDENTIALS_JSON'),
14 |
15 | 'array' => env('ANALYTICS_CREDENTIALS_ARRAY', [
16 | 'type' => env('ANALYTICS_CREDENTIALS_TYPE'),
17 | 'project_id' => env('ANALYTICS_CREDENTIALS_PROJECT_ID'),
18 | 'private_key_id' => env('ANALYTICS_CREDENTIALS_PRIVATE_KEY_ID'),
19 | 'private_key' => env('ANALYTICS_CREDENTIALS_PRIVATE_KEY'),
20 | 'client_email' => env('ANALYTICS_CREDENTIALS_CLIENT_EMAIL'),
21 | 'client_id' => env('ANALYTICS_CREDENTIALS_CLIENT_ID'),
22 | 'auth_uri' => env('ANALYTICS_CREDENTIALS_AUTH_URI'),
23 | 'token_uri' => env('ANALYTICS_CREDENTIALS_TOKEN_URI'),
24 | 'auth_provider_x509_cert_url' => env('ANALYTICS_CREDENTIALS_AUTH_PROVIDER_X509_CERT_URL'),
25 | 'client_x509_cert_url' => env('ANALYTICS_CREDENTIALS_CLIENT_X509_CERT_URL'),
26 | ]),
27 | ],
28 | ];
29 |
--------------------------------------------------------------------------------
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtmassey/laravel-analytics/58b9fa25246a1d8031991f86f18d90d5e1e054c1/phpstan-baseline.neon
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan-baseline.neon
3 |
4 | parameters:
5 | level: max
6 | paths:
7 | - src
8 | - config
9 | - tests # optional
10 | tmpDir: build/phpstan
11 |
12 | checkOctaneCompatibility: true
13 | checkModelProperties: true
14 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ./src
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/phpunit.xml.dist.bak:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 | tests
24 |
25 |
26 |
27 |
28 | ./src
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/Analytics.php:
--------------------------------------------------------------------------------
1 | client = resolve(BetaAnalyticsDataClient::class);
38 | $this->requestData = new RequestData(propertyId: $propertyId);
39 | }
40 |
41 | public static function query(?string $propertyId = null): static
42 | {
43 | /** @var static $analytics */
44 | $analytics = resolve(Analytics::class, ['propertyId' => $propertyId]);
45 |
46 | return $analytics;
47 | }
48 |
49 | /***************************************
50 | * Query Builders
51 | ***************************************/
52 |
53 | /**
54 | * Ability to add metrics to the query using a callback method
55 | * for example:
56 | * $query->setMetrics(function (Metrics $metrics) { $metrics->sessions()->bounceRate(); });
57 | */
58 | public function setMetrics(Closure $callback): static
59 | {
60 | /** @var Metrics $metrics */
61 | $metrics = $callback(resolve(Metrics::class));
62 | $this->requestData->metrics->push(...$metrics->getMetrics());
63 |
64 | return $this;
65 | }
66 |
67 | /**
68 | * Ability to add dimensions to the query using a callback method
69 | * for example:
70 | * $query->setDimensions(function (Dimensions $dimensions) { $dimensions->pageTitle()->pagePath(); });
71 | */
72 | public function setDimensions(Closure $callback): static
73 | {
74 | /** @var Dimensions $dimensions */
75 | $dimensions = $callback(resolve(Dimensions::class));
76 | $this->requestData->dimensions->push(...$dimensions->getDimensions());
77 |
78 | return $this;
79 | }
80 |
81 | /**
82 | * @param Closure(FilterExpression): FilterExpression $callback
83 | */
84 | public function dimensionFilter(Closure $callback): static
85 | {
86 | $this->requestData->dimensionFilter = $callback(new FilterExpression);
87 |
88 | return $this;
89 | }
90 |
91 | /**
92 | * @param Closure(FilterExpression): FilterExpression $callback
93 | */
94 | public function metricFilter(Closure $callback): static
95 | {
96 | $this->requestData->metricFilter = $callback(new FilterExpression);
97 |
98 | return $this;
99 | }
100 |
101 | public function forPeriod(Period $period): static
102 | {
103 | $dateRange = new DateRange([
104 | 'start_date' => $period->startDate->toDateString(),
105 | 'end_date' => $period->endDate->toDateString(),
106 | ]);
107 | $this->requestData->dateRanges->push($dateRange);
108 |
109 | return $this;
110 | }
111 |
112 | public function withTotals(bool $useTotals = true): static
113 | {
114 | $this->requestData->useTotals = $useTotals;
115 |
116 | return $this;
117 | }
118 |
119 | public function limit(int $limit = 10_000): static
120 | {
121 | $this->requestData->limit = $limit;
122 |
123 | return $this;
124 | }
125 |
126 | public function offset(int $offset = 0): static
127 | {
128 | $this->requestData->offset = $offset;
129 |
130 | return $this;
131 | }
132 |
133 | /***************************************
134 | * Process and Run Query
135 | ***************************************/
136 |
137 | /**
138 | * @throws ApiException
139 | */
140 | public function run(): ResponseData
141 | {
142 | $reportResponse = $this->client->runReport($this->requestData->toArray());
143 |
144 | return ResponseData::fromReportResponse($reportResponse);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/AnalyticsServiceProvider.php:
--------------------------------------------------------------------------------
1 | name('analytics')
15 | ->hasConfigFile('analytics');
16 |
17 | $this->app->bind(BetaAnalyticsDataClient::class, function () {
18 | $credentials = resolve(Credentials::class)->parse();
19 |
20 | return new BetaAnalyticsDataClient(['credentials' => $credentials]);
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Credentials.php:
--------------------------------------------------------------------------------
1 | |null
15 | *
16 | * @throws InvalidCredentialsJsonStringException
17 | * @throws InvalidCredentialsFileException
18 | * @throws InvalidCredentialsArrayException
19 | */
20 | public function parse(): ?array
21 | {
22 | if (config('analytics.credentials.use_env') && getenv('GOOGLE_APPLICATION_CREDENTIALS')) {
23 | return null;
24 | }
25 |
26 | if (($file = config('analytics.credentials.file')) !== null) {
27 | return $this->credentialsFile($file);
28 | }
29 |
30 | if (($json = config('analytics.credentials.json')) !== null) {
31 | return $this->credentialsJson($json);
32 | }
33 |
34 | return $this->credentialsArray();
35 | }
36 |
37 | /**
38 | * @return array
39 | *
40 | * @throws InvalidCredentialsFileException
41 | */
42 | private function credentialsFile(mixed $file): array
43 | {
44 | if (! is_string($file) || empty($file)) {
45 | throw InvalidCredentialsFileException::invalidPath();
46 | }
47 |
48 | try {
49 | $fileContents = (new Filesystem)->get($file);
50 | } catch (FileNotFoundException $e) {
51 | throw InvalidCredentialsFileException::notFound(previous: $e);
52 | }
53 |
54 | $credentials = json_decode($fileContents, true);
55 |
56 | if (! is_array($credentials)) {
57 | throw InvalidCredentialsFileException::invalidJson();
58 | }
59 |
60 | return $credentials;
61 | }
62 |
63 | /**
64 | * @return array
65 | *
66 | * @throws InvalidCredentialsJsonStringException
67 | */
68 | private function credentialsJson(mixed $json): array
69 | {
70 | if (! is_string($json) || empty($json)) {
71 | throw InvalidCredentialsJsonStringException::invalidString();
72 | }
73 |
74 | $credentials = json_decode($json, true);
75 |
76 | if (! is_array($credentials)) {
77 | throw InvalidCredentialsJsonStringException::invalidJson();
78 | }
79 |
80 | return $credentials;
81 | }
82 |
83 | /**
84 | * @return array
85 | *
86 | * @throws InvalidCredentialsArrayException
87 | */
88 | private function credentialsArray(): array
89 | {
90 | $credentials = config('analytics.credentials.array');
91 |
92 | if (! is_array($credentials) || empty($credentials)) {
93 | throw InvalidCredentialsArrayException::invalidArray();
94 | }
95 |
96 | return $credentials;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidCredentialsArrayException.php:
--------------------------------------------------------------------------------
1 | setMetrics(fn (Metrics $metric) => $metric->eventCount())
21 | ->setDimensions(fn (Dimensions $dimension) => $dimension->eventName())
22 | ->forPeriod($period ?? Period::defaultPeriod())
23 | ->run();
24 | }
25 |
26 | /**
27 | * @throws ApiException
28 | */
29 | public static function getUserAcquisitionOverview(?Period $period = null): ResponseData
30 | {
31 | return Analytics::query()
32 | ->setMetrics(fn (Metrics $metric) => $metric->sessions())
33 | ->setDimensions(fn (Dimensions $dimension) => $dimension->firstUserDefaultChannelGroup())
34 | ->forPeriod($period ?? Period::defaultPeriod())
35 | ->run();
36 | }
37 |
38 | /**
39 | * @throws ApiException
40 | */
41 | public static function getTopPages(?Period $period = null): ResponseData
42 | {
43 | return Analytics::query()
44 | ->setMetrics(fn (Metrics $metric) => $metric->sessions())
45 | ->setDimensions(fn (Dimensions $dimension) => $dimension->pageTitle())
46 | ->forPeriod($period ?? Period::defaultPeriod())
47 | ->run();
48 | }
49 |
50 | /**
51 | * @throws ApiException
52 | */
53 | public static function getUserEngagement(?Period $period = null): ResponseData
54 | {
55 | return Analytics::query()
56 | ->setMetrics(fn (Metrics $metric) => $metric
57 | ->averageSessionDuration()
58 | ->engagedSessions()
59 | ->sessionsPerUser()
60 | ->sessions()
61 | )
62 | ->forPeriod($period ?? Period::defaultPeriod())
63 | ->run();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Request/Dimensions.php:
--------------------------------------------------------------------------------
1 | */
11 | protected Collection $dimensions;
12 |
13 | public function __construct()
14 | {
15 | $this->dimensions = new Collection;
16 | }
17 |
18 | public function count(): int
19 | {
20 | return $this->dimensions->count();
21 | }
22 |
23 | public function first(): ?Dimension
24 | {
25 | return $this->dimensions->first();
26 | }
27 |
28 | /**
29 | * @return Collection
30 | */
31 | public function getDimensions(): Collection
32 | {
33 | return $this->dimensions;
34 | }
35 |
36 | public function achievementId(): self
37 | {
38 | $this->dimensions->push(new Dimension(['name' => 'achievementId']));
39 |
40 | return $this;
41 | }
42 |
43 | public function adFormat(): self
44 | {
45 | $this->dimensions->push(new Dimension(['name' => 'adFormat']));
46 |
47 | return $this;
48 | }
49 |
50 | public function adSourceName(): self
51 | {
52 | $this->dimensions->push(new Dimension(['name' => 'adSourceName']));
53 |
54 | return $this;
55 | }
56 |
57 | public function adUnitName(): self
58 | {
59 | $this->dimensions->push(new Dimension(['name' => 'adUnitName']));
60 |
61 | return $this;
62 | }
63 |
64 | public function appVersion(): self
65 | {
66 | $this->dimensions->push(new Dimension(['name' => 'appVersion']));
67 |
68 | return $this;
69 | }
70 |
71 | public function audienceId(): self
72 | {
73 | $this->dimensions->push(new Dimension(['name' => 'audienceId']));
74 |
75 | return $this;
76 | }
77 |
78 | public function audienceName(): self
79 | {
80 | $this->dimensions->push(new Dimension(['name' => 'audienceName']));
81 |
82 | return $this;
83 | }
84 |
85 | public function brandingInterest(): self
86 | {
87 | $this->dimensions->push(new Dimension(['name' => 'brandingInterest']));
88 |
89 | return $this;
90 | }
91 |
92 | public function browser(): self
93 | {
94 | $this->dimensions->push(new Dimension(['name' => 'browser']));
95 |
96 | return $this;
97 | }
98 |
99 | public function campaignId(): self
100 | {
101 | $this->dimensions->push(new Dimension(['name' => 'campaignId']));
102 |
103 | return $this;
104 | }
105 |
106 | public function campaignName(): self
107 | {
108 | $this->dimensions->push(new Dimension(['name' => 'campaignName']));
109 |
110 | return $this;
111 | }
112 |
113 | public function character(): self
114 | {
115 | $this->dimensions->push(new Dimension(['name' => 'character']));
116 |
117 | return $this;
118 | }
119 |
120 | public function city(): self
121 | {
122 | $this->dimensions->push(new Dimension(['name' => 'city']));
123 |
124 | return $this;
125 | }
126 |
127 | public function cityId(): self
128 | {
129 | $this->dimensions->push(new Dimension(['name' => 'cityId']));
130 |
131 | return $this;
132 | }
133 |
134 | public function cohort(): self
135 | {
136 | $this->dimensions->push(new Dimension(['name' => 'cohort']));
137 |
138 | return $this;
139 | }
140 |
141 | public function cohortNthDay(): self
142 | {
143 | $this->dimensions->push(new Dimension(['name' => 'cohortNthDay']));
144 |
145 | return $this;
146 | }
147 |
148 | public function cohortNthMonth(): self
149 | {
150 | $this->dimensions->push(new Dimension(['name' => 'cohortNthMonth']));
151 |
152 | return $this;
153 | }
154 |
155 | public function cohortNthWeek(): self
156 | {
157 | $this->dimensions->push(new Dimension(['name' => 'cohortNthWeek']));
158 |
159 | return $this;
160 | }
161 |
162 | public function contentGroup(): self
163 | {
164 | $this->dimensions->push(new Dimension(['name' => 'contentGroup']));
165 |
166 | return $this;
167 | }
168 |
169 | public function contentId(): self
170 | {
171 | $this->dimensions->push(new Dimension(['name' => 'contentId']));
172 |
173 | return $this;
174 | }
175 |
176 | public function contentType(): self
177 | {
178 | $this->dimensions->push(new Dimension(['name' => 'contentType']));
179 |
180 | return $this;
181 | }
182 |
183 | public function country(): self
184 | {
185 | $this->dimensions->push(new Dimension(['name' => 'country']));
186 |
187 | return $this;
188 | }
189 |
190 | public function countryId(): self
191 | {
192 | $this->dimensions->push(new Dimension(['name' => 'countryId']));
193 |
194 | return $this;
195 | }
196 |
197 | public function date(): self
198 | {
199 | $this->dimensions->push(new Dimension(['name' => 'date']));
200 |
201 | return $this;
202 | }
203 |
204 | public function dateHour(): self
205 | {
206 | $this->dimensions->push(new Dimension(['name' => 'dateHour']));
207 |
208 | return $this;
209 | }
210 |
211 | public function dateHourMinute(): self
212 | {
213 | $this->dimensions->push(new Dimension(['name' => 'dateHourMinute']));
214 |
215 | return $this;
216 | }
217 |
218 | public function day(): self
219 | {
220 | $this->dimensions->push(new Dimension(['name' => 'day']));
221 |
222 | return $this;
223 | }
224 |
225 | public function dayOfWeek(): self
226 | {
227 | $this->dimensions->push(new Dimension(['name' => 'dayOfWeek']));
228 |
229 | return $this;
230 | }
231 |
232 | public function defaultChannelGroup(): self
233 | {
234 | $this->dimensions->push(new Dimension(['name' => 'defaultChannelGroup']));
235 |
236 | return $this;
237 | }
238 |
239 | public function deviceCategory(): self
240 | {
241 | $this->dimensions->push(new Dimension(['name' => 'deviceCategory']));
242 |
243 | return $this;
244 | }
245 |
246 | public function deviceModel(): self
247 | {
248 | $this->dimensions->push(new Dimension(['name' => 'deviceModel']));
249 |
250 | return $this;
251 | }
252 |
253 | public function eventName(): self
254 | {
255 | $this->dimensions->push(new Dimension(['name' => 'eventName']));
256 |
257 | return $this;
258 | }
259 |
260 | public function fileExtension(): self
261 | {
262 | $this->dimensions->push(new Dimension(['name' => 'fileExtension']));
263 |
264 | return $this;
265 | }
266 |
267 | public function fileName(): self
268 | {
269 | $this->dimensions->push(new Dimension(['name' => 'fileName']));
270 |
271 | return $this;
272 | }
273 |
274 | public function firstSessionDate(): self
275 | {
276 | $this->dimensions->push(new Dimension(['name' => 'firstSessionDate']));
277 |
278 | return $this;
279 | }
280 |
281 | public function firstUserCampaignId(): self
282 | {
283 | $this->dimensions->push(new Dimension(['name' => 'firstUserCampaignId']));
284 |
285 | return $this;
286 | }
287 |
288 | public function firstUserCampaignName(): self
289 | {
290 | $this->dimensions->push(new Dimension(['name' => 'firstUserCampaignName']));
291 |
292 | return $this;
293 | }
294 |
295 | public function firstUserDefaultChannelGroup(): self
296 | {
297 | $this->dimensions->push(new Dimension(['name' => 'firstUserDefaultChannelGroup']));
298 |
299 | return $this;
300 | }
301 |
302 | public function firstUserGoogleAdsAccountName(): self
303 | {
304 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsAccountName']));
305 |
306 | return $this;
307 | }
308 |
309 | public function firstUserGoogleAdsAdGroupId(): self
310 | {
311 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsAdGroupId']));
312 |
313 | return $this;
314 | }
315 |
316 | public function firstUserGoogleAdsAdGroupName(): self
317 | {
318 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsAdGroupName']));
319 |
320 | return $this;
321 | }
322 |
323 | public function firstUserGoogleAdsAdNetworkType(): self
324 | {
325 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsAdNetworkType']));
326 |
327 | return $this;
328 | }
329 |
330 | public function firstUserGoogleAdsCampaignId(): self
331 | {
332 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsCampaignId']));
333 |
334 | return $this;
335 | }
336 |
337 | public function firstUserGoogleAdsCampaignName(): self
338 | {
339 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsCampaignName']));
340 |
341 | return $this;
342 | }
343 |
344 | public function firstUserGoogleAdsCampaignType(): self
345 | {
346 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsCampaignType']));
347 |
348 | return $this;
349 | }
350 |
351 | public function firstUserGoogleAdsCreativeId(): self
352 | {
353 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsCreativeId']));
354 |
355 | return $this;
356 | }
357 |
358 | public function firstUserGoogleAdsCustomerId(): self
359 | {
360 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsCustomerId']));
361 |
362 | return $this;
363 | }
364 |
365 | public function firstUserGoogleAdsKeyword(): self
366 | {
367 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsKeyword']));
368 |
369 | return $this;
370 | }
371 |
372 | public function firstUserGoogleAdsQuery(): self
373 | {
374 | $this->dimensions->push(new Dimension(['name' => 'firstUserGoogleAdsQuery']));
375 |
376 | return $this;
377 | }
378 |
379 | public function firstUserManualAdContent(): self
380 | {
381 | $this->dimensions->push(new Dimension(['name' => 'firstUserManualAdContent']));
382 |
383 | return $this;
384 | }
385 |
386 | public function firstUserManualTerm(): self
387 | {
388 | $this->dimensions->push(new Dimension(['name' => 'firstUserManualTerm']));
389 |
390 | return $this;
391 | }
392 |
393 | public function firstUserMedium(): self
394 | {
395 | $this->dimensions->push(new Dimension(['name' => 'firstUserMedium']));
396 |
397 | return $this;
398 | }
399 |
400 | public function firstUserSource(): self
401 | {
402 | $this->dimensions->push(new Dimension(['name' => 'firstUserSource']));
403 |
404 | return $this;
405 | }
406 |
407 | public function firstUserSourceMedium(): self
408 | {
409 | $this->dimensions->push(new Dimension(['name' => 'firstUserSourceMedium']));
410 |
411 | return $this;
412 | }
413 |
414 | public function firstUserSourcePlatform(): self
415 | {
416 | $this->dimensions->push(new Dimension(['name' => 'firstUserSourcePlatform']));
417 |
418 | return $this;
419 | }
420 |
421 | public function fullPageUrl(): self
422 | {
423 | $this->dimensions->push(new Dimension(['name' => 'fullPageUrl']));
424 |
425 | return $this;
426 | }
427 |
428 | public function googleAdsAccountName(): self
429 | {
430 | $this->dimensions->push(new Dimension(['name' => 'googleAdsAccountName']));
431 |
432 | return $this;
433 | }
434 |
435 | public function googleAdsAdGroupId(): self
436 | {
437 | $this->dimensions->push(new Dimension(['name' => 'googleAdsAdGroupId']));
438 |
439 | return $this;
440 | }
441 |
442 | public function googleAdsAdGroupName(): self
443 | {
444 | $this->dimensions->push(new Dimension(['name' => 'googleAdsAdGroupName']));
445 |
446 | return $this;
447 | }
448 |
449 | public function googleAdsAdNetworkType(): self
450 | {
451 | $this->dimensions->push(new Dimension(['name' => 'googleAdsAdNetworkType']));
452 |
453 | return $this;
454 | }
455 |
456 | public function googleAdsCampaignId(): self
457 | {
458 | $this->dimensions->push(new Dimension(['name' => 'googleAdsCampaignId']));
459 |
460 | return $this;
461 | }
462 |
463 | public function googleAdsCampaignName(): self
464 | {
465 | $this->dimensions->push(new Dimension(['name' => 'googleAdsCampaignName']));
466 |
467 | return $this;
468 | }
469 |
470 | public function googleAdsCampaignType(): self
471 | {
472 | $this->dimensions->push(new Dimension(['name' => 'googleAdsCampaignType']));
473 |
474 | return $this;
475 | }
476 |
477 | public function googleAdsCreativeId(): self
478 | {
479 | $this->dimensions->push(new Dimension(['name' => 'googleAdsCreativeId']));
480 |
481 | return $this;
482 | }
483 |
484 | public function googleAdsCustomerId(): self
485 | {
486 | $this->dimensions->push(new Dimension(['name' => 'googleAdsCustomerId']));
487 |
488 | return $this;
489 | }
490 |
491 | public function googleAdsKeyword(): self
492 | {
493 | $this->dimensions->push(new Dimension(['name' => 'googleAdsKeyword']));
494 |
495 | return $this;
496 | }
497 |
498 | public function googleAdsQuery(): self
499 | {
500 | $this->dimensions->push(new Dimension(['name' => 'googleAdsQuery']));
501 |
502 | return $this;
503 | }
504 |
505 | public function groupId(): self
506 | {
507 | $this->dimensions->push(new Dimension(['name' => 'groupId']));
508 |
509 | return $this;
510 | }
511 |
512 | public function hostName(): self
513 | {
514 | $this->dimensions->push(new Dimension(['name' => 'hostName']));
515 |
516 | return $this;
517 | }
518 |
519 | public function hour(): self
520 | {
521 | $this->dimensions->push(new Dimension(['name' => 'hour']));
522 |
523 | return $this;
524 | }
525 |
526 | public function isConversionEvent(): self
527 | {
528 | $this->dimensions->push(new Dimension(['name' => 'isConversionEvent']));
529 |
530 | return $this;
531 | }
532 |
533 | public function itemAffiliation(): self
534 | {
535 | $this->dimensions->push(new Dimension(['name' => 'itemAffiliation']));
536 |
537 | return $this;
538 | }
539 |
540 | public function itemBrand(): self
541 | {
542 | $this->dimensions->push(new Dimension(['name' => 'itemBrand']));
543 |
544 | return $this;
545 | }
546 |
547 | public function itemCategory(): self
548 | {
549 | $this->dimensions->push(new Dimension(['name' => 'itemCategory']));
550 |
551 | return $this;
552 | }
553 |
554 | public function itemCategory2(): self
555 | {
556 | $this->dimensions->push(new Dimension(['name' => 'itemCategory2']));
557 |
558 | return $this;
559 | }
560 |
561 | public function itemCategory3(): self
562 | {
563 | $this->dimensions->push(new Dimension(['name' => 'itemCategory3']));
564 |
565 | return $this;
566 | }
567 |
568 | public function itemCategory4(): self
569 | {
570 | $this->dimensions->push(new Dimension(['name' => 'itemCategory4']));
571 |
572 | return $this;
573 | }
574 |
575 | public function itemCategory5(): self
576 | {
577 | $this->dimensions->push(new Dimension(['name' => 'itemCategory5']));
578 |
579 | return $this;
580 | }
581 |
582 | public function itemId(): self
583 | {
584 | $this->dimensions->push(new Dimension(['name' => 'itemId']));
585 |
586 | return $this;
587 | }
588 |
589 | public function itemListId(): self
590 | {
591 | $this->dimensions->push(new Dimension(['name' => 'itemListId']));
592 |
593 | return $this;
594 | }
595 |
596 | public function itemListName(): self
597 | {
598 | $this->dimensions->push(new Dimension(['name' => 'itemListName']));
599 |
600 | return $this;
601 | }
602 |
603 | public function itemName(): self
604 | {
605 | $this->dimensions->push(new Dimension(['name' => 'itemName']));
606 |
607 | return $this;
608 | }
609 |
610 | public function itemPromotionCreativeName(): self
611 | {
612 | $this->dimensions->push(new Dimension(['name' => 'itemPromotionCreativeName']));
613 |
614 | return $this;
615 | }
616 |
617 | public function itemPromotionId(): self
618 | {
619 | $this->dimensions->push(new Dimension(['name' => 'itemPromotionId']));
620 |
621 | return $this;
622 | }
623 |
624 | public function itemPromotionName(): self
625 | {
626 | $this->dimensions->push(new Dimension(['name' => 'itemPromotionName']));
627 |
628 | return $this;
629 | }
630 |
631 | public function itemVariant(): self
632 | {
633 | $this->dimensions->push(new Dimension(['name' => 'itemVariant']));
634 |
635 | return $this;
636 | }
637 |
638 | public function landingPage(): self
639 | {
640 | $this->dimensions->push(new Dimension(['name' => 'landingPage']));
641 |
642 | return $this;
643 | }
644 |
645 | public function language(): self
646 | {
647 | $this->dimensions->push(new Dimension(['name' => 'language']));
648 |
649 | return $this;
650 | }
651 |
652 | public function languageCode(): self
653 | {
654 | $this->dimensions->push(new Dimension(['name' => 'languageCode']));
655 |
656 | return $this;
657 | }
658 |
659 | public function level(): self
660 | {
661 | $this->dimensions->push(new Dimension(['name' => 'level']));
662 |
663 | return $this;
664 | }
665 |
666 | public function linkClasses(): self
667 | {
668 | $this->dimensions->push(new Dimension(['name' => 'linkClasses']));
669 |
670 | return $this;
671 | }
672 |
673 | public function linkDomain(): self
674 | {
675 | $this->dimensions->push(new Dimension(['name' => 'linkDomain']));
676 |
677 | return $this;
678 | }
679 |
680 | public function linkId(): self
681 | {
682 | $this->dimensions->push(new Dimension(['name' => 'linkId']));
683 |
684 | return $this;
685 | }
686 |
687 | public function linkText(): self
688 | {
689 | $this->dimensions->push(new Dimension(['name' => 'linkText']));
690 |
691 | return $this;
692 | }
693 |
694 | public function linkUrl(): self
695 | {
696 | $this->dimensions->push(new Dimension(['name' => 'linkUrl']));
697 |
698 | return $this;
699 | }
700 |
701 | public function manualAdContent(): self
702 | {
703 | $this->dimensions->push(new Dimension(['name' => 'manualAdContent']));
704 |
705 | return $this;
706 | }
707 |
708 | public function manualTerm(): self
709 | {
710 | $this->dimensions->push(new Dimension(['name' => 'manualTerm']));
711 |
712 | return $this;
713 | }
714 |
715 | public function medium(): self
716 | {
717 | $this->dimensions->push(new Dimension(['name' => 'medium']));
718 |
719 | return $this;
720 | }
721 |
722 | public function method(): self
723 | {
724 | $this->dimensions->push(new Dimension(['name' => 'method']));
725 |
726 | return $this;
727 | }
728 |
729 | public function minute(): self
730 | {
731 | $this->dimensions->push(new Dimension(['name' => 'minute']));
732 |
733 | return $this;
734 | }
735 |
736 | public function mobileDeviceBranding(): self
737 | {
738 | $this->dimensions->push(new Dimension(['name' => 'mobileDeviceBranding']));
739 |
740 | return $this;
741 | }
742 |
743 | public function mobileDeviceMarketingName(): self
744 | {
745 | $this->dimensions->push(new Dimension(['name' => 'mobileDeviceMarketingName']));
746 |
747 | return $this;
748 | }
749 |
750 | public function mobileDeviceModel(): self
751 | {
752 | $this->dimensions->push(new Dimension(['name' => 'mobileDeviceModel']));
753 |
754 | return $this;
755 | }
756 |
757 | public function month(): self
758 | {
759 | $this->dimensions->push(new Dimension(['name' => 'month']));
760 |
761 | return $this;
762 | }
763 |
764 | public function newVsReturning(): self
765 | {
766 | $this->dimensions->push(new Dimension(['name' => 'newVsReturning']));
767 |
768 | return $this;
769 | }
770 |
771 | public function nthDay(): self
772 | {
773 | $this->dimensions->push(new Dimension(['name' => 'nthDay']));
774 |
775 | return $this;
776 | }
777 |
778 | public function nthHour(): self
779 | {
780 | $this->dimensions->push(new Dimension(['name' => 'nthHour']));
781 |
782 | return $this;
783 | }
784 |
785 | public function nthMinute(): self
786 | {
787 | $this->dimensions->push(new Dimension(['name' => 'nthMinute']));
788 |
789 | return $this;
790 | }
791 |
792 | public function nthMonth(): self
793 | {
794 | $this->dimensions->push(new Dimension(['name' => 'nthMonth']));
795 |
796 | return $this;
797 | }
798 |
799 | public function nthWeek(): self
800 | {
801 | $this->dimensions->push(new Dimension(['name' => 'nthWeek']));
802 |
803 | return $this;
804 | }
805 |
806 | public function nthYear(): self
807 | {
808 | $this->dimensions->push(new Dimension(['name' => 'nthYear']));
809 |
810 | return $this;
811 | }
812 |
813 | public function operatingSystem(): self
814 | {
815 | $this->dimensions->push(new Dimension(['name' => 'operatingSystem']));
816 |
817 | return $this;
818 | }
819 |
820 | public function operatingSystemVersion(): self
821 | {
822 | $this->dimensions->push(new Dimension(['name' => 'operatingSystemVersion']));
823 |
824 | return $this;
825 | }
826 |
827 | public function operatingSystemWithVersion(): self
828 | {
829 | $this->dimensions->push(new Dimension(['name' => 'operatingSystemWithVersion']));
830 |
831 | return $this;
832 | }
833 |
834 | public function orderCoupon(): self
835 | {
836 | $this->dimensions->push(new Dimension(['name' => 'orderCoupon']));
837 |
838 | return $this;
839 | }
840 |
841 | public function outbound(): self
842 | {
843 | $this->dimensions->push(new Dimension(['name' => 'outbound']));
844 |
845 | return $this;
846 | }
847 |
848 | public function pageLocation(): self
849 | {
850 | $this->dimensions->push(new Dimension(['name' => 'pageLocation']));
851 |
852 | return $this;
853 | }
854 |
855 | public function pagePath(): self
856 | {
857 | $this->dimensions->push(new Dimension(['name' => 'pagePath']));
858 |
859 | return $this;
860 | }
861 |
862 | public function pagePathPlusQueryString(): self
863 | {
864 | $this->dimensions->push(new Dimension(['name' => 'pagePathPlusQueryString']));
865 |
866 | return $this;
867 | }
868 |
869 | public function pageReferrer(): self
870 | {
871 | $this->dimensions->push(new Dimension(['name' => 'pageReferrer']));
872 |
873 | return $this;
874 | }
875 |
876 | public function pageTitle(): self
877 | {
878 | $this->dimensions->push(new Dimension(['name' => 'pageTitle']));
879 |
880 | return $this;
881 | }
882 |
883 | public function percentScrolled(): self
884 | {
885 | $this->dimensions->push(new Dimension(['name' => 'percentScrolled']));
886 |
887 | return $this;
888 | }
889 |
890 | public function platform(): self
891 | {
892 | $this->dimensions->push(new Dimension(['name' => 'platform']));
893 |
894 | return $this;
895 | }
896 |
897 | public function platformDeviceCategory(): self
898 | {
899 | $this->dimensions->push(new Dimension(['name' => 'platformDeviceCategory']));
900 |
901 | return $this;
902 | }
903 |
904 | public function region(): self
905 | {
906 | $this->dimensions->push(new Dimension(['name' => 'region']));
907 |
908 | return $this;
909 | }
910 |
911 | public function screenResolution(): self
912 | {
913 | $this->dimensions->push(new Dimension(['name' => 'screenResolution']));
914 |
915 | return $this;
916 | }
917 |
918 | public function searchTerm(): self
919 | {
920 | $this->dimensions->push(new Dimension(['name' => 'searchTerm']));
921 |
922 | return $this;
923 | }
924 |
925 | public function sessionCampaignId(): self
926 | {
927 | $this->dimensions->push(new Dimension(['name' => 'sessionCampaignId']));
928 |
929 | return $this;
930 | }
931 |
932 | public function sessionCampaignName(): self
933 | {
934 | $this->dimensions->push(new Dimension(['name' => 'sessionCampaignName']));
935 |
936 | return $this;
937 | }
938 |
939 | public function sessionDefaultChannelGroup(): self
940 | {
941 | $this->dimensions->push(new Dimension(['name' => 'sessionDefaultChannelGroup']));
942 |
943 | return $this;
944 | }
945 |
946 | public function sessionGoogleAdsAccountName(): self
947 | {
948 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsAccountName']));
949 |
950 | return $this;
951 | }
952 |
953 | public function sessionGoogleAdsAdGroupId(): self
954 | {
955 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsAdGroupId']));
956 |
957 | return $this;
958 | }
959 |
960 | public function sessionGoogleAdsAdGroupName(): self
961 | {
962 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsAdGroupName']));
963 |
964 | return $this;
965 | }
966 |
967 | public function sessionGoogleAdsAdNetworkType(): self
968 | {
969 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsAdNetworkType']));
970 |
971 | return $this;
972 | }
973 |
974 | public function sessionGoogleAdsCampaignId(): self
975 | {
976 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsCampaignId']));
977 |
978 | return $this;
979 | }
980 |
981 | public function sessionGoogleAdsCampaignName(): self
982 | {
983 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsCampaignName']));
984 |
985 | return $this;
986 | }
987 |
988 | public function sessionGoogleAdsCampaignType(): self
989 | {
990 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsCampaignType']));
991 |
992 | return $this;
993 | }
994 |
995 | public function sessionGoogleAdsCreativeId(): self
996 | {
997 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsCreativeId']));
998 |
999 | return $this;
1000 | }
1001 |
1002 | public function sessionGoogleAdsCustomerId(): self
1003 | {
1004 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsCustomerId']));
1005 |
1006 | return $this;
1007 | }
1008 |
1009 | public function sessionGoogleAdsKeyword(): self
1010 | {
1011 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsKeyword']));
1012 |
1013 | return $this;
1014 | }
1015 |
1016 | public function sessionGoogleAdsQuery(): self
1017 | {
1018 | $this->dimensions->push(new Dimension(['name' => 'sessionGoogleAdsQuery']));
1019 |
1020 | return $this;
1021 | }
1022 |
1023 | public function sessionManualAdContent(): self
1024 | {
1025 | $this->dimensions->push(new Dimension(['name' => 'sessionManualAdContent']));
1026 |
1027 | return $this;
1028 | }
1029 |
1030 | public function sessionManualTerm(): self
1031 | {
1032 | $this->dimensions->push(new Dimension(['name' => 'sessionManualTerm']));
1033 |
1034 | return $this;
1035 | }
1036 |
1037 | public function sessionMedium(): self
1038 | {
1039 | $this->dimensions->push(new Dimension(['name' => 'sessionMedium']));
1040 |
1041 | return $this;
1042 | }
1043 |
1044 | public function sessionSa360AdGroupName(): self
1045 | {
1046 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360AdGroupName']));
1047 |
1048 | return $this;
1049 | }
1050 |
1051 | public function sessionSa360CampaignId(): self
1052 | {
1053 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360CampaignId']));
1054 |
1055 | return $this;
1056 | }
1057 |
1058 | public function sessionSa360CampaignName(): self
1059 | {
1060 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360CampaignName']));
1061 |
1062 | return $this;
1063 | }
1064 |
1065 | public function sessionSa360CreativeFormat(): self
1066 | {
1067 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360CreativeFormat']));
1068 |
1069 | return $this;
1070 | }
1071 |
1072 | public function sessionSa360EngineAccountId(): self
1073 | {
1074 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360EngineAccountId']));
1075 |
1076 | return $this;
1077 | }
1078 |
1079 | public function sessionSa360EngineAccountName(): self
1080 | {
1081 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360EngineAccountName']));
1082 |
1083 | return $this;
1084 | }
1085 |
1086 | public function sessionSa360EngineAccountType(): self
1087 | {
1088 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360EngineAccountType']));
1089 |
1090 | return $this;
1091 | }
1092 |
1093 | public function sessionSa360Keyword(): self
1094 | {
1095 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360Keyword']));
1096 |
1097 | return $this;
1098 | }
1099 |
1100 | public function sessionSa360Medium(): self
1101 | {
1102 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360Medium']));
1103 |
1104 | return $this;
1105 | }
1106 |
1107 | public function sessionSa360Query(): self
1108 | {
1109 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360Query']));
1110 |
1111 | return $this;
1112 | }
1113 |
1114 | public function sessionSa360Source(): self
1115 | {
1116 | $this->dimensions->push(new Dimension(['name' => 'sessionSa360Source']));
1117 |
1118 | return $this;
1119 | }
1120 |
1121 | public function sessionSource(): self
1122 | {
1123 | $this->dimensions->push(new Dimension(['name' => 'sessionSource']));
1124 |
1125 | return $this;
1126 | }
1127 |
1128 | public function sessionSourceMedium(): self
1129 | {
1130 | $this->dimensions->push(new Dimension(['name' => 'sessionSourceMedium']));
1131 |
1132 | return $this;
1133 | }
1134 |
1135 | public function sessionSourcePlatform(): self
1136 | {
1137 | $this->dimensions->push(new Dimension(['name' => 'sessionSourcePlatform']));
1138 |
1139 | return $this;
1140 | }
1141 |
1142 | public function shippingTier(): self
1143 | {
1144 | $this->dimensions->push(new Dimension(['name' => 'shippingTier']));
1145 |
1146 | return $this;
1147 | }
1148 |
1149 | public function signedInWithUserId(): self
1150 | {
1151 | $this->dimensions->push(new Dimension(['name' => 'signedInWithUserId']));
1152 |
1153 | return $this;
1154 | }
1155 |
1156 | public function source(): self
1157 | {
1158 | $this->dimensions->push(new Dimension(['name' => 'source']));
1159 |
1160 | return $this;
1161 | }
1162 |
1163 | public function sourceMedium(): self
1164 | {
1165 | $this->dimensions->push(new Dimension(['name' => 'sourceMedium']));
1166 |
1167 | return $this;
1168 | }
1169 |
1170 | public function sourcePlatform(): self
1171 | {
1172 | $this->dimensions->push(new Dimension(['name' => 'sourcePlatform']));
1173 |
1174 | return $this;
1175 | }
1176 |
1177 | public function streamId(): self
1178 | {
1179 | $this->dimensions->push(new Dimension(['name' => 'streamId']));
1180 |
1181 | return $this;
1182 | }
1183 |
1184 | public function streamName(): self
1185 | {
1186 | $this->dimensions->push(new Dimension(['name' => 'streamName']));
1187 |
1188 | return $this;
1189 | }
1190 |
1191 | public function testDataFilterName(): self
1192 | {
1193 | $this->dimensions->push(new Dimension(['name' => 'testDataFilterName']));
1194 |
1195 | return $this;
1196 | }
1197 |
1198 | public function transactionId(): self
1199 | {
1200 | $this->dimensions->push(new Dimension(['name' => 'transactionId']));
1201 |
1202 | return $this;
1203 | }
1204 |
1205 | public function unifiedPagePathScreen(): self
1206 | {
1207 | $this->dimensions->push(new Dimension(['name' => 'unifiedPagePathScreen']));
1208 |
1209 | return $this;
1210 | }
1211 |
1212 | public function unifiedPageScreen(): self
1213 | {
1214 | $this->dimensions->push(new Dimension(['name' => 'unifiedPageScreen']));
1215 |
1216 | return $this;
1217 | }
1218 |
1219 | public function unifiedScreenClass(): self
1220 | {
1221 | $this->dimensions->push(new Dimension(['name' => 'unifiedScreenClass']));
1222 |
1223 | return $this;
1224 | }
1225 |
1226 | public function unifiedScreenName(): self
1227 | {
1228 | $this->dimensions->push(new Dimension(['name' => 'unifiedScreenName']));
1229 |
1230 | return $this;
1231 | }
1232 |
1233 | public function userAgeBracket(): self
1234 | {
1235 | $this->dimensions->push(new Dimension(['name' => 'userAgeBracket']));
1236 |
1237 | return $this;
1238 | }
1239 |
1240 | public function userGender(): self
1241 | {
1242 | $this->dimensions->push(new Dimension(['name' => 'userGender']));
1243 |
1244 | return $this;
1245 | }
1246 |
1247 | public function videoProvider(): self
1248 | {
1249 | $this->dimensions->push(new Dimension(['name' => 'videoProvider']));
1250 |
1251 | return $this;
1252 | }
1253 |
1254 | public function videoTitle(): self
1255 | {
1256 | $this->dimensions->push(new Dimension(['name' => 'videoTitle']));
1257 |
1258 | return $this;
1259 | }
1260 |
1261 | public function videoUrl(): self
1262 | {
1263 | $this->dimensions->push(new Dimension(['name' => 'videoUrl']));
1264 |
1265 | return $this;
1266 | }
1267 |
1268 | public function virtualCurrencyName(): self
1269 | {
1270 | $this->dimensions->push(new Dimension(['name' => 'virtualCurrencyName']));
1271 |
1272 | return $this;
1273 | }
1274 |
1275 | public function visible(): self
1276 | {
1277 | $this->dimensions->push(new Dimension(['name' => 'visible']));
1278 |
1279 | return $this;
1280 | }
1281 |
1282 | public function week(): self
1283 | {
1284 | $this->dimensions->push(new Dimension(['name' => 'week']));
1285 |
1286 | return $this;
1287 | }
1288 |
1289 | public function year(): self
1290 | {
1291 | $this->dimensions->push(new Dimension(['name' => 'year']));
1292 |
1293 | return $this;
1294 | }
1295 | }
1296 |
--------------------------------------------------------------------------------
/src/Request/Filters/AndGroup.php:
--------------------------------------------------------------------------------
1 | expression = $expression(new FilterExpressionList);
20 | }
21 |
22 | public function toRequest(): BaseFilterExpressionList
23 | {
24 | return $this->expression->toRequest();
25 | }
26 |
27 | public function field(): FilterExpressionField
28 | {
29 | return $this->field;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Request/Filters/BetweenFilter.php:
--------------------------------------------------------------------------------
1 | new NumericValue([
21 | $this->valueType->value => $this->min,
22 | ]),
23 | 'to_value' => new NumericValue([
24 | $this->valueType->value => $this->max,
25 | ]),
26 | ]);
27 | }
28 |
29 | public function field(): FilterField
30 | {
31 | return $this->field;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Request/Filters/Filter.php:
--------------------------------------------------------------------------------
1 | fieldName = $fieldName;
20 | }
21 |
22 | public function exact(string $value, bool $caseSensitive = false): static
23 | {
24 | $this->expression = new StringFilter(
25 | matchType: MatchType::EXACT,
26 | value: $value,
27 | caseSensitive: $caseSensitive,
28 | );
29 |
30 | return $this;
31 | }
32 |
33 | public function beginsWith(string $value, bool $caseSensitive = false): static
34 | {
35 | $this->expression = new StringFilter(
36 | matchType: MatchType::BEGINS_WITH,
37 | value: $value,
38 | caseSensitive: $caseSensitive,
39 | );
40 |
41 | return $this;
42 | }
43 |
44 | public function endsWith(string $value, bool $caseSensitive = false): static
45 | {
46 | $this->expression = new StringFilter(
47 | matchType: MatchType::ENDS_WITH,
48 | value: $value,
49 | caseSensitive: $caseSensitive,
50 | );
51 |
52 | return $this;
53 | }
54 |
55 | public function contains(string $value, bool $caseSensitive = false): static
56 | {
57 | $this->expression = new StringFilter(
58 | matchType: MatchType::CONTAINS,
59 | value: $value,
60 | caseSensitive: $caseSensitive,
61 | );
62 |
63 | return $this;
64 | }
65 |
66 | public function fullRegexp(string $value, bool $caseSensitive = false): static
67 | {
68 | $this->expression = new StringFilter(
69 | matchType: MatchType::FULL_REGEXP,
70 | value: $value,
71 | caseSensitive: $caseSensitive,
72 | );
73 |
74 | return $this;
75 | }
76 |
77 | public function partialRegexp(string $value, bool $caseSensitive = false): static
78 | {
79 | $this->expression = new StringFilter(
80 | matchType: MatchType::PARTIAL_REGEXP,
81 | value: $value,
82 | caseSensitive: $caseSensitive,
83 | );
84 |
85 | return $this;
86 | }
87 |
88 | /**
89 | * @param list $values
90 | */
91 | public function inList(array $values, bool $caseSensitive = false): static
92 | {
93 | $this->expression = new InListFilter(
94 | values: $values,
95 | caseSensitive: $caseSensitive,
96 | );
97 |
98 | return $this;
99 | }
100 |
101 | public function equalInt(int $value): static
102 | {
103 | $this->expression = new NumericFilter(
104 | operation: Operation::EQUAL,
105 | value: $value,
106 | valueType: NumericValueType::INTEGER,
107 | );
108 |
109 | return $this;
110 | }
111 |
112 | public function equalFloat(float $value): static
113 | {
114 | $this->expression = new NumericFilter(
115 | operation: Operation::EQUAL,
116 | value: $value,
117 | valueType: NumericValueType::FLOAT,
118 | );
119 |
120 | return $this;
121 | }
122 |
123 | public function lessThanInt(int $value): static
124 | {
125 | $this->expression = new NumericFilter(
126 | operation: Operation::LESS_THAN,
127 | value: $value,
128 | valueType: NumericValueType::INTEGER,
129 | );
130 |
131 | return $this;
132 | }
133 |
134 | public function lessThanFloat(float $value): static
135 | {
136 | $this->expression = new NumericFilter(
137 | operation: Operation::LESS_THAN,
138 | value: $value,
139 | valueType: NumericValueType::FLOAT,
140 | );
141 |
142 | return $this;
143 | }
144 |
145 | public function lessThanOrEqualInt(int $value): static
146 | {
147 | $this->expression = new NumericFilter(
148 | operation: Operation::LESS_THAN_OR_EQUAL,
149 | value: $value,
150 | valueType: NumericValueType::INTEGER,
151 | );
152 |
153 | return $this;
154 | }
155 |
156 | public function lessThanOrEqualFloat(float $value): static
157 | {
158 | $this->expression = new NumericFilter(
159 | operation: Operation::LESS_THAN_OR_EQUAL,
160 | value: $value,
161 | valueType: NumericValueType::FLOAT,
162 | );
163 |
164 | return $this;
165 | }
166 |
167 | public function greaterThanInt(int $value): static
168 | {
169 | $this->expression = new NumericFilter(
170 | operation: Operation::GREATER_THAN,
171 | value: $value,
172 | valueType: NumericValueType::INTEGER,
173 | );
174 |
175 | return $this;
176 | }
177 |
178 | public function greaterThanFloat(float $value): static
179 | {
180 | $this->expression = new NumericFilter(
181 | operation: Operation::GREATER_THAN,
182 | value: $value,
183 | valueType: NumericValueType::FLOAT,
184 | );
185 |
186 | return $this;
187 | }
188 |
189 | public function greaterThanOrEqualInt(int $value): static
190 | {
191 | $this->expression = new NumericFilter(
192 | operation: Operation::GREATER_THAN_OR_EQUAL,
193 | value: $value,
194 | valueType: NumericValueType::INTEGER,
195 | );
196 |
197 | return $this;
198 | }
199 |
200 | public function greaterThanOrEqualFloat(float $value): static
201 | {
202 | $this->expression = new NumericFilter(
203 | operation: Operation::GREATER_THAN_OR_EQUAL,
204 | value: $value,
205 | valueType: NumericValueType::FLOAT,
206 | );
207 |
208 | return $this;
209 | }
210 |
211 | public function betweenInt(int $min, int $max): static
212 | {
213 | $this->expression = new BetweenFilter(
214 | min: $min,
215 | max: $max,
216 | valueType: NumericValueType::INTEGER,
217 | );
218 |
219 | return $this;
220 | }
221 |
222 | public function betweenFloat(float $min, float $max): static
223 | {
224 | $this->expression = new BetweenFilter(
225 | min: $min,
226 | max: $max,
227 | valueType: NumericValueType::FLOAT,
228 | );
229 |
230 | return $this;
231 | }
232 |
233 | public function toRequest(): BaseFilter
234 | {
235 | return new BaseFilter([
236 | 'field_name' => $this->fieldName,
237 | $this->expression?->field()->value => $this->expression?->toRequest(),
238 | ]);
239 | }
240 |
241 | public function field(): FilterExpressionField
242 | {
243 | return $this->field;
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/src/Request/Filters/FilterContract.php:
--------------------------------------------------------------------------------
1 | expression = new AndGroup($filterExpressionList);
21 |
22 | return $this;
23 | }
24 |
25 | /**
26 | * @param Closure(FilterExpressionList): FilterExpressionList $filterExpressionList
27 | */
28 | public function orGroup(Closure $filterExpressionList): static
29 | {
30 | $this->expression = new OrGroup($filterExpressionList);
31 |
32 | return $this;
33 | }
34 |
35 | /**
36 | * @param Closure(FilterExpression): FilterExpression $filterExpression
37 | */
38 | public function not(Closure $filterExpression): static
39 | {
40 | $this->expression = new NotExpression($filterExpression);
41 |
42 | return $this;
43 | }
44 |
45 | /**
46 | * @param Closure(Filter): Filter $filter
47 | */
48 | public function filter(string $dimension, Closure $filter): static
49 | {
50 | $this->expression = $filter(new Filter($dimension));
51 |
52 | return $this;
53 | }
54 |
55 | /**
56 | * @param Closure(Filter): Filter $filter
57 | *
58 | * @throws InvalidFilterException
59 | */
60 | public function filterDimension(Closure $dimensionsCallback, Closure $filter): static
61 | {
62 | /** @var Dimensions $dimensions */
63 | $dimensions = $dimensionsCallback(resolve(Dimensions::class));
64 |
65 | $firstDimension = $dimensions->first();
66 |
67 | if ($firstDimension === null) {
68 | throw InvalidFilterException::noDimensionFilter();
69 | }
70 |
71 | return $this->filter($firstDimension->getName(), $filter);
72 | }
73 |
74 | /**
75 | * @param Closure(Filter): Filter $filter
76 | *
77 | * @throws InvalidFilterException
78 | */
79 | public function filterMetric(Closure $metricsCallback, Closure $filter): static
80 | {
81 | /** @var Metrics $metrics */
82 | $metrics = $metricsCallback(resolve(Metrics::class));
83 |
84 | $firstMetric = $metrics->first();
85 |
86 | if ($firstMetric === null) {
87 | throw InvalidFilterException::noMetricFilter();
88 | }
89 |
90 | return $this->filter($firstMetric->getName(), $filter);
91 | }
92 |
93 | public function toRequest(): BaseFilterExpression
94 | {
95 | return new BaseFilterExpression([
96 | $this->expression->field()->value => $this->expression->toRequest(),
97 | ]);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Request/Filters/FilterExpressionContract.php:
--------------------------------------------------------------------------------
1 | */
14 | private readonly Collection $expressions = new Collection,
15 | ) {}
16 |
17 | /**
18 | * @param Closure(FilterExpressionList): FilterExpressionList $filterExpressionList
19 | */
20 | public function andGroup(Closure $filterExpressionList): static
21 | {
22 | $this->expressions->push((new FilterExpression)->andGroup($filterExpressionList));
23 |
24 | return $this;
25 | }
26 |
27 | /**
28 | * @param Closure(FilterExpressionList): FilterExpressionList $filterExpressionList
29 | */
30 | public function orGroup(Closure $filterExpressionList): static
31 | {
32 | $this->expressions->push((new FilterExpression)->orGroup($filterExpressionList));
33 |
34 | return $this;
35 | }
36 |
37 | /**
38 | * @param Closure(FilterExpression): FilterExpression $filterExpression
39 | */
40 | public function not(Closure $filterExpression): static
41 | {
42 | $this->expressions->push((new FilterExpression)->not($filterExpression));
43 |
44 | return $this;
45 | }
46 |
47 | /**
48 | * @param Closure(Filter): Filter $filter
49 | */
50 | public function filter(string $dimension, Closure $filter): static
51 | {
52 | $this->expressions->push((new FilterExpression)->filter($dimension, $filter));
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * @param Closure(Filter): Filter $filter
59 | *
60 | * @throws InvalidFilterException
61 | */
62 | public function filterDimension(Closure $dimensionsCallback, Closure $filter): static
63 | {
64 | $this->expressions->push((new FilterExpression)->filterDimension($dimensionsCallback, $filter));
65 |
66 | return $this;
67 | }
68 |
69 | /**
70 | * @param Closure(Filter): Filter $filter
71 | *
72 | * @throws InvalidFilterException
73 | */
74 | public function filterMetric(Closure $metricsCallback, Closure $filter): static
75 | {
76 | $this->expressions->push((new FilterExpression)->filterMetric($metricsCallback, $filter));
77 |
78 | return $this;
79 | }
80 |
81 | public function toRequest(): BaseFilterExpressionList
82 | {
83 | return new BaseFilterExpressionList([
84 | 'expressions' => $this->expressions
85 | ->map(fn (FilterExpression $filterExpression) => $filterExpression->toRequest())
86 | ->toArray(),
87 | ]);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Request/Filters/FilterField.php:
--------------------------------------------------------------------------------
1 | $values
11 | */
12 | public function __construct(
13 | public array $values = [],
14 | public bool $caseSensitive = false,
15 | private readonly FilterField $field = FilterField::IN_LIST_FILTER,
16 | ) {}
17 |
18 | public function field(): FilterField
19 | {
20 | return $this->field;
21 | }
22 |
23 | public function toRequest(): BaseInListFilter
24 | {
25 | return new BaseInListFilter([
26 | 'values' => $this->values,
27 | 'case_sensitive' => $this->caseSensitive,
28 | ]);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Request/Filters/NotExpression.php:
--------------------------------------------------------------------------------
1 | expression = $expression(new FilterExpression);
20 | }
21 |
22 | public function toRequest(): BaseFilterExpression
23 | {
24 | return $this->expression->toRequest();
25 | }
26 |
27 | public function field(): FilterExpressionField
28 | {
29 | return $this->field;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Request/Filters/NumericFilter.php:
--------------------------------------------------------------------------------
1 | $this->operation,
22 | 'value' => new NumericValue([
23 | $this->valueType->value => $this->value,
24 | ]),
25 | ]);
26 | }
27 |
28 | public function field(): FilterField
29 | {
30 | return $this->field;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Request/Filters/NumericValueType.php:
--------------------------------------------------------------------------------
1 | expression = $expression(new FilterExpressionList);
20 | }
21 |
22 | public function toRequest(): BaseFilterExpressionList
23 | {
24 | return $this->expression->toRequest();
25 | }
26 |
27 | public function field(): FilterExpressionField
28 | {
29 | return $this->field;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Request/Filters/StringFilter.php:
--------------------------------------------------------------------------------
1 | $this->matchType,
21 | 'value' => $this->value,
22 | 'case_sensitive' => $this->caseSensitive,
23 | ]);
24 | }
25 |
26 | public function field(): FilterField
27 | {
28 | return $this->field;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Request/Metrics.php:
--------------------------------------------------------------------------------
1 | */
11 | protected Collection $metrics;
12 |
13 | public function __construct()
14 | {
15 | $this->metrics = new Collection;
16 | }
17 |
18 | public function count(): int
19 | {
20 | return $this->metrics->count();
21 | }
22 |
23 | public function first(): ?Metric
24 | {
25 | return $this->metrics->first();
26 | }
27 |
28 | /**
29 | * @return Collection
30 | */
31 | public function getMetrics(): Collection
32 | {
33 | return $this->metrics;
34 | }
35 |
36 | public function active1DayUsers(): self
37 | {
38 | $this->metrics->push(new Metric(['name' => 'active1DayUsers']));
39 |
40 | return $this;
41 | }
42 |
43 | public function active28DayUsers(): self
44 | {
45 | $this->metrics->push(new Metric(['name' => 'active28DayUsers']));
46 |
47 | return $this;
48 | }
49 |
50 | public function active7DayUsers(): self
51 | {
52 | $this->metrics->push(new Metric(['name' => 'active7DayUsers']));
53 |
54 | return $this;
55 | }
56 |
57 | public function activeUsers(): self
58 | {
59 | $this->metrics->push(new Metric(['name' => 'activeUsers']));
60 |
61 | return $this;
62 | }
63 |
64 | public function adUnitExposure(): self
65 | {
66 | $this->metrics->push(new Metric(['name' => 'adUnitExposure']));
67 |
68 | return $this;
69 | }
70 |
71 | public function addToCarts(): self
72 | {
73 | $this->metrics->push(new Metric(['name' => 'addToCarts']));
74 |
75 | return $this;
76 | }
77 |
78 | public function advertiserAdClicks(): self
79 | {
80 | $this->metrics->push(new Metric(['name' => 'advertiserAdClicks']));
81 |
82 | return $this;
83 | }
84 |
85 | public function advertiserAdCost(): self
86 | {
87 | $this->metrics->push(new Metric(['name' => 'advertiserAdCost']));
88 |
89 | return $this;
90 | }
91 |
92 | public function advertiserAdCostPerClick(): self
93 | {
94 | $this->metrics->push(new Metric(['name' => 'advertiserAdCostPerClick']));
95 |
96 | return $this;
97 | }
98 |
99 | public function advertiserAdCostPerConversion(): self
100 | {
101 | $this->metrics->push(new Metric(['name' => 'advertiserAdCostPerConversion']));
102 |
103 | return $this;
104 | }
105 |
106 | public function advertiserAdImpressions(): self
107 | {
108 | $this->metrics->push(new Metric(['name' => 'advertiserAdImpressions']));
109 |
110 | return $this;
111 | }
112 |
113 | public function averagePurchaseRevenue(): self
114 | {
115 | $this->metrics->push(new Metric(['name' => 'averagePurchaseRevenue']));
116 |
117 | return $this;
118 | }
119 |
120 | public function averagePurchaseRevenuePerPayingUser(): self
121 | {
122 | $this->metrics->push(new Metric(['name' => 'averagePurchaseRevenuePerPayingUser']));
123 |
124 | return $this;
125 | }
126 |
127 | public function averagePurchaseRevenuePerUser(): self
128 | {
129 | $this->metrics->push(new Metric(['name' => 'averagePurchaseRevenuePerUser']));
130 |
131 | return $this;
132 | }
133 |
134 | public function averageRevenuePerUser(): self
135 | {
136 | $this->metrics->push(new Metric(['name' => 'averageRevenuePerUser']));
137 |
138 | return $this;
139 | }
140 |
141 | public function averageSessionDuration(): self
142 | {
143 | $this->metrics->push(new Metric(['name' => 'averageSessionDuration']));
144 |
145 | return $this;
146 | }
147 |
148 | public function bounceRate(): self
149 | {
150 | $this->metrics->push(new Metric(['name' => 'bounceRate']));
151 |
152 | return $this;
153 | }
154 |
155 | public function cartToViewRate(): self
156 | {
157 | $this->metrics->push(new Metric(['name' => 'cartToViewRate']));
158 |
159 | return $this;
160 | }
161 |
162 | public function checkouts(): self
163 | {
164 | $this->metrics->push(new Metric(['name' => 'checkouts']));
165 |
166 | return $this;
167 | }
168 |
169 | public function cohortActiveUsers(): self
170 | {
171 | $this->metrics->push(new Metric(['name' => 'cohortActiveUsers']));
172 |
173 | return $this;
174 | }
175 |
176 | public function cohortTotalUsers(): self
177 | {
178 | $this->metrics->push(new Metric(['name' => 'cohortTotalUsers']));
179 |
180 | return $this;
181 | }
182 |
183 | public function conversions(): self
184 | {
185 | $this->metrics->push(new Metric(['name' => 'conversions']));
186 |
187 | return $this;
188 | }
189 |
190 | public function crashAffectedUsers(): self
191 | {
192 | $this->metrics->push(new Metric(['name' => 'crashAffectedUsers']));
193 |
194 | return $this;
195 | }
196 |
197 | public function crashFreeUsersRate(): self
198 | {
199 | $this->metrics->push(new Metric(['name' => 'crashFreeUsersRate']));
200 |
201 | return $this;
202 | }
203 |
204 | public function dauPerMau(): self
205 | {
206 | $this->metrics->push(new Metric(['name' => 'dauPerMau']));
207 |
208 | return $this;
209 | }
210 |
211 | public function dauPerWau(): self
212 | {
213 | $this->metrics->push(new Metric(['name' => 'dauPerWau']));
214 |
215 | return $this;
216 | }
217 |
218 | public function ecommercePurchases(): self
219 | {
220 | $this->metrics->push(new Metric(['name' => 'ecommercePurchases']));
221 |
222 | return $this;
223 | }
224 |
225 | public function engagedSessions(): self
226 | {
227 | $this->metrics->push(new Metric(['name' => 'engagedSessions']));
228 |
229 | return $this;
230 | }
231 |
232 | public function engagementRate(): self
233 | {
234 | $this->metrics->push(new Metric(['name' => 'engagementRate']));
235 |
236 | return $this;
237 | }
238 |
239 | public function eventCount(): self
240 | {
241 | $this->metrics->push(new Metric(['name' => 'eventCount']));
242 |
243 | return $this;
244 | }
245 |
246 | public function eventCountPerUser(): self
247 | {
248 | $this->metrics->push(new Metric(['name' => 'eventCountPerUser']));
249 |
250 | return $this;
251 | }
252 |
253 | public function eventValue(): self
254 | {
255 | $this->metrics->push(new Metric(['name' => 'eventValue']));
256 |
257 | return $this;
258 | }
259 |
260 | public function eventsPerSession(): self
261 | {
262 | $this->metrics->push(new Metric(['name' => 'eventsPerSession']));
263 |
264 | return $this;
265 | }
266 |
267 | public function firstTimePurchaserConversionRate(): self
268 | {
269 | $this->metrics->push(new Metric(['name' => 'firstTimePurchaserConversionRate']));
270 |
271 | return $this;
272 | }
273 |
274 | public function firstTimePurchasers(): self
275 | {
276 | $this->metrics->push(new Metric(['name' => 'firstTimePurchasers']));
277 |
278 | return $this;
279 | }
280 |
281 | public function firstTimePurchasersPerNewUser(): self
282 | {
283 | $this->metrics->push(new Metric(['name' => 'firstTimePurchasersPerNewUser']));
284 |
285 | return $this;
286 | }
287 |
288 | public function itemListClickEvents(): self
289 | {
290 | $this->metrics->push(new Metric(['name' => 'itemListClickEvents']));
291 |
292 | return $this;
293 | }
294 |
295 | public function itemListClickThroughRate(): self
296 | {
297 | $this->metrics->push(new Metric(['name' => 'itemListClickThroughRate']));
298 |
299 | return $this;
300 | }
301 |
302 | public function itemListViewEvents(): self
303 | {
304 | $this->metrics->push(new Metric(['name' => 'itemListViewEvents']));
305 |
306 | return $this;
307 | }
308 |
309 | public function itemPromotionClickThroughRate(): self
310 | {
311 | $this->metrics->push(new Metric(['name' => 'itemPromotionClickThroughRate']));
312 |
313 | return $this;
314 | }
315 |
316 | public function itemRevenue(): self
317 | {
318 | $this->metrics->push(new Metric(['name' => 'itemRevenue']));
319 |
320 | return $this;
321 | }
322 |
323 | public function itemViewEvents(): self
324 | {
325 | $this->metrics->push(new Metric(['name' => 'itemViewEvents']));
326 |
327 | return $this;
328 | }
329 |
330 | public function itemsAddedToCart(): self
331 | {
332 | $this->metrics->push(new Metric(['name' => 'itemsAddedToCart']));
333 |
334 | return $this;
335 | }
336 |
337 | public function itemsCheckedOut(): self
338 | {
339 | $this->metrics->push(new Metric(['name' => 'itemsCheckedOut']));
340 |
341 | return $this;
342 | }
343 |
344 | public function itemsClickedInList(): self
345 | {
346 | $this->metrics->push(new Metric(['name' => 'itemsClickedInList']));
347 |
348 | return $this;
349 | }
350 |
351 | public function itemsClickedInPromotion(): self
352 | {
353 | $this->metrics->push(new Metric(['name' => 'itemsClickedInPromotion']));
354 |
355 | return $this;
356 | }
357 |
358 | public function itemsPurchased(): self
359 | {
360 | $this->metrics->push(new Metric(['name' => 'itemsPurchased']));
361 |
362 | return $this;
363 | }
364 |
365 | public function itemsViewed(): self
366 | {
367 | $this->metrics->push(new Metric(['name' => 'itemsViewed']));
368 |
369 | return $this;
370 | }
371 |
372 | public function itemsViewedInList(): self
373 | {
374 | $this->metrics->push(new Metric(['name' => 'itemsViewedInList']));
375 |
376 | return $this;
377 | }
378 |
379 | public function itemsViewedInPromotion(): self
380 | {
381 | $this->metrics->push(new Metric(['name' => 'itemsViewedInPromotion']));
382 |
383 | return $this;
384 | }
385 |
386 | public function newUsers(): self
387 | {
388 | $this->metrics->push(new Metric(['name' => 'newUsers']));
389 |
390 | return $this;
391 | }
392 |
393 | public function organicGoogleSearchAveragePosition(): self
394 | {
395 | $this->metrics->push(new Metric(['name' => 'organicGoogleSearchAveragePosition']));
396 |
397 | return $this;
398 | }
399 |
400 | public function organicGoogleSearchClickThroughRate(): self
401 | {
402 | $this->metrics->push(new Metric(['name' => 'organicGoogleSearchClickThroughRate']));
403 |
404 | return $this;
405 | }
406 |
407 | public function organicGoogleSearchClicks(): self
408 | {
409 | $this->metrics->push(new Metric(['name' => 'organicGoogleSearchClicks']));
410 |
411 | return $this;
412 | }
413 |
414 | public function organicGoogleSearchImpressions(): self
415 | {
416 | $this->metrics->push(new Metric(['name' => 'organicGoogleSearchImpressions']));
417 |
418 | return $this;
419 | }
420 |
421 | public function promotionClicks(): self
422 | {
423 | $this->metrics->push(new Metric(['name' => 'promotionClicks']));
424 |
425 | return $this;
426 | }
427 |
428 | public function promotionViews(): self
429 | {
430 | $this->metrics->push(new Metric(['name' => 'promotionViews']));
431 |
432 | return $this;
433 | }
434 |
435 | public function publisherAdClicks(): self
436 | {
437 | $this->metrics->push(new Metric(['name' => 'publisherAdClicks']));
438 |
439 | return $this;
440 | }
441 |
442 | public function publisherAdImpressions(): self
443 | {
444 | $this->metrics->push(new Metric(['name' => 'publisherAdImpressions']));
445 |
446 | return $this;
447 | }
448 |
449 | public function purchaseRevenue(): self
450 | {
451 | $this->metrics->push(new Metric(['name' => 'purchaseRevenue']));
452 |
453 | return $this;
454 | }
455 |
456 | public function purchaseToViewRate(): self
457 | {
458 | $this->metrics->push(new Metric(['name' => 'purchaseToViewRate']));
459 |
460 | return $this;
461 | }
462 |
463 | public function purchaserConversionRate(): self
464 | {
465 | $this->metrics->push(new Metric(['name' => 'purchaserConversionRate']));
466 |
467 | return $this;
468 | }
469 |
470 | public function returnOnAdSpend(): self
471 | {
472 | $this->metrics->push(new Metric(['name' => 'returnOnAdSpend']));
473 |
474 | return $this;
475 | }
476 |
477 | public function screenPageViews(): self
478 | {
479 | $this->metrics->push(new Metric(['name' => 'screenPageViews']));
480 |
481 | return $this;
482 | }
483 |
484 | public function screenPageViewsPerSession(): self
485 | {
486 | $this->metrics->push(new Metric(['name' => 'screenPageViewsPerSession']));
487 |
488 | return $this;
489 | }
490 |
491 | public function sessionConversionRate(): self
492 | {
493 | $this->metrics->push(new Metric(['name' => 'sessionConversionRate']));
494 |
495 | return $this;
496 | }
497 |
498 | public function sessions(): self
499 | {
500 | $this->metrics->push(new Metric(['name' => 'sessions']));
501 |
502 | return $this;
503 | }
504 |
505 | public function sessionsPerUser(): self
506 | {
507 | $this->metrics->push(new Metric(['name' => 'sessionsPerUser']));
508 |
509 | return $this;
510 | }
511 |
512 | public function shippingAmount(): self
513 | {
514 | $this->metrics->push(new Metric(['name' => 'shippingAmount']));
515 |
516 | return $this;
517 | }
518 |
519 | public function taxAmount(): self
520 | {
521 | $this->metrics->push(new Metric(['name' => 'taxAmount']));
522 |
523 | return $this;
524 | }
525 |
526 | public function totalAdRevenue(): self
527 | {
528 | $this->metrics->push(new Metric(['name' => 'totalAdRevenue']));
529 |
530 | return $this;
531 | }
532 |
533 | public function totalPurchasers(): self
534 | {
535 | $this->metrics->push(new Metric(['name' => 'totalPurchasers']));
536 |
537 | return $this;
538 | }
539 |
540 | public function totalRevenue(): self
541 | {
542 | $this->metrics->push(new Metric(['name' => 'totalRevenue']));
543 |
544 | return $this;
545 | }
546 |
547 | public function totalUsers(): self
548 | {
549 | $this->metrics->push(new Metric(['name' => 'totalUsers']));
550 |
551 | return $this;
552 | }
553 |
554 | public function transactions(): self
555 | {
556 | $this->metrics->push(new Metric(['name' => 'transactions']));
557 |
558 | return $this;
559 | }
560 |
561 | public function transactionsPerPurchaser(): self
562 | {
563 | $this->metrics->push(new Metric(['name' => 'transactionsPerPurchaser']));
564 |
565 | return $this;
566 | }
567 |
568 | public function userConversionRate(): self
569 | {
570 | $this->metrics->push(new Metric(['name' => 'userConversionRate']));
571 |
572 | return $this;
573 | }
574 |
575 | public function userEngagementDuration(): self
576 | {
577 | $this->metrics->push(new Metric(['name' => 'userEngagementDuration']));
578 |
579 | return $this;
580 | }
581 |
582 | public function wauPerMau(): self
583 | {
584 | $this->metrics->push(new Metric(['name' => 'wauPerMau']));
585 |
586 | return $this;
587 | }
588 | }
589 |
--------------------------------------------------------------------------------
/src/Request/RequestData.php:
--------------------------------------------------------------------------------
1 | $dateRanges
17 | * @param Collection $metrics
18 | * @param Collection $dimensions
19 | */
20 | class RequestData extends Data
21 | {
22 | public function __construct(
23 | public string $propertyId,
24 | /** @var Collection */
25 | public Collection $dateRanges = new Collection,
26 | /** @var Collection */
27 | public Collection $metrics = new Collection,
28 | /** @var Collection */
29 | public Collection $dimensions = new Collection,
30 |
31 | public ?FilterExpression $dimensionFilter = null,
32 |
33 | public ?FilterExpression $metricFilter = null,
34 |
35 | public bool $returnPropertyQuota = true,
36 |
37 | public bool $useTotals = false,
38 |
39 | public int $limit = 10_000,
40 |
41 | public int $offset = 0,
42 | ) {}
43 |
44 | /** @return array{property: string, dateRanges: DateRange[], dimensions: Dimension[], metrics: Metric[], dimensionFilter: BaseFilterExpression|null, returnPropertyQuota: bool, metricAggregations: int[]} */
45 | public function toArray(): array
46 | {
47 | return [
48 | 'property' => 'properties/'.$this->propertyId,
49 | 'dateRanges' => $this->dateRanges->all(),
50 | 'dimensions' => $this->dimensions->unique()->all(),
51 | 'metrics' => $this->metrics->unique()->all(),
52 | 'dimensionFilter' => $this->dimensionFilter?->toRequest(),
53 | 'metricFilter' => $this->metricFilter?->toRequest(),
54 | 'returnPropertyQuota' => $this->returnPropertyQuota,
55 | 'metricAggregations' => $this->useTotals ? [MetricAggregation::TOTAL] : [],
56 | 'limit' => $this->limit,
57 | 'offset' => $this->offset,
58 | ];
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Response/DimensionHeader.php:
--------------------------------------------------------------------------------
1 | |null $dimensionHeaders
14 | * @param DataCollection $metricHeaders
15 | * @param DataCollection $rows
16 | * @param DataCollection|null $totals
17 | */
18 | public function __construct(
19 | #[DataCollectionOf(DimensionHeader::class)]
20 | public ?DataCollection $dimensionHeaders,
21 | #[DataCollectionOf(MetricHeader::class)]
22 | public DataCollection $metricHeaders,
23 | #[DataCollectionOf(Row::class)]
24 | public DataCollection $rows,
25 | #[DataCollectionOf(Total::class)]
26 | public ?DataCollection $totals,
27 | public int $rowCount,
28 | public Metadata $metadata,
29 | public ?PropertyQuota $propertyQuota,
30 | public string $kind,
31 | ) {}
32 |
33 | public static function fromReportResponse(RunReportResponse $reportResponse): static
34 | {
35 | $json = $reportResponse->serializeToJsonString();
36 |
37 | $report = json_decode($json, true);
38 |
39 | return self::from($report + ['rows' => new DataCollection(Row::class, []), 'rowCount' => 0]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Response/Row.php:
--------------------------------------------------------------------------------
1 | |null $dimensionValues
13 | * @param DataCollection $metricValues
14 | */
15 | public function __construct(
16 | #[DataCollectionOf(DimensionValue::class)]
17 | public ?DataCollection $dimensionValues,
18 | #[DataCollectionOf(MetricValue::class)]
19 | public DataCollection $metricValues,
20 | ) {}
21 | }
22 |
--------------------------------------------------------------------------------
/src/Response/Total.php:
--------------------------------------------------------------------------------
1 | $dimensionValues
13 | * @param DataCollection $metricValues
14 | */
15 | public function __construct(
16 | #[DataCollectionOf(DimensionValue::class)]
17 | public DataCollection $dimensionValues,
18 | #[DataCollectionOf(MetricValue::class)]
19 | public DataCollection $metricValues,
20 | ) {}
21 | }
22 |
--------------------------------------------------------------------------------
/tests/AnalyticsTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Analytics::class, new Analytics);
36 | }
37 |
38 | public function test_constructor_with_propertyid(): void
39 | {
40 | config()->set('analytics.property_id', 'def');
41 | $newPropertyId = 'abc';
42 | $analytics = new Analytics($newPropertyId);
43 | $this->assertInstanceOf(Analytics::class, $analytics);
44 |
45 | /** @var RequestData $requestData */
46 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
47 |
48 | $this->assertEquals($newPropertyId, $requestData->propertyId);
49 | }
50 |
51 | public function test_property_string_is_empty_exception(): void
52 | {
53 | config()->offsetUnset('analytics.property_id');
54 |
55 | $this->expectException(InvalidPropertyIdException::class);
56 | $this->expectExceptionMessage(InvalidPropertyIdException::MESSAGE_INVALID_PROPERTY_ID);
57 |
58 | Analytics::query();
59 | }
60 |
61 | public function test_property_string_is_not_string_exception(): void
62 | {
63 | config()->set('analytics.property_id', 1234);
64 |
65 | $this->expectException(InvalidPropertyIdException::class);
66 | $this->expectExceptionMessage(InvalidPropertyIdException::MESSAGE_INVALID_PROPERTY_ID);
67 |
68 | Analytics::query();
69 | }
70 |
71 | public function test_set_metrics(): void
72 | {
73 | $analytics = Analytics::query()
74 | ->setMetrics(fn (Metrics $metrics) => $metrics
75 | ->sessions()
76 | ->bounceRate()
77 | );
78 |
79 | $this->assertInstanceOf(Analytics::class, $analytics);
80 |
81 | /** @var RequestData $requestData */
82 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
83 | $requestMetrics = $requestData->metrics->map(fn (Metric $metric) => $metric->getName())->toArray();
84 |
85 | $this->assertEquals(['sessions', 'bounceRate'], $requestMetrics);
86 | }
87 |
88 | public function test_custom_metrics(): void
89 | {
90 | app()->bind(Metrics::class, CustomMetrics::class);
91 |
92 | $analytics = Analytics::query()
93 | ->setMetrics(fn (CustomMetrics $metrics) => $metrics
94 | ->customMetric()
95 | ->sessions()
96 | );
97 |
98 | $this->assertInstanceOf(Analytics::class, $analytics);
99 |
100 | /** @var RequestData $requestData */
101 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
102 | $requestMetrics = $requestData->metrics->map(fn (Metric $metric) => $metric->getName())->toArray();
103 |
104 | $this->assertEquals(['customMetric', 'sessions'], $requestMetrics);
105 | }
106 |
107 | public function test_set_dimensions(): void
108 | {
109 | $analytics = Analytics::query()
110 | ->setDimensions(fn (Dimensions $dimensions) => $dimensions
111 | ->browser()
112 | );
113 |
114 | $this->assertInstanceOf(Analytics::class, $analytics);
115 |
116 | /** @var RequestData $requestData */
117 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
118 | $requestDimensions = $requestData->dimensions->map(fn (Dimension $dimension) => $dimension->getName())->toArray();
119 |
120 | $this->assertEquals(['browser'], $requestDimensions);
121 | }
122 |
123 | public function test_custom_dimensions(): void
124 | {
125 | app()->bind(Dimensions::class, CustomDimensions::class);
126 |
127 | $analytics = Analytics::query()
128 | ->setDimensions(fn (CustomDimensions $dimensions) => $dimensions
129 | ->customDimension()
130 | ->browser()
131 | );
132 |
133 | $this->assertInstanceOf(Analytics::class, $analytics);
134 |
135 | /** @var RequestData $requestData */
136 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
137 | $requestDimensions = $requestData->dimensions->map(fn (Dimension $dimension) => $dimension->getName())->toArray();
138 |
139 | $this->assertEquals(['customDimension', 'browser'], $requestDimensions);
140 | }
141 |
142 | public function test_limit(): void
143 | {
144 | $analytics = Analytics::query()
145 | ->limit(5);
146 |
147 | $this->assertInstanceOf(Analytics::class, $analytics);
148 |
149 | /** @var RequestData $requestData */
150 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
151 |
152 | $this->assertEquals(5, $requestData->limit);
153 |
154 | $analytics->limit();
155 |
156 | $this->assertEquals(10_000, $requestData->limit);
157 | }
158 |
159 | public function test_offset(): void
160 | {
161 | $analytics = Analytics::query()
162 | ->offset(5);
163 |
164 | $this->assertInstanceOf(Analytics::class, $analytics);
165 |
166 | /** @var RequestData $requestData */
167 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
168 |
169 | $this->assertEquals(5, $requestData->offset);
170 |
171 | $analytics->offset();
172 |
173 | $this->assertEquals(0, $requestData->offset);
174 | }
175 |
176 | public function test_for_period(): void
177 | {
178 | CarbonImmutable::setTestNow(CarbonImmutable::parse('2022-10-10'));
179 |
180 | $analytics = Analytics::query()
181 | ->forPeriod(Period::lastWeek()
182 | );
183 |
184 | $this->assertInstanceOf(Analytics::class, $analytics);
185 |
186 | /** @var RequestData $requestData */
187 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
188 | $requestDateRange = $requestData->dateRanges->first();
189 |
190 | if ($requestDateRange === null) {
191 | $this->fail('Request date range is null');
192 | }
193 |
194 | $this->assertEquals('2022-10-03', $requestDateRange->getStartDate());
195 | $this->assertEquals('2022-10-09', $requestDateRange->getEndDate());
196 | }
197 |
198 | public function test_with_totals(): void
199 | {
200 | $analytics = Analytics::query()
201 | ->withTotals();
202 |
203 | $this->assertInstanceOf(Analytics::class, $analytics);
204 |
205 | /** @var RequestData $requestData */
206 | $requestData = (new ReflectionProperty(Analytics::class, 'requestData'))->getValue($analytics);
207 |
208 | $this->assertTrue($requestData->useTotals);
209 |
210 | $analytics->withTotals(false);
211 |
212 | $this->assertFalse($requestData->useTotals);
213 | }
214 |
215 | /**
216 | * @throws ApiException
217 | */
218 | public function test_run(): void
219 | {
220 | CarbonImmutable::setTestNow(CarbonImmutable::parse('2022-10-10'));
221 |
222 | $responseMock = $this->mock(RunReportResponse::class, function (MockInterface $mock) {
223 | $mock->shouldReceive('serializeToJsonString')
224 | ->once()
225 | ->andReturn(json_encode([
226 | 'dimensionHeaders' => [
227 | [
228 | 'name' => 'browser',
229 | ],
230 | ],
231 | 'metricHeaders' => [
232 | [
233 | 'name' => 'sessions',
234 | 'type' => 'TYPE_INTEGER',
235 | ],
236 | [
237 | 'name' => 'bounceRate',
238 | 'type' => 'TYPE_DOUBLE',
239 | ],
240 | ],
241 | 'rows' => [
242 | [
243 | 'dimensionValues' => [
244 |
245 | [
246 | 'value' => 'Browser1',
247 | ],
248 | ],
249 | 'metricValues' => [
250 | [
251 | 'value' => 123,
252 | ],
253 | [
254 | 'value' => 0.123,
255 | ],
256 | ],
257 | ],
258 | [
259 | 'dimensionValues' => [
260 |
261 | [
262 | 'value' => 'Browser2',
263 | ],
264 | ],
265 | 'metricValues' => [
266 | [
267 | 'value' => 456,
268 | ],
269 | [
270 | 'value' => 0.456,
271 | ],
272 | ],
273 | ],
274 | ],
275 | 'totals' => [
276 | [
277 | 'dimensionValues' => [
278 | [
279 | 'value' => 'RESERVED_TOTAL',
280 | ],
281 | ],
282 | 'metricValues' => [
283 | [
284 | 'value' => 579,
285 | ],
286 | [
287 | 'value' => 0.579,
288 | ],
289 | ],
290 | ],
291 | ],
292 | 'rowCount' => 2,
293 | 'metadata' => [
294 | 'currencyCode' => 'USD',
295 | 'timeZone' => 'UTC',
296 | ],
297 | 'propertyQuota' => [
298 | 'tokensPerDay' => [
299 | 'consumed' => 9,
300 | 'remaining' => 24821,
301 | ],
302 | 'tokensPerHour' => [
303 | 'consumed' => 9,
304 | 'remaining' => 4981,
305 | ],
306 | 'concurrentRequests' => [
307 | 'remaining' => 10,
308 | ],
309 | 'serverErrorsPerProjectPerHour' => [
310 | 'remaining' => 10,
311 | ],
312 | 'potentiallyThresholdedRequestsPerHour' => [
313 | 'remaining' => 120,
314 | ],
315 | 'tokensPerProjectPerHour' => [
316 | 'consumed' => 9,
317 | 'remaining' => 1231,
318 | ],
319 | ],
320 | 'kind' => 'analyticsData#runReport',
321 | ]));
322 | });
323 |
324 | $this->mock(BetaAnalyticsDataClient::class, function (MockInterface $mock) use ($responseMock) {
325 | $mock->shouldReceive('runReport')
326 | ->with(Mockery::on(function (array $reportRequest) {
327 | /** @var array{property: string, dateRanges: DateRange[], dimensions: Dimension[], metrics: Metric[], returnPropertyQuota: bool, metricAggregations: int[]} $reportRequest */
328 | $this->assertEquals('properties/test123', $reportRequest['property']);
329 |
330 | $this->assertCount(1, $reportRequest['dateRanges']);
331 | $this->assertEquals('2022-10-03', $reportRequest['dateRanges'][0]->getStartDate());
332 | $this->assertEquals('2022-10-09', $reportRequest['dateRanges'][0]->getEndDate());
333 |
334 | $this->assertCount(2, $reportRequest['metrics']);
335 | $this->assertEquals('sessions', $reportRequest['metrics'][0]->getName());
336 | $this->assertEquals('bounceRate', $reportRequest['metrics'][1]->getName());
337 |
338 | $this->assertCount(1, $reportRequest['dimensions']);
339 | $this->assertEquals('browser', $reportRequest['dimensions'][0]->getName());
340 |
341 | $this->assertTrue($reportRequest['returnPropertyQuota']);
342 |
343 | $this->assertCount(1, $reportRequest['metricAggregations']);
344 | $this->assertEquals(MetricAggregation::TOTAL, $reportRequest['metricAggregations'][0]);
345 |
346 | return true;
347 | }))
348 | ->once()
349 | ->andReturn($responseMock);
350 | });
351 |
352 | $response = Analytics::query()
353 | ->setMetrics(fn (Metrics $metrics) => $metrics
354 | ->sessions()
355 | ->bounceRate()
356 | )
357 | ->setDimensions(fn (Dimensions $dimensions) => $dimensions
358 | ->browser()
359 | )
360 | ->forPeriod(Period::lastWeek())
361 | ->withTotals()
362 | ->run();
363 |
364 | $this->assertInstanceOf(ResponseData::class, $response);
365 |
366 | $response->dimensionHeaders?->each(function (DimensionHeader $dimensionHeader) {
367 | $this->assertInstanceOf(DimensionHeader::class, $dimensionHeader);
368 | $this->assertEquals('browser', $dimensionHeader->name);
369 | });
370 |
371 | $this->assertInstanceOf(MetricHeader::class, ($metricHeader = $response->metricHeaders->items()[0]));
372 | $this->assertEquals('sessions', $metricHeader->name);
373 | $this->assertEquals('TYPE_INTEGER', $metricHeader->type);
374 |
375 | $this->assertInstanceOf(MetricHeader::class, ($metricHeader = $response->metricHeaders->items()[1]));
376 | $this->assertEquals('bounceRate', $metricHeader->name);
377 | $this->assertEquals('TYPE_DOUBLE', $metricHeader->type);
378 |
379 | $this->assertEquals(2, $response->rowCount);
380 | $this->assertCount(2, $response->rows);
381 |
382 | $this->assertInstanceOf(Row::class, ($row = $response->rows->items()[0]));
383 | $this->assertEquals(1, $row->dimensionValues?->count());
384 | $this->assertEquals('Browser1', $row->dimensionValues?->items()[0]->value);
385 | $this->assertCount(2, $row->metricValues);
386 | $this->assertEquals(123, $row->metricValues->items()[0]->value);
387 | $this->assertEquals(0.123, $row->metricValues->items()[1]->value);
388 |
389 | $this->assertInstanceOf(Row::class, ($row = $response->rows->items()[1]));
390 | $this->assertEquals(1, $row->dimensionValues?->count());
391 | $this->assertEquals('Browser2', $row->dimensionValues?->items()[0]->value);
392 | $this->assertCount(2, $row->metricValues);
393 | $this->assertEquals(456, $row->metricValues->items()[0]->value);
394 | $this->assertEquals(0.456, $row->metricValues->items()[1]->value);
395 |
396 | $this->assertEquals(1, $response->totals?->count());
397 | $this->assertInstanceOf(Total::class, ($totalRow = $response->totals?->items()[0]));
398 | $this->assertEquals(1, $totalRow->dimensionValues->count());
399 | $this->assertEquals('RESERVED_TOTAL', $totalRow->dimensionValues->items()[0]->value);
400 | $this->assertCount(2, $totalRow->metricValues);
401 | $this->assertEquals(579, $totalRow->metricValues->items()[0]->value);
402 | $this->assertEquals(0.579, $totalRow->metricValues->items()[1]->value);
403 |
404 | $this->assertInstanceOf(PropertyQuota::class, $response->propertyQuota);
405 | $this->assertEquals(9, $response->propertyQuota->tokensPerDay->consumed);
406 | $this->assertEquals(24821, $response->propertyQuota->tokensPerDay->remaining);
407 | $this->assertEquals(9, $response->propertyQuota->tokensPerHour->consumed);
408 | $this->assertEquals(4981, $response->propertyQuota->tokensPerHour->remaining);
409 | $this->assertEquals(10, $response->propertyQuota->concurrentRequests->remaining);
410 | $this->assertEquals(10, $response->propertyQuota->serverErrorsPerProjectPerHour->remaining);
411 | $this->assertEquals(120, $response->propertyQuota->potentiallyThresholdedRequestsPerHour->remaining);
412 | $this->assertEquals(9, $response->propertyQuota->tokensPerProjectPerHour->consumed);
413 | $this->assertEquals(1231, $response->propertyQuota->tokensPerProjectPerHour->remaining);
414 | }
415 | }
416 |
--------------------------------------------------------------------------------
/tests/CredentialsTest.php:
--------------------------------------------------------------------------------
1 | disk = Storage::fake('testing-storage');
21 | }
22 |
23 | /**
24 | * @param array $credentials
25 | */
26 | private function setupCredentialsFile(array $credentials = [], string $fileName = 'test-credentials'): string
27 | {
28 | $encodedCredentials = json_encode($credentials + $this->credentials());
29 |
30 | if (! $encodedCredentials) {
31 | $this->fail('Failed to encode credentials');
32 | }
33 |
34 | $this->disk->put("$fileName.json", $encodedCredentials);
35 |
36 | return $this->disk->path("$fileName.json");
37 | }
38 |
39 | /**
40 | * @return array|null
41 | *
42 | * @throws InvalidCredentialsJsonStringException
43 | * @throws InvalidCredentialsFileException
44 | * @throws InvalidCredentialsArrayException
45 | */
46 | private function getApplicationCredentials(): ?array
47 | {
48 | return resolve(Credentials::class)->parse();
49 | }
50 |
51 | /**
52 | * @throws InvalidCredentialsFileException
53 | * @throws InvalidCredentialsJsonStringException
54 | * @throws InvalidCredentialsArrayException
55 | */
56 | public function test_client_init_with_default_credentials_env(): void
57 | {
58 | $credentialsFile = $this->setupCredentialsFile();
59 |
60 | putenv('GOOGLE_APPLICATION_CREDENTIALS='.$credentialsFile);
61 |
62 | $this->assertNull($this->getApplicationCredentials());
63 |
64 | putenv('GOOGLE_APPLICATION_CREDENTIALS=');
65 | }
66 |
67 | /**
68 | * @throws InvalidCredentialsJsonStringException
69 | * @throws InvalidCredentialsFileException
70 | * @throws InvalidCredentialsArrayException
71 | */
72 | public function test_client_init_with_credentials_file(): void
73 | {
74 | $credentials = ['project_id' => 'testing-credentials-file'] + $this->credentials();
75 | $credentialsFile = $this->setupCredentialsFile(['project_id' => 'testing-credentials-file']);
76 |
77 | config()->set('analytics.credentials.file', $credentialsFile);
78 |
79 | $this->assertSame($credentials, $this->getApplicationCredentials());
80 |
81 | config()->offsetUnset('analytics.credentials.file');
82 | }
83 |
84 | /**
85 | * @throws InvalidCredentialsJsonStringException
86 | * @throws InvalidCredentialsFileException
87 | * @throws InvalidCredentialsArrayException
88 | */
89 | public function test_client_init_with_credentials_file_while_default_google_application_credentials_exist(): void
90 | {
91 | $defaultCredentialsFile = $this->setupCredentialsFile(['project_id' => 'do_not_use'], 'default-credentials');
92 | putenv('GOOGLE_APPLICATION_CREDENTIALS='.$defaultCredentialsFile);
93 |
94 | config()->set('analytics.credentials.use_env', false);
95 |
96 | $credentials = ['project_id' => 'testing-credentials-file-with-default'] + $this->credentials();
97 | $credentialsFile = $this->setupCredentialsFile(['project_id' => 'testing-credentials-file-with-default']);
98 |
99 | config()->set('analytics.credentials.file', $credentialsFile);
100 |
101 | $this->assertSame($credentials, $this->getApplicationCredentials());
102 |
103 | config()->offsetUnset('analytics.credentials.file');
104 | putenv('GOOGLE_APPLICATION_CREDENTIALS=');
105 | }
106 |
107 | /**
108 | * @throws InvalidCredentialsJsonStringException
109 | * @throws InvalidCredentialsFileException
110 | * @throws InvalidCredentialsArrayException
111 | */
112 | public function test_client_init_with_credentials_json_string(): void
113 | {
114 | $credentials = ['project_id' => 'testing-credentials-json'] + $this->credentials();
115 |
116 | config()->set('analytics.credentials.json', json_encode($credentials));
117 |
118 | $this->assertSame($credentials, $this->getApplicationCredentials());
119 |
120 | config()->offsetUnset('analytics.credentials.json');
121 | }
122 |
123 | /**
124 | * @throws InvalidCredentialsFileException
125 | * @throws InvalidCredentialsJsonStringException
126 | * @throws InvalidCredentialsArrayException
127 | */
128 | public function test_client_init_with_credentials_array(): void
129 | {
130 | $credentials = ['project_id' => 'testing-credentials-array'] + $this->credentials();
131 |
132 | config()->set('analytics.credentials.array', $credentials);
133 |
134 | $this->assertSame($credentials, $this->getApplicationCredentials());
135 |
136 | config()->offsetUnset('analytics.credentials.array');
137 | }
138 |
139 | /**
140 | * @throws InvalidCredentialsFileException
141 | * @throws InvalidCredentialsJsonStringException
142 | * @throws InvalidCredentialsArrayException
143 | */
144 | public function test_client_init_with_separate_credential_values(): void
145 | {
146 | config()->offsetUnset('analytics.credentials.array');
147 |
148 | $credentials = ['project_id' => 'testing-credentials-separate-values'] + $this->credentials();
149 |
150 | foreach ($credentials as $key => $value) {
151 | config()->set('analytics.credentials.array.'.$key, $value);
152 | }
153 |
154 | $this->assertSame($credentials, $this->getApplicationCredentials());
155 |
156 | config()->offsetUnset('analytics.credentials.array');
157 | }
158 |
159 | /**
160 | * @throws InvalidCredentialsJsonStringException
161 | * @throws InvalidCredentialsArrayException
162 | */
163 | public function test_invalid_credentials_file_path_exception(): void
164 | {
165 | config()->offsetUnset('analytics.credentials.array');
166 | config()->set('analytics.credentials.file', '');
167 |
168 | $this->expectException(InvalidCredentialsFileException::class);
169 | $this->expectExceptionMessage(InvalidCredentialsFileException::MESSAGE_INVALID_PATH);
170 |
171 | $this->getApplicationCredentials();
172 |
173 | config()->offsetUnset('analytics.credentials.file');
174 | }
175 |
176 | /**
177 | * @throws InvalidCredentialsJsonStringException
178 | * @throws InvalidCredentialsArrayException
179 | */
180 | public function test_credentials_file_does_not_exist_exception(): void
181 | {
182 | config()->offsetUnset('analytics.credentials.array');
183 | config()->set('analytics.credentials.file', 'invalid-file.json');
184 |
185 | $this->expectException(InvalidCredentialsFileException::class);
186 | $this->expectExceptionMessage(InvalidCredentialsFileException::MESSAGE_NOT_FOUND);
187 |
188 | $this->getApplicationCredentials();
189 |
190 | config()->offsetUnset('analytics.credentials.file');
191 | }
192 |
193 | /**
194 | * @throws InvalidCredentialsJsonStringException
195 | * @throws InvalidCredentialsArrayException
196 | */
197 | public function test_credentials_file_is_not_a_valid_json_exception(): void
198 | {
199 | config()->offsetUnset('analytics.credentials.array');
200 |
201 | $this->disk->put('invalid-json.json', 'invalid-json');
202 | $credentialsFile = $this->disk->path('invalid-json.json');
203 |
204 | config()->set('analytics.credentials.file', $credentialsFile);
205 |
206 | $this->expectException(InvalidCredentialsFileException::class);
207 | $this->expectExceptionMessage(InvalidCredentialsFileException::MESSAGE_INVALID_JSON);
208 |
209 | $this->getApplicationCredentials();
210 |
211 | config()->offsetUnset('analytics.credentials.file');
212 | }
213 |
214 | /**
215 | * @throws InvalidCredentialsFileException
216 | * @throws InvalidCredentialsArrayException
217 | */
218 | public function test_invalid_credentials_json_string_exception(): void
219 | {
220 | config()->offsetUnset('analytics.credentials.array');
221 | config()->set('analytics.credentials.json', '');
222 |
223 | $this->expectException(InvalidCredentialsJsonStringException::class);
224 | $this->expectExceptionMessage(InvalidCredentialsJsonStringException::MESSAGE_INVALID_STRING);
225 |
226 | $this->getApplicationCredentials();
227 |
228 | config()->offsetUnset('analytics.credentials.json');
229 | }
230 |
231 | /**
232 | * @throws InvalidCredentialsFileException
233 | * @throws InvalidCredentialsArrayException
234 | */
235 | public function test_credentials_json_string_is_not_a_valid_json_exception(): void
236 | {
237 | config()->offsetUnset('analytics.credentials.array');
238 | config()->set('analytics.credentials.json', 'invalid-json');
239 |
240 | $this->expectException(InvalidCredentialsJsonStringException::class);
241 | $this->expectExceptionMessage(InvalidCredentialsJsonStringException::MESSAGE_INVALID_JSON);
242 |
243 | $this->getApplicationCredentials();
244 |
245 | config()->offsetUnset('analytics.credentials.json');
246 | }
247 |
248 | /**
249 | * @throws InvalidCredentialsFileException
250 | * @throws InvalidCredentialsJsonStringException
251 | */
252 | public function test_invalid_credentials_array_exception(): void
253 | {
254 | config()->set('analytics.credentials.array', '');
255 |
256 | $this->expectException(InvalidCredentialsArrayException::class);
257 | $this->expectExceptionMessage(InvalidCredentialsArrayException::MESSAGE_INVALID_ARRAY);
258 |
259 | $this->getApplicationCredentials();
260 |
261 | config()->offsetUnset('analytics.credentials.array');
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/tests/Helpers/CustomDimensions.php:
--------------------------------------------------------------------------------
1 | dimensions->push(new Dimension(['name' => 'customDimension']));
13 |
14 | return $this;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Helpers/CustomMetrics.php:
--------------------------------------------------------------------------------
1 | metrics->push(new Metric(['name' => 'customMetric']));
13 |
14 | return $this;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/MetricsTest.php:
--------------------------------------------------------------------------------
1 | [
16 | 'method' => fn (Metrics $metrics) => $metrics->active1DayUsers(),
17 | 'metric' => 'active1DayUsers',
18 | ];
19 |
20 | yield 'active28DayUsers' => [
21 | 'method' => fn (Metrics $metrics) => $metrics->active28DayUsers(),
22 | 'metric' => 'active28DayUsers',
23 | ];
24 |
25 | yield 'active7DayUsers' => [
26 | 'method' => fn (Metrics $metrics) => $metrics->active7DayUsers(),
27 | 'metric' => 'active7DayUsers',
28 | ];
29 |
30 | yield 'activeUsers' => [
31 | 'method' => fn (Metrics $metrics) => $metrics->activeUsers(),
32 | 'metric' => 'activeUsers',
33 | ];
34 |
35 | yield 'adUnitExposure' => [
36 | 'method' => fn (Metrics $metrics) => $metrics->adUnitExposure(),
37 | 'metric' => 'adUnitExposure',
38 | ];
39 |
40 | yield 'addToCarts' => [
41 | 'method' => fn (Metrics $metrics) => $metrics->addToCarts(),
42 | 'metric' => 'addToCarts',
43 | ];
44 |
45 | yield 'advertiserAdClicks' => [
46 | 'method' => fn (Metrics $metrics) => $metrics->advertiserAdClicks(),
47 | 'metric' => 'advertiserAdClicks',
48 | ];
49 |
50 | yield 'advertiserAdCost' => [
51 | 'method' => fn (Metrics $metrics) => $metrics->advertiserAdCost(),
52 | 'metric' => 'advertiserAdCost',
53 | ];
54 |
55 | yield 'advertiserAdCostPerClick' => [
56 | 'method' => fn (Metrics $metrics) => $metrics->advertiserAdCostPerClick(),
57 | 'metric' => 'advertiserAdCostPerClick',
58 | ];
59 |
60 | yield 'advertiserAdCostPerConversion' => [
61 | 'method' => fn (Metrics $metrics) => $metrics->advertiserAdCostPerConversion(),
62 | 'metric' => 'advertiserAdCostPerConversion',
63 | ];
64 |
65 | yield 'advertiserAdImpressions' => [
66 | 'method' => fn (Metrics $metrics) => $metrics->advertiserAdImpressions(),
67 | 'metric' => 'advertiserAdImpressions',
68 | ];
69 |
70 | yield 'averagePurchaseRevenue' => [
71 | 'method' => fn (Metrics $metrics) => $metrics->averagePurchaseRevenue(),
72 | 'metric' => 'averagePurchaseRevenue',
73 | ];
74 |
75 | yield 'averagePurchaseRevenuePerPayingUser' => [
76 | 'method' => fn (Metrics $metrics) => $metrics->averagePurchaseRevenuePerPayingUser(),
77 | 'metric' => 'averagePurchaseRevenuePerPayingUser',
78 | ];
79 |
80 | yield 'averagePurchaseRevenuePerUser' => [
81 | 'method' => fn (Metrics $metrics) => $metrics->averagePurchaseRevenuePerUser(),
82 | 'metric' => 'averagePurchaseRevenuePerUser',
83 | ];
84 |
85 | yield 'averageRevenuePerUser' => [
86 | 'method' => fn (Metrics $metrics) => $metrics->averageRevenuePerUser(),
87 | 'metric' => 'averageRevenuePerUser',
88 | ];
89 |
90 | yield 'averageSessionDuration' => [
91 | 'method' => fn (Metrics $metrics) => $metrics->averageSessionDuration(),
92 | 'metric' => 'averageSessionDuration',
93 | ];
94 |
95 | yield 'bounceRate' => [
96 | 'method' => fn (Metrics $metrics) => $metrics->bounceRate(),
97 | 'metric' => 'bounceRate',
98 | ];
99 |
100 | yield 'cartToViewRate' => [
101 | 'method' => fn (Metrics $metrics) => $metrics->cartToViewRate(),
102 | 'metric' => 'cartToViewRate',
103 | ];
104 |
105 | yield 'checkouts' => [
106 | 'method' => fn (Metrics $metrics) => $metrics->checkouts(),
107 | 'metric' => 'checkouts',
108 | ];
109 |
110 | yield 'cohortActiveUsers' => [
111 | 'method' => fn (Metrics $metrics) => $metrics->cohortActiveUsers(),
112 | 'metric' => 'cohortActiveUsers',
113 | ];
114 |
115 | yield 'cohortTotalUsers' => [
116 | 'method' => fn (Metrics $metrics) => $metrics->cohortTotalUsers(),
117 | 'metric' => 'cohortTotalUsers',
118 | ];
119 |
120 | yield 'conversions' => [
121 | 'method' => fn (Metrics $metrics) => $metrics->conversions(),
122 | 'metric' => 'conversions',
123 | ];
124 |
125 | yield 'crashAffectedUsers' => [
126 | 'method' => fn (Metrics $metrics) => $metrics->crashAffectedUsers(),
127 | 'metric' => 'crashAffectedUsers',
128 | ];
129 |
130 | yield 'crashFreeUsersRate' => [
131 | 'method' => fn (Metrics $metrics) => $metrics->crashFreeUsersRate(),
132 | 'metric' => 'crashFreeUsersRate',
133 | ];
134 |
135 | yield 'dauPerMau' => [
136 | 'method' => fn (Metrics $metrics) => $metrics->dauPerMau(),
137 | 'metric' => 'dauPerMau',
138 | ];
139 |
140 | yield 'dauPerWau' => [
141 | 'method' => fn (Metrics $metrics) => $metrics->dauPerWau(),
142 | 'metric' => 'dauPerWau',
143 | ];
144 |
145 | yield 'ecommercePurchases' => [
146 | 'method' => fn (Metrics $metrics) => $metrics->ecommercePurchases(),
147 | 'metric' => 'ecommercePurchases',
148 | ];
149 |
150 | yield 'engagedSessions' => [
151 | 'method' => fn (Metrics $metrics) => $metrics->engagedSessions(),
152 | 'metric' => 'engagedSessions',
153 | ];
154 |
155 | yield 'engagementRate' => [
156 | 'method' => fn (Metrics $metrics) => $metrics->engagementRate(),
157 | 'metric' => 'engagementRate',
158 | ];
159 |
160 | yield 'eventCount' => [
161 | 'method' => fn (Metrics $metrics) => $metrics->eventCount(),
162 | 'metric' => 'eventCount',
163 | ];
164 |
165 | yield 'eventCountPerUser' => [
166 | 'method' => fn (Metrics $metrics) => $metrics->eventCountPerUser(),
167 | 'metric' => 'eventCountPerUser',
168 | ];
169 |
170 | yield 'eventValue' => [
171 | 'method' => fn (Metrics $metrics) => $metrics->eventValue(),
172 | 'metric' => 'eventValue',
173 | ];
174 |
175 | yield 'eventsPerSession' => [
176 | 'method' => fn (Metrics $metrics) => $metrics->eventsPerSession(),
177 | 'metric' => 'eventsPerSession',
178 | ];
179 |
180 | yield 'firstTimePurchaserConversionRate' => [
181 | 'method' => fn (Metrics $metrics) => $metrics->firstTimePurchaserConversionRate(),
182 | 'metric' => 'firstTimePurchaserConversionRate',
183 | ];
184 |
185 | yield 'firstTimePurchasers' => [
186 | 'method' => fn (Metrics $metrics) => $metrics->firstTimePurchasers(),
187 | 'metric' => 'firstTimePurchasers',
188 | ];
189 |
190 | yield 'firstTimePurchasersPerNewUser' => [
191 | 'method' => fn (Metrics $metrics) => $metrics->firstTimePurchasersPerNewUser(),
192 | 'metric' => 'firstTimePurchasersPerNewUser',
193 | ];
194 |
195 | yield 'itemListClickEvents' => [
196 | 'method' => fn (Metrics $metrics) => $metrics->itemListClickEvents(),
197 | 'metric' => 'itemListClickEvents',
198 | ];
199 |
200 | yield 'itemListClickThroughRate' => [
201 | 'method' => fn (Metrics $metrics) => $metrics->itemListClickThroughRate(),
202 | 'metric' => 'itemListClickThroughRate',
203 | ];
204 |
205 | yield 'itemListViewEvents' => [
206 | 'method' => fn (Metrics $metrics) => $metrics->itemListViewEvents(),
207 | 'metric' => 'itemListViewEvents',
208 | ];
209 |
210 | yield 'itemPromotionClickThroughRate' => [
211 | 'method' => fn (Metrics $metrics) => $metrics->itemPromotionClickThroughRate(),
212 | 'metric' => 'itemPromotionClickThroughRate',
213 | ];
214 |
215 | yield 'itemRevenue' => [
216 | 'method' => fn (Metrics $metrics) => $metrics->itemRevenue(),
217 | 'metric' => 'itemRevenue',
218 | ];
219 |
220 | yield 'itemViewEvents' => [
221 | 'method' => fn (Metrics $metrics) => $metrics->itemViewEvents(),
222 | 'metric' => 'itemViewEvents',
223 | ];
224 |
225 | yield 'itemsAddedToCart' => [
226 | 'method' => fn (Metrics $metrics) => $metrics->itemsAddedToCart(),
227 | 'metric' => 'itemsAddedToCart',
228 | ];
229 |
230 | yield 'itemsCheckedOut' => [
231 | 'method' => fn (Metrics $metrics) => $metrics->itemsCheckedOut(),
232 | 'metric' => 'itemsCheckedOut',
233 | ];
234 |
235 | yield 'itemsClickedInList' => [
236 | 'method' => fn (Metrics $metrics) => $metrics->itemsClickedInList(),
237 | 'metric' => 'itemsClickedInList',
238 | ];
239 |
240 | yield 'itemsClickedInPromotion' => [
241 | 'method' => fn (Metrics $metrics) => $metrics->itemsClickedInPromotion(),
242 | 'metric' => 'itemsClickedInPromotion',
243 | ];
244 |
245 | yield 'itemsPurchased' => [
246 | 'method' => fn (Metrics $metrics) => $metrics->itemsPurchased(),
247 | 'metric' => 'itemsPurchased',
248 | ];
249 |
250 | yield 'itemsViewed' => [
251 | 'method' => fn (Metrics $metrics) => $metrics->itemsViewed(),
252 | 'metric' => 'itemsViewed',
253 | ];
254 |
255 | yield 'itemsViewedInList' => [
256 | 'method' => fn (Metrics $metrics) => $metrics->itemsViewedInList(),
257 | 'metric' => 'itemsViewedInList',
258 | ];
259 |
260 | yield 'itemsViewedInPromotion' => [
261 | 'method' => fn (Metrics $metrics) => $metrics->itemsViewedInPromotion(),
262 | 'metric' => 'itemsViewedInPromotion',
263 | ];
264 |
265 | yield 'newUsers' => [
266 | 'method' => fn (Metrics $metrics) => $metrics->newUsers(),
267 | 'metric' => 'newUsers',
268 | ];
269 |
270 | yield 'organicGoogleSearchAveragePosition' => [
271 | 'method' => fn (Metrics $metrics) => $metrics->organicGoogleSearchAveragePosition(),
272 | 'metric' => 'organicGoogleSearchAveragePosition',
273 | ];
274 |
275 | yield 'organicGoogleSearchClickThroughRate' => [
276 | 'method' => fn (Metrics $metrics) => $metrics->organicGoogleSearchClickThroughRate(),
277 | 'metric' => 'organicGoogleSearchClickThroughRate',
278 | ];
279 |
280 | yield 'organicGoogleSearchClicks' => [
281 | 'method' => fn (Metrics $metrics) => $metrics->organicGoogleSearchClicks(),
282 | 'metric' => 'organicGoogleSearchClicks',
283 | ];
284 |
285 | yield 'organicGoogleSearchImpressions' => [
286 | 'method' => fn (Metrics $metrics) => $metrics->organicGoogleSearchImpressions(),
287 | 'metric' => 'organicGoogleSearchImpressions',
288 | ];
289 |
290 | yield 'promotionClicks' => [
291 | 'method' => fn (Metrics $metrics) => $metrics->promotionClicks(),
292 | 'metric' => 'promotionClicks',
293 | ];
294 |
295 | yield 'promotionViews' => [
296 | 'method' => fn (Metrics $metrics) => $metrics->promotionViews(),
297 | 'metric' => 'promotionViews',
298 | ];
299 |
300 | yield 'publisherAdClicks' => [
301 | 'method' => fn (Metrics $metrics) => $metrics->publisherAdClicks(),
302 | 'metric' => 'publisherAdClicks',
303 | ];
304 |
305 | yield 'publisherAdImpressions' => [
306 | 'method' => fn (Metrics $metrics) => $metrics->publisherAdImpressions(),
307 | 'metric' => 'publisherAdImpressions',
308 | ];
309 |
310 | yield 'purchaseRevenue' => [
311 | 'method' => fn (Metrics $metrics) => $metrics->purchaseRevenue(),
312 | 'metric' => 'purchaseRevenue',
313 | ];
314 |
315 | yield 'purchaseToViewRate' => [
316 | 'method' => fn (Metrics $metrics) => $metrics->purchaseToViewRate(),
317 | 'metric' => 'purchaseToViewRate',
318 | ];
319 |
320 | yield 'purchaserConversionRate' => [
321 | 'method' => fn (Metrics $metrics) => $metrics->purchaserConversionRate(),
322 | 'metric' => 'purchaserConversionRate',
323 | ];
324 |
325 | yield 'returnOnAdSpend' => [
326 | 'method' => fn (Metrics $metrics) => $metrics->returnOnAdSpend(),
327 | 'metric' => 'returnOnAdSpend',
328 | ];
329 |
330 | yield 'screenPageViews' => [
331 | 'method' => fn (Metrics $metrics) => $metrics->screenPageViews(),
332 | 'metric' => 'screenPageViews',
333 | ];
334 |
335 | yield 'screenPageViewsPerSession' => [
336 | 'method' => fn (Metrics $metrics) => $metrics->screenPageViewsPerSession(),
337 | 'metric' => 'screenPageViewsPerSession',
338 | ];
339 |
340 | yield 'sessionConversionRate' => [
341 | 'method' => fn (Metrics $metrics) => $metrics->sessionConversionRate(),
342 | 'metric' => 'sessionConversionRate',
343 | ];
344 |
345 | yield 'sessions' => [
346 | 'method' => fn (Metrics $metrics) => $metrics->sessions(),
347 | 'metric' => 'sessions',
348 | ];
349 |
350 | yield 'sessionsPerUser' => [
351 | 'method' => fn (Metrics $metrics) => $metrics->sessionsPerUser(),
352 | 'metric' => 'sessionsPerUser',
353 | ];
354 |
355 | yield 'shippingAmount' => [
356 | 'method' => fn (Metrics $metrics) => $metrics->shippingAmount(),
357 | 'metric' => 'shippingAmount',
358 | ];
359 |
360 | yield 'taxAmount' => [
361 | 'method' => fn (Metrics $metrics) => $metrics->taxAmount(),
362 | 'metric' => 'taxAmount',
363 | ];
364 |
365 | yield 'totalAdRevenue' => [
366 | 'method' => fn (Metrics $metrics) => $metrics->totalAdRevenue(),
367 | 'metric' => 'totalAdRevenue',
368 | ];
369 |
370 | yield 'totalPurchasers' => [
371 | 'method' => fn (Metrics $metrics) => $metrics->totalPurchasers(),
372 | 'metric' => 'totalPurchasers',
373 | ];
374 |
375 | yield 'totalRevenue' => [
376 | 'method' => fn (Metrics $metrics) => $metrics->totalRevenue(),
377 | 'metric' => 'totalRevenue',
378 | ];
379 |
380 | yield 'totalUsers' => [
381 | 'method' => fn (Metrics $metrics) => $metrics->totalUsers(),
382 | 'metric' => 'totalUsers',
383 | ];
384 |
385 | yield 'transactions' => [
386 | 'method' => fn (Metrics $metrics) => $metrics->transactions(),
387 | 'metric' => 'transactions',
388 | ];
389 |
390 | yield 'transactionsPerPurchaser' => [
391 | 'method' => fn (Metrics $metrics) => $metrics->transactionsPerPurchaser(),
392 | 'metric' => 'transactionsPerPurchaser',
393 | ];
394 |
395 | yield 'userConversionRate' => [
396 | 'method' => fn (Metrics $metrics) => $metrics->userConversionRate(),
397 | 'metric' => 'userConversionRate',
398 | ];
399 |
400 | yield 'userEngagementDuration' => [
401 | 'method' => fn (Metrics $metrics) => $metrics->userEngagementDuration(),
402 | 'metric' => 'userEngagementDuration',
403 | ];
404 |
405 | yield 'wauPerMau' => [
406 | 'method' => fn (Metrics $metrics) => $metrics->wauPerMau(),
407 | 'metric' => 'wauPerMau',
408 | ];
409 | }
410 |
411 | /**
412 | * @param Closure(Metrics): Metrics $method
413 | *
414 | * @dataProvider metricProvider
415 | */
416 | public function test_predefined_metrics(Closure $method, string $metric): void
417 | {
418 | $metrics = new Metrics;
419 | $metrics = $method($metrics);
420 |
421 | $this->assertEquals(1, $metrics->count());
422 | $this->assertInstanceOf(Collection::class, $metrics->getMetrics());
423 | $this->assertInstanceOf(Metric::class, $metrics->getMetrics()->first());
424 | $this->assertEquals($metric, $metrics->getMetrics()->first()->getName());
425 | }
426 | }
427 |
--------------------------------------------------------------------------------
/tests/ReportTest.php:
--------------------------------------------------------------------------------
1 | [
23 | 'fakeResponse' => [
24 | 'dimensionHeaders' => [
25 | [
26 | 'name' => 'eventName',
27 | ],
28 | ],
29 | 'metricHeaders' => [
30 | [
31 | 'name' => 'eventCount',
32 | 'type' => 'TYPE_INTEGER',
33 | ],
34 | ],
35 | 'rows' => [
36 | [
37 | 'dimensionValues' => [
38 | [
39 | 'value' => 'testEvent1',
40 | ],
41 | ],
42 | 'metricValues' => [
43 | [
44 | 'value' => '222',
45 | ],
46 | ],
47 | ],
48 | [
49 | 'dimensionValues' => [
50 | [
51 | 'value' => 'testEvent2',
52 | ],
53 | ],
54 | 'metricValues' => [
55 | [
56 | 'value' => '111',
57 | ],
58 | ],
59 | ],
60 | ],
61 | 'totals' => [
62 | [
63 | 'dimensionValues' => [
64 | [
65 | 'value' => 'RESERVED_TOTAL',
66 | ],
67 | ],
68 | 'metricValues' => [
69 | [
70 | 'value' => '333',
71 | ],
72 | ],
73 | ],
74 | ],
75 | 'rowCount' => 2,
76 | 'metadata' => [
77 | 'currencyCode' => 'USD',
78 | 'timeZone' => 'UTC',
79 | ],
80 | 'kind' => 'analyticsData#runReport',
81 | ],
82 | 'assertRequest' => function (array $reportRequest) {
83 | /** @var array{property: string, dateRanges: DateRange[], dimensions: Dimension[], metrics: Metric[]} $reportRequest */
84 | self::assertEquals('properties/'.'test123', $reportRequest['property']);
85 |
86 | self::assertCount(1, $reportRequest['dateRanges']);
87 | self::assertEquals('2022-10-22', $reportRequest['dateRanges'][0]->getStartDate());
88 | self::assertEquals('2022-11-21', $reportRequest['dateRanges'][0]->getEndDate());
89 |
90 | self::assertCount(1, $reportRequest['dimensions']);
91 | self::assertEquals('eventName', $reportRequest['dimensions'][0]->getName());
92 |
93 | self::assertCount(1, $reportRequest['metrics']);
94 | self::assertEquals('eventCount', $reportRequest['metrics'][0]->getName());
95 |
96 | return true;
97 | },
98 | 'reportCall' => fn () => Analytics::getTopEvents(),
99 | ];
100 |
101 | yield 'getUserAcquisitionOverview' => [
102 | 'fakeResponse' => [
103 | 'dimensionHeaders' => [
104 | [
105 | 'name' => 'firstUserDefaultChannelGroup',
106 | ],
107 | ],
108 | 'metricHeaders' => [
109 | [
110 | 'name' => 'sessions',
111 | 'type' => 'TYPE_INTEGER',
112 | ],
113 | ],
114 | 'rows' => [
115 | [
116 | 'dimensionValues' => [
117 | [
118 | 'value' => 'Direct',
119 | ],
120 | ],
121 | 'metricValues' => [
122 | [
123 | 'value' => '222',
124 | ],
125 | ],
126 | ],
127 | [
128 | 'dimensionValues' => [
129 | [
130 | 'value' => 'Referral',
131 | ],
132 | ],
133 | 'metricValues' => [
134 | [
135 | 'value' => '111',
136 | ],
137 | ],
138 | ],
139 | [
140 | 'dimensionValues' => [
141 | [
142 | 'value' => 'Organic Search',
143 | ],
144 | ],
145 | 'metricValues' => [
146 | [
147 | 'value' => '111',
148 | ],
149 | ],
150 | ],
151 | [
152 | 'dimensionValues' => [
153 | [
154 | 'value' => 'Organic Social',
155 | ],
156 | ],
157 | 'metricValues' => [
158 | [
159 | 'value' => '111',
160 | ],
161 | ],
162 | ],
163 | ],
164 | 'rowCount' => 4,
165 | 'metadata' => [
166 | 'currencyCode' => 'USD',
167 | 'timeZone' => 'UTC',
168 | ],
169 | 'kind' => 'analyticsData#runReport',
170 | ],
171 | 'assertRequest' => function (array $reportRequest) {
172 | /** @var array{property: string, dateRanges: DateRange[], dimensions: Dimension[], metrics: Metric[]} $reportRequest */
173 | self::assertEquals('properties/'.'test123', $reportRequest['property']);
174 |
175 | self::assertCount(1, $reportRequest['dateRanges']);
176 | self::assertEquals('2022-10-22', $reportRequest['dateRanges'][0]->getStartDate());
177 | self::assertEquals('2022-11-21', $reportRequest['dateRanges'][0]->getEndDate());
178 |
179 | self::assertCount(1, $reportRequest['dimensions']);
180 | self::assertEquals('firstUserDefaultChannelGroup', $reportRequest['dimensions'][0]->getName());
181 |
182 | self::assertCount(1, $reportRequest['metrics']);
183 | self::assertEquals('sessions', $reportRequest['metrics'][0]->getName());
184 |
185 | return true;
186 | },
187 | 'reportCall' => fn () => Analytics::getUserAcquisitionOverview(),
188 | ];
189 |
190 | yield 'getTopPages' => [
191 | 'fakeResponse' => [
192 | 'dimensionHeaders' => [
193 | [
194 | 'name' => 'pageTitle',
195 | ],
196 | ],
197 | 'metricHeaders' => [
198 | [
199 | 'name' => 'sessions',
200 | 'type' => 'TYPE_INTEGER',
201 | ],
202 | ],
203 | 'rows' => [
204 | [
205 | 'dimensionValues' => [
206 | [
207 | 'value' => 'Page Title 1',
208 | ],
209 | ],
210 | 'metricValues' => [
211 | [
212 | 'value' => '222',
213 | ],
214 | ],
215 | ],
216 | [
217 | 'dimensionValues' => [
218 | [
219 | 'value' => 'Page Title 2',
220 | ],
221 | ],
222 | 'metricValues' => [
223 | [
224 | 'value' => '111',
225 | ],
226 | ],
227 | ],
228 | [
229 | 'dimensionValues' => [
230 | [
231 | 'value' => 'Page Title 3',
232 | ],
233 | ],
234 | 'metricValues' => [
235 | [
236 | 'value' => '111',
237 | ],
238 | ],
239 | ],
240 | ],
241 | 'rowCount' => 3,
242 | 'metadata' => [
243 | 'currencyCode' => 'USD',
244 | 'timeZone' => 'UTC',
245 | ],
246 | 'kind' => 'analyticsData#runReport',
247 | ],
248 | 'assertRequest' => function (array $reportRequest) {
249 | /** @var array{property: string, dateRanges: DateRange[], dimensions: Dimension[], metrics: Metric[]} $reportRequest */
250 | self::assertEquals('properties/'.'test123', $reportRequest['property']);
251 |
252 | self::assertCount(1, $reportRequest['dateRanges']);
253 | self::assertEquals('2022-10-22', $reportRequest['dateRanges'][0]->getStartDate());
254 | self::assertEquals('2022-11-21', $reportRequest['dateRanges'][0]->getEndDate());
255 |
256 | self::assertCount(1, $reportRequest['dimensions']);
257 | self::assertEquals('pageTitle', $reportRequest['dimensions'][0]->getName());
258 |
259 | self::assertCount(1, $reportRequest['metrics']);
260 | self::assertEquals('sessions', $reportRequest['metrics'][0]->getName());
261 |
262 | return true;
263 | },
264 | 'reportCall' => fn () => Analytics::getTopPages(),
265 | ];
266 |
267 | yield 'getUserEngagement' => [
268 | 'fakeResponse' => [
269 | 'metricHeaders' => [
270 | [
271 | 'name' => 'averageSessionDuration',
272 | 'type' => 'TYPE_SECONDS',
273 | ],
274 | [
275 | 'name' => 'engagedSessions',
276 | 'type' => 'TYPE_INTEGER',
277 | ],
278 | [
279 | 'name' => 'sessionsPerUser',
280 | 'type' => 'TYPE_FLOAT',
281 | ],
282 | [
283 | 'name' => 'sessions',
284 | 'type' => 'TYPE_INTEGER',
285 | ],
286 | ],
287 | 'rows' => [
288 | [
289 | 'metricValues' => [
290 | [
291 | 'value' => '386.96577397089948',
292 | ],
293 | [
294 | 'value' => '256',
295 | ],
296 | [
297 | 'value' => '1.5555555555555556',
298 | ],
299 | [
300 | 'value' => '378',
301 | ],
302 | ],
303 | ],
304 | ],
305 | 'rowCount' => 4,
306 | 'metadata' => [
307 | 'currencyCode' => 'USD',
308 | 'timeZone' => 'UTC',
309 | ],
310 | 'kind' => 'analyticsData#runReport',
311 | ],
312 | 'assertRequest' => function (array $reportRequest) {
313 | /** @var array{property: string, dateRanges: DateRange[], dimensions: Dimension[], metrics: Metric[]} $reportRequest */
314 | self::assertEquals('properties/'.'test123', $reportRequest['property']);
315 |
316 | self::assertCount(1, $reportRequest['dateRanges']);
317 | self::assertEquals('2022-10-22', $reportRequest['dateRanges'][0]->getStartDate());
318 | self::assertEquals('2022-11-21', $reportRequest['dateRanges'][0]->getEndDate());
319 |
320 | self::assertCount(4, $reportRequest['metrics']);
321 | self::assertEquals('averageSessionDuration', $reportRequest['metrics'][0]->getName());
322 | self::assertEquals('engagedSessions', $reportRequest['metrics'][1]->getName());
323 | self::assertEquals('sessionsPerUser', $reportRequest['metrics'][2]->getName());
324 | self::assertEquals('sessions', $reportRequest['metrics'][3]->getName());
325 |
326 | return true;
327 | },
328 | 'reportCall' => fn () => Analytics::getUserEngagement(),
329 | ];
330 | }
331 |
332 | /**
333 | * @dataProvider reportProvider
334 | *
335 | * @param array $fakeResponse
336 | */
337 | public function test_reports(array $fakeResponse, Closure $assertRequest, Closure $reportCall): void
338 | {
339 | CarbonImmutable::setTestNow(CarbonImmutable::create(2022, 11, 21));
340 |
341 | $responseMock = $this->mock(RunReportResponse::class, function (MockInterface $mock) use ($fakeResponse) {
342 | $mock->shouldReceive('serializeToJsonString')
343 | ->once()
344 | ->andReturn(json_encode($fakeResponse));
345 | });
346 |
347 | $this->mock(BetaAnalyticsDataClient::class, function (MockInterface $mock) use ($responseMock, $assertRequest) {
348 | $mock->shouldReceive('runReport')
349 | ->with(Mockery::on($assertRequest))
350 | ->once()
351 | ->andReturn($responseMock);
352 | });
353 |
354 | $this->assertInstanceOf(ResponseData::class, $reportCall());
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | set('analytics.property_id', 'test123');
26 | config()->set('analytics.credentials.array', $this->credentials());
27 | }
28 |
29 | /**
30 | * @return array
31 | */
32 | protected function credentials(): array
33 | {
34 | return [
35 | 'type' => 'service_account',
36 | 'project_id' => 'bogus-project',
37 | 'private_key_id' => 'bogus-id',
38 | 'private_key' => 'bogus-key',
39 | 'client_email' => 'bogus-user@bogus-app.iam.gserviceaccount.com',
40 | 'client_id' => 'bogus-id',
41 | 'auth_uri' => 'https://accounts.google.com/o/oauth2/auth',
42 | 'token_uri' => 'https://accounts.google.com/o/oauth2/token',
43 | 'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs',
44 | 'client_x509_cert_url' => 'https://www.googleapis.com/robot/v1/metadata/x509/bogus-ser%40bogus-app.iam.gserviceaccount.com',
45 | ];
46 | }
47 | }
48 |
--------------------------------------------------------------------------------