├── .editorconfig
├── .github
└── workflows
│ └── php.yml
├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── build
├── coverage
│ └── .gitignore
├── logs
│ └── .gitignore
└── redis.ini
├── composer.json
├── composer.lock
├── phpcs.xml
├── phpunit.xml.dist
├── phpunit.xml.dist.bak
├── src
├── Lodash
│ ├── Auth
│ │ ├── Contracts
│ │ │ ├── AuthServiceContract.php
│ │ │ ├── ClientRepositoryContract.php
│ │ │ ├── RefreshTokenBridgeRepositoryContract.php
│ │ │ ├── RefreshTokenRepositoryContract.php
│ │ │ ├── TokenRepositoryContract.php
│ │ │ ├── UserContract.php
│ │ │ └── UserRepositoryContract.php
│ │ ├── Events
│ │ │ ├── StartEmulateEvent.php
│ │ │ └── StopEmulateEvent.php
│ │ ├── InternalUserProvider.php
│ │ ├── Passport
│ │ │ ├── Grants
│ │ │ │ ├── EmulateUserGrant.php
│ │ │ │ ├── GoogleAccessTokenGrant.php
│ │ │ │ ├── GoogleIdTokenGrant.php
│ │ │ │ ├── Grant.php
│ │ │ │ ├── InternalGrant.php
│ │ │ │ └── InternalRefreshTokenGrant.php
│ │ │ ├── Guards
│ │ │ │ ├── RequestGuard.php
│ │ │ │ └── TokenGuard.php
│ │ │ └── PassportServiceProvider.php
│ │ ├── Repositories
│ │ │ ├── ClientRepository.php
│ │ │ ├── RefreshTokenBridgeRepository.php
│ │ │ ├── RefreshTokenRepository.php
│ │ │ ├── TokenRepository.php
│ │ │ └── UserRepository.php
│ │ └── Services
│ │ │ └── AuthService.php
│ ├── Cache
│ │ ├── CacheManager.php
│ │ ├── CacheServiceProvider.php
│ │ └── RedisStore.php
│ ├── Commands
│ │ ├── ClearAll.php
│ │ ├── Compile.php
│ │ ├── DbClear.php
│ │ ├── DbDump.php
│ │ ├── DbRestore.php
│ │ ├── LogClear.php
│ │ ├── UserAdd.php
│ │ └── UserPassword.php
│ ├── Composer
│ │ ├── ComposerChecker.php
│ │ └── ComposerScripts.php
│ ├── Debug
│ │ └── DebugServiceProvider.php
│ ├── Elasticsearch
│ │ ├── ElasticsearchException.php
│ │ ├── ElasticsearchManager.php
│ │ ├── ElasticsearchManagerContract.php
│ │ ├── ElasticsearchQueryContract.php
│ │ └── ElasticsearchServiceProvider.php
│ ├── Eloquent
│ │ ├── Casts
│ │ │ └── BinaryUuid.php
│ │ ├── ManyToManyPreload.php
│ │ ├── UserIdentities.php
│ │ ├── UsesUuidAsPrimary.php
│ │ └── UuidAsPrimaryContract.php
│ ├── Foundation
│ │ └── Application.php
│ ├── Http
│ │ ├── Request.php
│ │ ├── Requests
│ │ │ └── RestrictsExtraAttributes.php
│ │ └── Resources
│ │ │ ├── ArrayResource.php
│ │ │ ├── ErrorResource.php
│ │ │ ├── JsonResource.php
│ │ │ ├── JsonResourceCollection.php
│ │ │ ├── Response
│ │ │ ├── PaginatedResourceResponse.php
│ │ │ └── ResourceResponse.php
│ │ │ ├── SuccessResource.php
│ │ │ ├── TransformableArrayResource.php
│ │ │ ├── TransformableContract.php
│ │ │ └── TransformsData.php
│ ├── Log
│ │ └── DateTimeFormatter.php
│ ├── Middlewares
│ │ ├── ApiLocale.php
│ │ ├── SetRequestContext.php
│ │ ├── SimpleBasicAuth.php
│ │ └── XssSecurity.php
│ ├── Queue
│ │ ├── QueueServiceProvider.php
│ │ └── SqsFifo
│ │ │ ├── Connectors
│ │ │ └── SqsFifoConnector.php
│ │ │ └── SqsFifoQueue.php
│ ├── Redis
│ │ ├── Connections
│ │ │ └── PhpRedisArrayConnection.php
│ │ ├── Connectors
│ │ │ └── PhpRedisConnector.php
│ │ ├── RedisManager.php
│ │ └── RedisServiceProvider.php
│ ├── ServiceProvider.php
│ ├── Support
│ │ ├── Arr.php
│ │ ├── Declensions.php
│ │ ├── Str.php
│ │ └── Uuid.php
│ ├── Testing
│ │ ├── Attributes.php
│ │ ├── DataStructuresProvider.php
│ │ ├── FakeDataProvider.php
│ │ └── Response.php
│ └── Validation
│ │ └── StrictTypesValidator.php
├── config
│ └── config.php
└── helpers.php
└── tests
├── Bootstrap.php
└── Unit
├── Eloquent
└── UsesUuidAsPrimaryTest.php
├── Http
├── Requests
│ ├── CustomRequest.php
│ └── RestrictsExtraAttributesTest.php
└── Resources
│ ├── ArrayResourceTest.php
│ ├── ErrorResourceTest.php
│ ├── JsonResourceCollectionTest.php
│ ├── User.php
│ ├── UserResource.php
│ └── UserResourceWithHidden.php
├── Middleware
└── SimpleBasicAuthTest.php
├── RedisTest.php
├── ServiceProviderTest.php
├── Support
├── DeclensionsTest.php
└── StrTest.php
├── TestCase.php
└── Testing
├── AttributesTest.php
├── DataStructuresBuilderTest.php
└── ItemStructuresProvider.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.yml]
15 | indent_style = space
16 | indent_size = 2
17 |
18 | # Tab indentation (no size specified)
19 | [Makefile]
20 | indent_style = tab
21 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHP Composer
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - name: Validate composer.json and composer.lock
18 | run: composer validate
19 |
20 | - name: Cache Composer packages
21 | id: composer-cache
22 | uses: actions/cache@v2
23 | with:
24 | path: vendor
25 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
26 | restore-keys: |
27 | ${{ runner.os }}-php-
28 |
29 | - name: Install dependencies
30 | if: steps.composer-cache.outputs.cache-hit != 'true'
31 | run: composer install --prefer-dist --no-progress --no-suggest
32 |
33 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
34 | # Docs: https://getcomposer.org/doc/articles/scripts.md
35 |
36 | - name: Run code style check
37 | run: composer run-script phpcs
38 |
39 | - name: Run test suite
40 | run: composer run-script test
41 |
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor/
3 | node_modules/
4 | npm-debug.log
5 |
6 | # Laravel 4 specific
7 | bootstrap/compiled.php
8 | app/storage/
9 |
10 | # Laravel 5 & Lumen specific
11 | public/storage
12 | public/hot
13 | storage/*.key
14 | .env.*.php
15 | .env.php
16 | .env
17 | Homestead.yaml
18 | Homestead.json
19 |
20 | # Rocketeer PHP task runner and deployment package. https://github.com/rocketeers/rocketeer
21 | .rocketeer/
22 | .phpunit*
23 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | external_code_coverage:
3 | timeout: 600
4 |
5 | checks:
6 | php:
7 | code_rating: true
8 | duplication: true
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: focal
2 | language: php
3 |
4 | env:
5 | global:
6 | - SETUP=stable
7 |
8 | matrix:
9 | fast_finish: true
10 | include:
11 | - php: 7.4
12 | - php: 8.0
13 | #- php: 7.3
14 | # env: SETUP=lowest
15 |
16 | cache:
17 | directories:
18 | - $HOME/.composer/cache
19 |
20 | services:
21 | - redis-server
22 | - mysql
23 |
24 | before_install:
25 | #- phpenv config-rm xdebug.ini || true
26 | - printf "\n" | pecl install -f igbinary
27 | # Install & Build Redis
28 | - printf "\n" | pecl install -f --nobuild redis
29 | - cd "$(pecl config-get temp_dir)/redis"
30 | - phpize
31 | - ./configure --enable-redis-igbinary
32 | - make && make install
33 | - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
34 | - cd -
35 | - travis_retry composer self-update
36 | - mysql -e 'CREATE DATABASE forge;'
37 |
38 | install:
39 | - travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest
40 |
41 | script:
42 | - php --ri redis
43 | - composer phpcs
44 | - composer coverage-clover
45 |
46 | after_script:
47 | - wget https://scrutinizer-ci.com/ocular.phar
48 | - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
49 |
50 | notifications:
51 | on_success: never
52 | on_failure: always
53 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at akalongman@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | -------------
3 |
4 | Before you contribute code to this project, please make sure it conforms to the PSR-2 coding standard
5 | and that the project unit tests still pass. The easiest way to contribute is to work on a checkout of the repository,
6 | or your own fork. If you do this, you can run the following commands to check if everything is ready to submit:
7 |
8 | composer phpcs
9 |
10 | Which should give you no output, indicating that there are no coding standard errors. And then:
11 |
12 | composer test
13 |
14 | Which should give you no failures or errors. You can ignore any skipped tests as these are for external tools.
15 |
16 | Pushing
17 | -------
18 |
19 | Development is based on the git flow branching model (see http://nvie.com/posts/a-successful-git-branching-model/ )
20 | If you fix a bug please push in hotfix branch.
21 | If you develop a new feature please create a new branch.
22 |
23 | Version
24 | -------
25 | Version number: 0.#version.#hotfix
26 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The [MIT License](http://opensource.org/licenses/mit-license.php)
2 |
3 | Copyright (c) 2016 [Avtandil Kikabidze aka LONGMAN](https://github.com/akalongman)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/build/coverage/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/build/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/build/redis.ini:
--------------------------------------------------------------------------------
1 | extension="igbinary.so"
2 | extension="redis.so"
3 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "longman/laravel-lodash",
3 | "type": "library",
4 | "description": "Add more functional to Laravel",
5 | "keywords": [
6 | "lodash",
7 | "laravel",
8 | "utilities",
9 | "igbinary",
10 | "redis",
11 | "phpredis",
12 | "aws",
13 | "sqs",
14 | "tools"
15 | ],
16 | "license": "MIT",
17 | "minimum-stability": "stable",
18 | "homepage": "https://github.com/akalongman/laravel-lodash",
19 | "support": {
20 | "issues": "https://github.com/akalongman/laravel-lodash/issues",
21 | "source": "https://github.com/akalongman/laravel-lodash"
22 | },
23 | "authors": [
24 | {
25 | "name": "Avtandil Kikabidze aka LONGMAN",
26 | "email": "akalongman@gmail.com",
27 | "homepage": "https://longman.me",
28 | "role": "Maintainer, Developer"
29 | }
30 | ],
31 | "require": {
32 | "php": "^8.3",
33 | "laravel/framework": "^12.0"
34 | },
35 | "require-dev": {
36 | "google/apiclient": "^2.18",
37 | "aws/aws-sdk-php": "^3.306",
38 | "elasticsearch/elasticsearch": "^8.17",
39 | "jetbrains/phpstorm-attributes": "^1.0",
40 | "laravel/horizon": "^5.24",
41 | "laravel/passport": "^12.0",
42 | "longman/php-code-style": "^10.1",
43 | "mockery/mockery": "^1.6",
44 | "neitanod/forceutf8": "^2.0",
45 | "orchestra/testbench": "^10.1",
46 | "phpunit/phpunit": "^11.0"
47 | },
48 | "autoload": {
49 | "psr-4": {
50 | "Longman\\LaravelLodash\\": "src/Lodash"
51 | },
52 | "files": [
53 | "src/helpers.php"
54 | ]
55 | },
56 | "autoload-dev": {
57 | "psr-4": {
58 | "Tests\\": "tests/"
59 | }
60 | },
61 | "config": {
62 | "sort-packages": true,
63 | "process-timeout": 3600,
64 | "allow-plugins": {
65 | "dealerdirect/phpcodesniffer-composer-installer": true,
66 | "php-http/discovery": true
67 | }
68 | },
69 | "extra": {
70 | "laravel": {
71 | "providers": [
72 | "Longman\\LaravelLodash\\ServiceProvider"
73 | ]
74 | }
75 | },
76 | "suggest": {
77 | "ext-json": "Needed for using resources",
78 | "ext-redis": "Needed for using phpredis",
79 | "ext-igbinary": "Needed for speed up Redis/Memcached data serialization and reduce serialized data size",
80 | "ext-msgpack": "Needed for speed up Redis/Memcached data serialization and reduce serialized data size",
81 | "ext-lzf": "Needed for serialization Redis traffic",
82 | "ext-lz4": "Needed for serialization Redis traffic",
83 | "longman/laravel-multilang": "Adds multilanguage functional to Laravel >=5.5",
84 | "ramsey/uuid": "Use UUID identifiers in the eloquent models",
85 | "elasticsearch/elasticsearch": "Use Elasticsearch service in the laravel",
86 | "aws/aws-sdk-php": "Use AWS SQS >=3",
87 | "barryvdh/laravel-debugbar": "Allow print compiled queries via function get_db_queries()",
88 | "neitanod/forceutf8": "Allow encoding/decoding utf-8 strings",
89 | "beyondcode/laravel-self-diagnosis": "Run system diagnosis with many custom checks"
90 | },
91 | "scripts": {
92 | "phpcs": "./vendor/bin/phpcs --standard=phpcs.xml -spn --encoding=utf-8 src/ tests/ --report-width=150",
93 | "phpcbf": "./vendor/bin/phpcbf --standard=phpcs.xml -spn --encoding=utf-8 src/ tests/ --report-width=150",
94 | "test": "./vendor/bin/phpunit -c phpunit.xml.dist",
95 | "coverage-clover": "./vendor/bin/phpunit --stop-on-failure --coverage-clover build/logs/clover.xml",
96 | "coverage-html": "./vendor/bin/phpunit --stop-on-failure --coverage-html build/coverage"
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | */tests/*
8 |
9 |
10 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./src
6 |
7 |
8 | ./src/config
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ./tests/
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/phpunit.xml.dist.bak:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./src
6 |
7 |
8 | ./src/config
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ./tests/
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Contracts/AuthServiceContract.php:
--------------------------------------------------------------------------------
1 | authService->retrieveUserById((int) $identifier);
21 | }
22 |
23 | public function retrieveByToken($identifier, $token): ?Authenticatable
24 | {
25 | return $this->authService->retrieveUserByToken((int) $identifier, $token);
26 | }
27 |
28 | public function updateRememberToken(Authenticatable $user, $token): void
29 | {
30 | $this->authService->updateRememberToken($user, $token);
31 | }
32 |
33 | public function retrieveByCredentials(array $credentials): ?Authenticatable
34 | {
35 | return $this->authService->retrieveByCredentials($credentials);
36 | }
37 |
38 | public function validateCredentials(Authenticatable $user, array $credentials): bool
39 | {
40 | $plain = $credentials['password'];
41 |
42 | return $this->authService->validateCredentials($user, $plain);
43 | }
44 |
45 | public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void
46 | {
47 | $this->authService->rehashPasswordIfRequired($user, $credentials, $force);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/Grants/EmulateUserGrant.php:
--------------------------------------------------------------------------------
1 | setRefreshTokenRepository($refreshTokenRepository);
31 | $this->refreshTokenTTL = new DateInterval('P1M');
32 | }
33 |
34 | public function respondToAccessTokenRequest(
35 | ServerRequestInterface $request,
36 | ResponseTypeInterface $responseType,
37 | DateInterval $accessTokenTtl,
38 | ): ResponseTypeInterface {
39 | $startEmulating = Arr::get($request->getAttributes(), 'startEmulating', false);
40 |
41 | $emulatedUser = $request->getAttribute('emulated_user');
42 |
43 | // Validate request
44 | $client = $this->validateClient($request);
45 | $scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
46 | $emulatedUser = $this->validateEmulatedUser($emulatedUser, $request);
47 |
48 | // Finalize the requested scopes
49 | $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $emulatedUser->getIdentifier());
50 |
51 | // Issue and persist access token
52 | $accessToken = $this->issueAccessToken($accessTokenTtl, $client, $emulatedUser->getIdentifier(), $scopes);
53 | $refreshToken = $this->issueRefreshToken($accessToken);
54 |
55 | if ($startEmulating) {
56 | $user = $request->getAttribute('user');
57 |
58 | event(new StartEmulateEvent($user, $emulatedUser));
59 |
60 | $user = $this->validateUser($user, $request, $emulatedUser);
61 | $this->authService->updateAccessToken($accessToken->getIdentifier(), $user->getId());
62 | } else {
63 | event(new StopEmulateEvent($emulatedUser));
64 | }
65 |
66 | // Inject access token into response type
67 | $responseType->setAccessToken($accessToken);
68 | $responseType->setRefreshToken($refreshToken);
69 |
70 | return $responseType;
71 | }
72 |
73 | public function getRefreshToken(AccessToken $token): void
74 | {
75 | $this->issueRefreshToken($token);
76 | }
77 |
78 | protected function validateUser(
79 | UserEntityInterface $user,
80 | ServerRequestInterface $request,
81 | UserEntityInterface $userEntity,
82 | ): UserEntityInterface {
83 | if (! $this->authService->canUserEmulateOtherUser($user, $userEntity)) {
84 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
85 |
86 | throw OAuthServerException::accessDenied();
87 | }
88 |
89 | if (! $user instanceof UserEntityInterface) {
90 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
91 |
92 | throw OAuthServerException::invalidCredentials();
93 | }
94 |
95 | return $user;
96 | }
97 |
98 | protected function validateEmulatedUser(
99 | UserEntityInterface $user,
100 | ServerRequestInterface $request,
101 | ): UserEntityInterface {
102 | return $user;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/Grants/GoogleAccessTokenGrant.php:
--------------------------------------------------------------------------------
1 | setRefreshTokenRepository($refreshTokenRepository);
28 | $this->refreshTokenTTL = new DateInterval('P1M');
29 | }
30 |
31 | public function respondToAccessTokenRequest(
32 | ServerRequestInterface $request,
33 | ResponseTypeInterface $responseType,
34 | DateInterval $accessTokenTtl,
35 | ): ResponseTypeInterface {
36 | // Validate request
37 | $client = $this->validateClient($request);
38 | $scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
39 | $user = $this->validateUser($request);
40 |
41 | // Finalize the requested scopes
42 | $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
43 |
44 | // Issue and persist access token
45 | $accessToken = $this->issueAccessToken($accessTokenTtl, $client, $user->getIdentifier(), $scopes);
46 | $refreshToken = $this->issueRefreshToken($accessToken);
47 |
48 | // Inject access token into response type
49 | $responseType->setAccessToken($accessToken);
50 | $responseType->setRefreshToken($refreshToken);
51 |
52 | // Fire login event
53 | $this->authService->fireLoginEvent('api', $user);
54 |
55 | return $responseType;
56 | }
57 |
58 | protected function validateUser(ServerRequestInterface $request): UserEntityInterface
59 | {
60 | $googleToken = $this->getRequestParameter('token', $request);
61 | if (is_null($googleToken)) {
62 | throw OAuthServerException::invalidRequest('token');
63 | }
64 |
65 | try {
66 | $user = $this->authService->getGoogleUserByAccessToken($googleToken);
67 | } catch (Throwable $e) {
68 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
69 |
70 | throw OAuthServerException::invalidRequest('token');
71 | }
72 |
73 | if (! $user instanceof UserEntityInterface) {
74 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
75 |
76 | throw OAuthServerException::invalidCredentials();
77 | }
78 |
79 | return $user;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/Grants/GoogleIdTokenGrant.php:
--------------------------------------------------------------------------------
1 | setRefreshTokenRepository($refreshTokenRepository);
28 | $this->refreshTokenTTL = new DateInterval('P1M');
29 | }
30 |
31 | public function respondToAccessTokenRequest(
32 | ServerRequestInterface $request,
33 | ResponseTypeInterface $responseType,
34 | DateInterval $accessTokenTtl,
35 | ): ResponseTypeInterface {
36 | // Validate request
37 | $client = $this->validateClient($request);
38 | $scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
39 | $user = $this->validateUser($request);
40 |
41 | // Finalize the requested scopes
42 | $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
43 |
44 | // Issue and persist access token
45 | $accessToken = $this->issueAccessToken($accessTokenTtl, $client, $user->getIdentifier(), $scopes);
46 | $refreshToken = $this->issueRefreshToken($accessToken);
47 |
48 | // Inject access token into response type
49 | $responseType->setAccessToken($accessToken);
50 | $responseType->setRefreshToken($refreshToken);
51 |
52 | // Fire login event
53 | $this->authService->fireLoginEvent('api', $user);
54 |
55 | return $responseType;
56 | }
57 |
58 | protected function validateUser(ServerRequestInterface $request): UserEntityInterface
59 | {
60 | $googleToken = $this->getRequestParameter('token', $request);
61 | if (is_null($googleToken)) {
62 | throw OAuthServerException::invalidRequest('token');
63 | }
64 |
65 | try {
66 | $user = $this->authService->getGoogleUserByIdToken($googleToken);
67 | } catch (Throwable $e) {
68 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
69 |
70 | throw OAuthServerException::invalidRequest('token');
71 | }
72 |
73 | if (! $user instanceof UserEntityInterface) {
74 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
75 |
76 | throw OAuthServerException::invalidCredentials();
77 | }
78 |
79 | return $user;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/Grants/Grant.php:
--------------------------------------------------------------------------------
1 | getRequestParameter('client_id', $request);
27 | if (is_null($clientId)) {
28 | throw OAuthServerException::invalidRequest('client_id');
29 | }
30 |
31 | // Get client without validating secret
32 | $client = $this->clientRepository->getClientEntity($clientId);
33 |
34 | if (! $client instanceof ClientEntityInterface) {
35 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
36 | throw OAuthServerException::invalidClient($request);
37 | }
38 |
39 | return $client;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/Grants/InternalGrant.php:
--------------------------------------------------------------------------------
1 | setRefreshTokenRepository($refreshTokenRepository);
29 | $this->refreshTokenTTL = new DateInterval('P1M');
30 | }
31 |
32 | public function respondToAccessTokenRequest(
33 | ServerRequestInterface $request,
34 | ResponseTypeInterface $responseType,
35 | DateInterval $accessTokenTtl,
36 | ): ResponseTypeInterface {
37 | // Validate request
38 | $client = $this->validateClient($request);
39 | $scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
40 | $user = $this->validateUser($request);
41 |
42 | // Finalize the requested scopes
43 | $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
44 |
45 | // Issue and persist access token
46 | $accessToken = $this->issueAccessToken($accessTokenTtl, $client, $user->getIdentifier(), $scopes);
47 | $refreshToken = $this->issueRefreshToken($accessToken);
48 |
49 | // Inject access token into response type
50 | $responseType->setAccessToken($accessToken);
51 | $responseType->setRefreshToken($refreshToken);
52 |
53 | // Fire login event
54 | $this->authService->fireLoginEvent('api', $user);
55 |
56 | return $responseType;
57 | }
58 |
59 | public function getRefreshToken(AccessToken $token): void
60 | {
61 | $this->issueRefreshToken($token);
62 | }
63 |
64 | protected function validateUser(ServerRequestInterface $request): UserEntityInterface
65 | {
66 | $login = $this->getRequestParameter('login', $request);
67 | if (is_null($login)) {
68 | throw OAuthServerException::invalidRequest('login');
69 | }
70 |
71 | $password = $this->getRequestParameter('password', $request);
72 | if (is_null($password)) {
73 | throw OAuthServerException::invalidRequest('password');
74 | }
75 |
76 | try {
77 | $user = $this->authService->retrieveByCredentials(['login' => $login]);
78 | } catch (Throwable $e) {
79 | report($e);
80 |
81 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
82 |
83 | throw OAuthServerException::invalidCredentials();
84 | }
85 |
86 | if (! $user instanceof UserEntityInterface) {
87 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
88 |
89 | throw OAuthServerException::invalidCredentials();
90 | }
91 |
92 | if (! $this->authService->validateCredentials($user, $password)) {
93 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
94 |
95 | throw OAuthServerException::invalidCredentials();
96 | }
97 |
98 | return $user;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/Grants/InternalRefreshTokenGrant.php:
--------------------------------------------------------------------------------
1 | setRefreshTokenRepository($refreshTokenRepository);
35 |
36 | $this->refreshTokenTTL = new DateInterval('P1M');
37 | }
38 |
39 | public function respondToAccessTokenRequest(
40 | ServerRequestInterface $request,
41 | ResponseTypeInterface $responseType,
42 | DateInterval $accessTokenTtl,
43 | ): ResponseTypeInterface {
44 | // Validate request
45 | $client = $this->validateClient($request);
46 | $oldRefreshToken = $this->validateOldRefreshToken($request, $client->getIdentifier());
47 | $scopes = $this->validateScopes(
48 | $this->getRequestParameter(
49 | 'scope',
50 | $request,
51 | implode(self::SCOPE_DELIMITER_STRING, $oldRefreshToken['scopes']),
52 | ),
53 | );
54 |
55 | // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure
56 | // the request doesn't include any new scopes
57 | foreach ($scopes as $scope) {
58 | if (in_array($scope->getIdentifier(), $oldRefreshToken['scopes'], true) === false) {
59 | throw OAuthServerException::invalidScope($scope->getIdentifier());
60 | }
61 | }
62 |
63 | // Expire old tokens
64 | $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
65 | if ($this->revokeRefreshTokens) {
66 | $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
67 | }
68 |
69 | // Issue and persist new access token
70 | $accessToken = $this->issueAccessToken($accessTokenTtl, $client, $oldRefreshToken['user_id'], $scopes);
71 | $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken));
72 | $responseType->setAccessToken($accessToken);
73 |
74 | // Issue and persist new refresh token if given
75 | if ($this->revokeRefreshTokens) {
76 | $refreshToken = $this->issueRefreshToken($accessToken);
77 |
78 | if ($refreshToken !== null) {
79 | $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken));
80 | $responseType->setRefreshToken($refreshToken);
81 | }
82 | }
83 |
84 | // when emulated user requests new access token. set emulator user id.
85 | $oldAccessToken = $this->tokenRepository->find($oldRefreshToken['access_token_id']);
86 | if ($oldAccessToken->emulator_user_id) {
87 | $this->authService->updateAccessToken($accessToken->getIdentifier(), $oldAccessToken->emulator_user_id);
88 | }
89 |
90 | return $responseType;
91 | }
92 |
93 | protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId)
94 | {
95 | $encryptedRefreshToken = $this->getRequestParameter('refresh_token', $request);
96 | if (! is_string($encryptedRefreshToken)) {
97 | throw OAuthServerException::invalidRequest('refresh_token');
98 | }
99 |
100 | // Validate refresh token
101 | try {
102 | $refreshToken = $this->decrypt($encryptedRefreshToken);
103 | } catch (Throwable $e) {
104 | throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e);
105 | }
106 |
107 | $refreshTokenData = json_decode($refreshToken, true);
108 | if ($refreshTokenData['client_id'] !== $clientId) {
109 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_CLIENT_FAILED, $request));
110 | throw OAuthServerException::invalidRefreshToken('Token is not linked to client');
111 | }
112 |
113 | if ($refreshTokenData['expire_time'] < time()) {
114 | throw OAuthServerException::invalidRefreshToken('Token has expired');
115 | }
116 |
117 | if ($this->refreshTokenRepository->isRefreshTokenRevoked($refreshTokenData['refresh_token_id']) === true) {
118 | throw OAuthServerException::invalidRefreshToken('Token has been revoked');
119 | }
120 |
121 | return $refreshTokenData;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/Guards/RequestGuard.php:
--------------------------------------------------------------------------------
1 | user = null;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/Guards/TokenGuard.php:
--------------------------------------------------------------------------------
1 | server = $server;
26 | $this->tokens = $tokens;
27 | $this->clients = $clients;
28 | $this->provider = $provider;
29 | $this->encrypter = $encrypter;
30 | $this->request = $request;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Passport/PassportServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerCustomRepositories();
45 | }
46 |
47 | #[Override]
48 | protected function registerAuthorizationServer(): void
49 | {
50 | $this->app->singleton(AuthorizationServer::class, function () {
51 | return tap($this->makeAuthorizationServer(), function (AuthorizationServer $server) {
52 | $this->enableGrants($server);
53 | });
54 | });
55 | }
56 |
57 | protected function enableGrants(AuthorizationServer $server): void
58 | {
59 | $accessTokenTtl = new DateInterval('PT1H');
60 |
61 | $server->setDefaultScope(Passport::$defaultScope);
62 |
63 | $server->enableGrantType($this->makeInternalGrant(), $accessTokenTtl);
64 |
65 | $server->enableGrantType($this->makeInternalRefreshTokenGrant(), $accessTokenTtl);
66 |
67 | $server->enableGrantType($this->makeGoogleAccessTokenGrant(), $accessTokenTtl);
68 |
69 | $server->enableGrantType($this->makeGoogleIdTokenGrant(), $accessTokenTtl);
70 |
71 | $server->enableGrantType($this->makeEmulateGrant(), $accessTokenTtl);
72 | }
73 |
74 | #[Override]
75 | protected function makeGuard(array $config): RequestGuard
76 | {
77 | return new RequestGuard(function (Request $request) use ($config) {
78 | /** @var \Illuminate\Auth\AuthManager $authManager */
79 | $authManager = $this->app['auth'];
80 |
81 | return (new TokenGuard(
82 | $this->app->make(ResourceServer::class),
83 | new PassportUserProvider($authManager->createUserProvider($config['provider']), $config['provider']),
84 | $this->app->make(TokenRepositoryContract::class),
85 | $this->app->make(ClientRepositoryContract::class),
86 | $this->app->make('encrypter'),
87 | $request,
88 | ))->user();
89 | }, $this->app['request']);
90 | }
91 |
92 | protected function registerCustomRepositories(): void
93 | {
94 | $this->app->bind(ClientRepositoryContract::class, ClientRepository::class);
95 | $this->app->bind(TokenRepositoryContract::class, TokenRepository::class);
96 | $this->app->bind(RefreshTokenBridgeRepositoryContract::class, RefreshTokenBridgeRepository::class);
97 | $this->app->bind(UserRepositoryContract::class, UserRepository::class);
98 | $this->app->bind(RefreshTokenRepositoryContract::class, RefreshTokenRepository::class);
99 | $this->app->bind(AuthServiceContract::class, AuthService::class);
100 | }
101 |
102 | protected function makeInternalGrant(): InternalGrant
103 | {
104 | $grant = new InternalGrant(
105 | $this->app->make(AuthServiceContract::class),
106 | $this->app->make(RefreshTokenBridgeRepositoryContract::class),
107 | );
108 |
109 | $grant->setRefreshTokenTTL(new DateInterval('P1Y'));
110 |
111 | return $grant;
112 | }
113 |
114 | protected function makeInternalRefreshTokenGrant(): InternalRefreshTokenGrant
115 | {
116 | $grant = new InternalRefreshTokenGrant(
117 | $this->app->make(RefreshTokenBridgeRepositoryContract::class),
118 | $this->app->make(TokenRepositoryContract::class),
119 | $this->app->make(AuthServiceContract::class),
120 | );
121 |
122 | $grant->setRefreshTokenTTL(new DateInterval('P1Y'));
123 |
124 | return $grant;
125 | }
126 |
127 | protected function makeGoogleAccessTokenGrant(): GoogleAccessTokenGrant
128 | {
129 | $grant = new GoogleAccessTokenGrant(
130 | $this->app->make(AuthServiceContract::class),
131 | $this->app->make(RefreshTokenBridgeRepositoryContract::class),
132 | );
133 |
134 | $grant->setRefreshTokenTTL(new DateInterval('P1Y'));
135 |
136 | return $grant;
137 | }
138 |
139 | protected function makeGoogleIdTokenGrant(): GoogleIdTokenGrant
140 | {
141 | $grant = new GoogleIdTokenGrant(
142 | $this->app->make(AuthServiceContract::class),
143 | $this->app->make(RefreshTokenBridgeRepositoryContract::class),
144 | );
145 |
146 | $grant->setRefreshTokenTTL(new DateInterval('P1Y'));
147 |
148 | return $grant;
149 | }
150 |
151 | protected function makeEmulateGrant(): EmulateUserGrant
152 | {
153 | $grant = new EmulateUserGrant(
154 | $this->app->make(AuthServiceContract::class),
155 | $this->app->make(RefreshTokenBridgeRepository::class),
156 | );
157 |
158 | $grant->setRefreshTokenTTL(new DateInterval('P1Y'));
159 |
160 | return $grant;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Repositories/ClientRepository.php:
--------------------------------------------------------------------------------
1 | find($accessTokenIdentifier);
17 | $token->setAttribute('emulator_user_id', $userId);
18 | $this->save($token);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lodash/Auth/Repositories/UserRepository.php:
--------------------------------------------------------------------------------
1 | database = $database;
27 | $this->googleOauthService = $googleOauthService;
28 | }
29 |
30 | public function findOneForAuth(int $id): ?UserContract
31 | {
32 | $item = $this->getModel()
33 | ->find($id);
34 |
35 | return $item;
36 | }
37 |
38 | public function getGoogleUserByAccessToken(string $googleToken): ?array
39 | {
40 | $this->googleOauthService
41 | ->getClient()
42 | ->setAccessToken($googleToken);
43 |
44 | $user = $this->googleOauthService->userinfo->get();
45 | if (empty($user->getEmail())) {
46 | return null;
47 | }
48 |
49 | return ['email' => $user->getEmail()];
50 | }
51 |
52 | public function getGoogleUserByIdToken(string $googleToken): ?array
53 | {
54 | try {
55 | $user = $this->googleOauthService
56 | ->getClient()
57 | ->verifyIdToken($googleToken);
58 | } catch (Throwable $e) {
59 | return null;
60 | }
61 |
62 | return $user;
63 | }
64 |
65 | public function getUserEntityByUserCredentials(
66 | $username,
67 | $password,
68 | $grantType,
69 | ClientEntityInterface $clientEntity,
70 | ) {
71 | throw new Exception('getUserEntityByUserCredentials is deprecated!');
72 | }
73 |
74 | public function retrieveByCredentials(array $credentials): ?UserContract
75 | {
76 | if (empty($credentials)) {
77 | return null;
78 | }
79 |
80 | $login = $credentials['login'] ?? $credentials['email'];
81 |
82 | $query = $this->getModel()->newQuery();
83 |
84 | $query->where('email', '=', $login);
85 |
86 | /** @var \App\Models\User $user */
87 | $user = $query->first();
88 | if (! $user) {
89 | return null;
90 | }
91 |
92 | return $user;
93 | }
94 |
95 | public function retrieveUserByToken(int $identifier, string $token): ?UserContract
96 | {
97 | throw new RuntimeException('Not implemented');
98 | }
99 |
100 | public function updateRememberToken(UserContract $user, string $token): void
101 | {
102 | // Not used
103 | }
104 |
105 | protected function getModel(): User
106 | {
107 | return new User();
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Lodash/Cache/CacheManager.php:
--------------------------------------------------------------------------------
1 | app['redis'];
20 |
21 | $connection = $config['connection'] ?? 'default';
22 |
23 | return $this->repository(new RedisStore($redis, $this->getPrefix($config), $connection));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lodash/Cache/CacheServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton('cache', static function ($app) {
28 | return new CacheManager($app);
29 | });
30 |
31 | $this->app->singleton('cache.store', static function ($app) {
32 | return $app['cache']->driver();
33 | });
34 |
35 | $this->app->singleton('memcached.connector', static function () {
36 | return new MemcachedConnector();
37 | });
38 |
39 | $this->app->singleton(RateLimiter::class);
40 | }
41 |
42 | /**
43 | * Get the services provided by the provider.
44 | *
45 | * @return array
46 | */
47 | public function provides(): array
48 | {
49 | return [
50 | 'cache',
51 | 'cache.store',
52 | 'memcached.connector',
53 | RateLimiter::class,
54 | ];
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Lodash/Cache/RedisStore.php:
--------------------------------------------------------------------------------
1 | call('clear-compiled');
33 | $this->call('cache:clear');
34 | $this->call('config:clear');
35 | $this->call('route:clear');
36 | $this->call('view:clear');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Lodash/Commands/Compile.php:
--------------------------------------------------------------------------------
1 | call('config:cache');
33 | $this->call('route:cache');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Lodash/Commands/DbClear.php:
--------------------------------------------------------------------------------
1 | confirmToProceed('Application In Production! Will be dropped all tables!')) {
39 | return;
40 | }
41 |
42 | $dbConn = $this->getDatabase();
43 | $connection = DB::connection($dbConn);
44 |
45 | $database = $connection->getDatabaseName();
46 | $tables = $connection->select('SHOW TABLES');
47 |
48 | if (empty($tables)) {
49 | $this->info('Tables not found in database "' . $database . '"');
50 |
51 | return;
52 | }
53 |
54 | $pretend = $this->input->getOption('pretend');
55 | $connection->transaction(function () use ($connection, $tables, $database, $pretend) {
56 | if (! $pretend) {
57 | $connection->statement('SET FOREIGN_KEY_CHECKS=0;');
58 | }
59 |
60 | foreach ($tables as $table) {
61 | foreach ($table as $value) {
62 | $stm = 'DROP TABLE IF EXISTS `' . $value . '`';
63 | if ($pretend) {
64 | $this->line($stm);
65 | } else {
66 | $connection->statement($stm);
67 | $this->comment('Table `' . $value . '` dropped');
68 | }
69 | }
70 | }
71 | if ($pretend) {
72 | return;
73 | }
74 |
75 | $connection->statement('SET FOREIGN_KEY_CHECKS=1;');
76 | $this->info('All tables dropped from database "' . $database . '"!');
77 | });
78 | }
79 |
80 | protected function getDatabase(): string
81 | {
82 | $database = $this->input->getOption('database');
83 |
84 | return $database ?: $this->laravel['config']['database.default'];
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Lodash/Commands/DbDump.php:
--------------------------------------------------------------------------------
1 | getDatabase();
43 | $connection = DB::connection($dbConn);
44 | $dbName = $connection->getConfig('database');
45 | $filename = $dbName . '_' . Carbon::now()->format('Ymd_His') . '.sql';
46 |
47 | $path = $this->getPath($filename);
48 |
49 | $process = new Process('mysqldump --host=' . $connection->getConfig('host') . ' --user=' . $connection->getConfig('username') . ' --password=' . $connection->getConfig('password') . ' ' . $dbName . ' > ' . $path);
50 | $process->run();
51 |
52 | // Executes after the command finishes
53 | if (! $process->isSuccessful()) {
54 | throw new ProcessFailedException($process);
55 | }
56 |
57 | $this->info('Database backup saved to: ' . $path);
58 | }
59 |
60 | protected function getPath(string $filename): string
61 | {
62 | $path = $this->input->getOption('path');
63 | if ($path) {
64 | if (! is_dir(base_path($path))) {
65 | mkdir(base_path($path), 0777, true);
66 | }
67 |
68 | $path = base_path($path . DIRECTORY_SEPARATOR . $filename);
69 | } else {
70 | $path = storage_path($filename);
71 | }
72 |
73 | return $path;
74 | }
75 |
76 | protected function getDatabase(): string
77 | {
78 | $database = $this->input->getOption('database');
79 |
80 | return $database ?: $this->laravel['config']['database.default'];
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Lodash/Commands/DbRestore.php:
--------------------------------------------------------------------------------
1 | confirmToProceed('Application In Production! Will be imported sql file!')) {
42 | return;
43 | }
44 |
45 | $dbConn = $this->getDatabase();
46 | $connection = DB::connection($dbConn);
47 |
48 | $path = $this->getFilePath();
49 | if (! file_exists($path)) {
50 | $this->error('File ' . $path . ' not found!');
51 |
52 | return;
53 | }
54 |
55 | $connection->unprepared(file_get_contents($path));
56 |
57 | $this->info('Database backup restored successfully');
58 | }
59 |
60 | protected function getDatabase(): string
61 | {
62 | $database = $this->input->getOption('database');
63 |
64 | return $database ?: $this->laravel['config']['database.default'];
65 | }
66 |
67 | protected function getFilePath(): string
68 | {
69 | $file = $this->input->getArgument('file');
70 |
71 | return base_path($file);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Lodash/Commands/LogClear.php:
--------------------------------------------------------------------------------
1 | confirmToProceed('Application In Production! Will be deleted all log files from storage/log folder!')) {
38 | return;
39 | }
40 |
41 | $logFiles = $filesystem->allFiles(storage_path('logs'));
42 | if (empty($logFiles)) {
43 | $this->comment('Log files does not found in path ' . storage_path('logs'));
44 |
45 | return;
46 | }
47 |
48 | foreach ($logFiles as $file) {
49 | if ($file->getExtension() !== 'log') {
50 | continue;
51 | }
52 |
53 | $status = $filesystem->delete($file->getRealPath());
54 | if (! $status) {
55 | continue;
56 | }
57 |
58 | $this->info('Successfully deleted: ' . $file);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Lodash/Commands/UserAdd.php:
--------------------------------------------------------------------------------
1 | getGuard();
34 | $config = config('auth');
35 |
36 | if (! isset($config['guards'][$guard]['provider'])) {
37 | $this->error('Provider not found for guard "' . $guard . '"!');
38 |
39 | return;
40 | }
41 | $provider = $config['guards'][$guard]['provider'];
42 | if (! isset($config['providers'][$provider]['model'])) {
43 | $this->error('Model not found for provider "' . $provider . '"!');
44 |
45 | return;
46 | }
47 | $model = $config['providers'][$provider]['model'];
48 | $user = new $model();
49 |
50 | $email = $this->argument('email');
51 | $password = $this->argument('password');
52 | if (! $password) {
53 | if ($this->confirm('Let system generate password?', true)) {
54 | $password = str_random(16);
55 | } else {
56 | $password = $this->secret('Please enter new password');
57 | }
58 | }
59 | $cryptedPassword = bcrypt($password);
60 | $user->create([
61 | 'email' => $email,
62 | 'password' => $cryptedPassword,
63 | ]);
64 |
65 | $this->comment('User successfully created');
66 | $this->info('E-Mail: ' . $email);
67 | $this->info('Password: ' . $password);
68 | }
69 |
70 | protected function getGuard(): string
71 | {
72 | $guard = $this->input->getOption('guard');
73 |
74 | return $guard ?? config('auth.defaults.guard');
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Lodash/Commands/UserPassword.php:
--------------------------------------------------------------------------------
1 | getGuard();
36 | $config = config('auth');
37 |
38 | if (! isset($config['guards'][$guard]['provider'])) {
39 | $this->error('Provider not found for guard "' . $guard . '"!');
40 |
41 | return;
42 | }
43 | $provider = $config['guards'][$guard]['provider'];
44 | if (! isset($config['providers'][$provider]['model'])) {
45 | $this->error('Model not found for provider "' . $provider . '"!');
46 |
47 | return;
48 | }
49 | $model = $config['providers'][$provider]['model'];
50 | $user = new $model();
51 |
52 | $email = $this->argument('email');
53 | $user = $user->where(compact('email'))->first();
54 | if (empty($user)) {
55 | $this->error('User with email "' . $email . '" not found');
56 |
57 | return;
58 | }
59 |
60 | $password = $this->argument('password');
61 | if (! $password) {
62 | if ($this->confirm('Let system generate password?', true)) {
63 | $password = str_random(16);
64 | } else {
65 | $password = $this->secret('Please enter new password');
66 | }
67 | }
68 | $cryptedPassword = bcrypt($password);
69 | $user->update([
70 | 'password' => $cryptedPassword,
71 | ]);
72 |
73 | $this->comment('User "' . $email . '" successfully updated');
74 | $this->info('New Password: ' . $password);
75 | }
76 |
77 | protected function getGuard(): string
78 | {
79 | $guard = $this->input->getOption('guard');
80 |
81 | return $guard ?? config('auth.defaults.guard');
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Lodash/Composer/ComposerChecker.php:
--------------------------------------------------------------------------------
1 | getHashPath(), $this->getHash($this->getContent($this->getJsonPath())));
31 | chmod($this->getHashPath(), 0777);
32 | }
33 |
34 | public function checkHash(): void
35 | {
36 | if (! $this->validateHash()) {
37 | throw new RuntimeException('The vendor folder is not in sync with the composer.lock file, it is recommended that you run `composer install`.');
38 | }
39 | }
40 |
41 | public function validateHash(): bool
42 | {
43 | if (! file_exists($this->getHashPath())) {
44 | return false;
45 | }
46 |
47 | $hash = $this->getContent($this->getHashPath());
48 | $currentHash = $this->getHash($this->getContent($this->getJsonPath()));
49 |
50 | return $hash === $currentHash;
51 | }
52 |
53 | private function getJsonPath(): string
54 | {
55 | return $this->baseDir . DIRECTORY_SEPARATOR . $this->lockPath;
56 | }
57 |
58 | private function getHashPath(): string
59 | {
60 | return $this->baseDir . DIRECTORY_SEPARATOR . $this->hashPath;
61 | }
62 |
63 | private function getHash(string $content): string
64 | {
65 | return md5($content);
66 | }
67 |
68 | private function getContent(string $file): string
69 | {
70 | if (! file_exists($file)) {
71 | throw new InvalidArgumentException('File ' . $file . ' does not found!');
72 | }
73 |
74 | return file_get_contents($file);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Lodash/Composer/ComposerScripts.php:
--------------------------------------------------------------------------------
1 | getComposer()->getConfig()->get('vendor-dir'));
16 |
17 | $composer = new ComposerChecker($baseDir);
18 | $composer->createHash();
19 | $event->getIO()->write('The composer.lock hash saved in the "vendor/composer.hash" file.');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Lodash/Debug/DebugServiceProvider.php:
--------------------------------------------------------------------------------
1 | getClientIp();
18 | if (! in_array($ip, $ips)) {
19 | return;
20 | }
21 |
22 | config(['app.debug' => true]);
23 | }
24 |
25 | public function register(): void
26 | {
27 | //
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Lodash/Elasticsearch/ElasticsearchException.php:
--------------------------------------------------------------------------------
1 | errors = $errors;
18 | }
19 |
20 | public function getErrors(): array
21 | {
22 | return $this->errors;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Lodash/Elasticsearch/ElasticsearchManagerContract.php:
--------------------------------------------------------------------------------
1 | app->singleton(Client::class, static function (Application $app) {
23 | // Logger instance
24 | $config = $app['config']->get('services.elastic_search');
25 |
26 | $params = [
27 | 'hosts' => $config['hosts'],
28 | ];
29 |
30 | if (! empty($config['connectionParams'])) {
31 | $params['connectionParams'] = $config['connectionParams'];
32 | }
33 |
34 | $logger = ! empty($config['log_channel']) ? $app['log']->stack($config['log_channel']) : null;
35 | if ($logger) {
36 | $params['logger'] = $logger;
37 | }
38 |
39 | $client = ClientBuilder::fromConfig($params);
40 |
41 | return $client;
42 | });
43 |
44 | $this->app->singleton(ElasticsearchManagerContract::class, static function (Application $app) {
45 | $client = $app->make(Client::class);
46 | $enabled = (bool) $app['config']->get('services.elastic_search.enabled', false);
47 |
48 | return new ElasticsearchManager($client, $enabled);
49 | });
50 | }
51 |
52 | public function provides(): array
53 | {
54 | return [Client::class, ElasticsearchManagerContract::class];
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Lodash/Eloquent/Casts/BinaryUuid.php:
--------------------------------------------------------------------------------
1 | Uuid::toBinary($value),
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Lodash/Eloquent/ManyToManyPreload.php:
--------------------------------------------------------------------------------
1 | getTable();
46 | $queryKeyColumn = $query->getQuery()->wheres[0]['column'];
47 | $join = $query->getQuery()->joins;
48 | $newQuery = $this->newQueryWithoutScopes();
49 | $connection = $this->getConnection();
50 |
51 | // Initialize MySQL variables inline
52 | $newQuery->from($connection->raw('(select @num:=0, @group:=0) as `vars`, ' . $this->quoteColumn($table)));
53 |
54 | // If no columns already selected, let's select *
55 | if (! $query->getQuery()->columns) {
56 | $newQuery->select($table . '.*');
57 | }
58 |
59 | // Make sure column aliases are unique
60 | $groupAlias = $table . '_grp';
61 | $numAlias = $table . '_rn';
62 |
63 | // Apply mysql variables
64 | $newQuery->addSelect(
65 | $connection->raw(
66 | '@num := if(@group = ' . $this->quoteColumn($queryKeyColumn) . ', @num+1, 1) as `' . $numAlias . '`, @group := ' . $this->quoteColumn($queryKeyColumn) . ' as `' . $groupAlias . '`',
67 | ),
68 | );
69 |
70 | // Make sure first order clause is the group order
71 | $newQuery->getQuery()->orders = (array) $query->getQuery()->orders;
72 | array_unshift($newQuery->getQuery()->orders, [
73 | 'column' => $queryKeyColumn,
74 | 'direction' => 'asc',
75 | ]);
76 |
77 | if ($join) {
78 | $leftKey = explode('.', $queryKeyColumn)[1];
79 | $leftKeyColumn = '`' . $table . '`.`' . $leftKey . '`';
80 | $newQuery->addSelect($queryKeyColumn);
81 | $newQuery->mergeBindings($query->getQuery());
82 | $newQuery->getQuery()->joins = (array) $query->getQuery()->joins;
83 | $query->whereRaw($leftKeyColumn . ' = ' . $this->quoteColumn($queryKeyColumn));
84 | }
85 |
86 | $query->from($connection->raw('(' . $newQuery->toSql() . ') as `' . $table . '`'))
87 | ->where($numAlias, '<=', $limit);
88 |
89 | return $this;
90 | }
91 |
92 | public function scopeLimitPerGroupViaUnion(Builder $query, int $limit = 10, array $pivotColumns = []): Model
93 | {
94 | $table = $this->getTable();
95 | $queryKeyColumn = $query->getQuery()->wheres[0]['column'];
96 | $joins = $query->getQuery()->joins;
97 | $connection = $this->getConnection();
98 |
99 | $queryKeyValues = $query->getQuery()->wheres[0]['values'];
100 | $pivotTable = explode('.', $queryKeyColumn)[0];
101 |
102 | $joinLeftColumn = $joins[0]->wheres[0]['first'];
103 | $joinRightColumn = $joins[0]->wheres[0]['second'];
104 | $joinOperator = $joins[0]->wheres[0]['operator'];
105 |
106 | // Remove extra wheres
107 | $wheres = $query->getQuery()->wheres;
108 | $bindings = $query->getQuery()->bindings;
109 | foreach ($wheres as $key => $where) {
110 | if (! isset($where['column']) || $where['column'] !== $queryKeyColumn) {
111 | continue;
112 | }
113 |
114 | //$count = count($where['values']);
115 | unset($wheres[$key]);
116 | }
117 | $groups = $query->getQuery()->groups;
118 | $orders = $query->getQuery()->orders;
119 |
120 | foreach ($queryKeyValues as $value) {
121 | if (! isset($unionQuery1)) {
122 | $unionQuery1 = $connection->table($pivotTable)
123 | ->select([$table . '.*'])
124 | ->join($table, $joinLeftColumn, $joinOperator, $joinRightColumn)
125 | ->where($queryKeyColumn, '=', $value)
126 | ->limit($limit);
127 | if (! empty($groups)) {
128 | foreach ($groups as $group) {
129 | $unionQuery1->groupBy($group);
130 | }
131 | }
132 |
133 | if (! empty($orders)) {
134 | foreach ($orders as $order) {
135 | $unionQuery1->orderBy($order['column'], $order['direction']);
136 | }
137 | }
138 |
139 | // Merge wheres
140 | $unionQuery1->mergeWheres($wheres, $bindings);
141 | } else {
142 | $select = [
143 | $table . '.*',
144 | ];
145 |
146 | foreach ($pivotColumns as $pivotColumn) {
147 | $select[] = $pivotTable . '.' . $pivotColumn . ' as pivot_' . $pivotColumn;
148 | }
149 |
150 | $unionQuery2 = $connection->table($pivotTable)
151 | ->select($select)
152 | ->join($table, $joinLeftColumn, $joinOperator, $joinRightColumn)
153 | ->where($queryKeyColumn, '=', $value)
154 | ->limit($limit);
155 | if (! empty($groups)) {
156 | foreach ($groups as $group) {
157 | $unionQuery2->groupBy($group);
158 | }
159 | }
160 |
161 | if (! empty($orders)) {
162 | foreach ($orders as $order) {
163 | $unionQuery2->orderBy($order['column'], $order['direction']);
164 | }
165 | }
166 |
167 | // Merge wheres
168 | $unionQuery2->mergeWheres($wheres, $bindings);
169 |
170 | $unionQuery1->unionAll($unionQuery2);
171 | }
172 | }
173 |
174 | if (! isset($unionQuery1)) {
175 | throw new InvalidArgumentException('Union query does not found');
176 | }
177 |
178 | $query->setQuery($unionQuery1);
179 |
180 | return $this;
181 | }
182 |
183 | private function quoteColumn(string $column): string
184 | {
185 | return '`' . str_replace('.', '`.`', $column) . '`';
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/Lodash/Eloquent/UserIdentities.php:
--------------------------------------------------------------------------------
1 | belongsTo($this->getUserClass(), $this->columnCreatedBy);
57 | }
58 |
59 | public function editor(): BelongsTo
60 | {
61 | return $this->belongsTo($this->getUserClass(), $this->columnUpdatedBy);
62 | }
63 |
64 | public function destroyer(): BelongsTo
65 | {
66 | return $this->belongsTo($this->getUserClass(), $this->columnDeletedBy);
67 | }
68 |
69 | public function usesUserIdentities(): bool
70 | {
71 | return $this->userIdentities;
72 | }
73 |
74 | public function stopUserIdentities(): self
75 | {
76 | $this->userIdentities = false;
77 |
78 | return $this;
79 | }
80 |
81 | public function startUserIdentities(): self
82 | {
83 | $this->userIdentities = true;
84 |
85 | return $this;
86 | }
87 |
88 | protected static function bootUserIdentities(): void
89 | {
90 | // Creating
91 | static::creating(static function (Model $model) {
92 | if (! $model->usesUserIdentities()) {
93 | return;
94 | }
95 |
96 | if (is_null($model->{$model->columnCreatedBy})) {
97 | $model->{$model->columnCreatedBy} = auth()->id();
98 | }
99 |
100 | if (! is_null($model->{$model->columnUpdatedBy})) {
101 | return;
102 | }
103 |
104 | $model->{$model->columnUpdatedBy} = auth()->id();
105 | });
106 |
107 | // Updating
108 | static::updating(static function (Model $model) {
109 | if (! $model->usesUserIdentities()) {
110 | return;
111 | }
112 |
113 | $model->{$model->columnUpdatedBy} = auth()->id();
114 | });
115 |
116 | // Deleting/Restoring
117 | if (! static::usingSoftDeletes()) {
118 | return;
119 | }
120 |
121 | static::deleting(static function (Model $model) {
122 | if (! $model->usesUserIdentities()) {
123 | return;
124 | }
125 |
126 | if (is_null($model->{$model->columnDeletedBy})) {
127 | $model->{$model->columnDeletedBy} = auth()->id();
128 | }
129 |
130 | $model->save();
131 | });
132 |
133 | static::restoring(static function (Model $model) {
134 | if (! $model->usesUserIdentities()) {
135 | return;
136 | }
137 |
138 | $model->{$model->columnDeletedBy} = null;
139 | });
140 | }
141 |
142 | protected function getUserClass(): string
143 | {
144 | $provider = auth()->guard()->getProvider();
145 | if ($provider) {
146 | return $provider->getModel();
147 | }
148 |
149 | $user = auth()->guard()->user();
150 | if ($user) {
151 | return $user::class;
152 | }
153 |
154 | if (class_exists(User::class)) {
155 | return User::class;
156 | }
157 |
158 | throw new InvalidArgumentException('User class can not detected');
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/Lodash/Eloquent/UsesUuidAsPrimary.php:
--------------------------------------------------------------------------------
1 | getKeyName();
34 |
35 | if (! empty($model->{$keyName})) {
36 | // Do some validation
37 | $model->isUuidBinary($model->{$keyName})
38 | ? Uuid::fromBytes($model->{$keyName})
39 | : Uuid::fromString($model->{$keyName});
40 | } elseif (! empty($model->attributes[$keyName])) {
41 | // Do some validation
42 | $model->isUuidBinary($model->attributes[$keyName])
43 | ? Uuid::fromBytes($model->attributes[$keyName])
44 | : Uuid::fromString($model->attributes[$keyName]);
45 | } else {
46 | $model->{$keyName} = Uuid::uuid1()->getBytes();
47 | }
48 | });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Lodash/Eloquent/UuidAsPrimaryContract.php:
--------------------------------------------------------------------------------
1 | getRevision();
30 | $version = 'v' . self::APP_VERSION;
31 | if (! empty($revision)) {
32 | $version .= '.' . substr($revision, 0, 8);
33 | }
34 |
35 | return $version;
36 | }
37 |
38 | public function getUrl(): string
39 | {
40 | return (string) config('app.url', 'http://localhost');
41 | }
42 |
43 | public function isDebug(): bool
44 | {
45 | return (bool) config('app.debug', false);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Request.php:
--------------------------------------------------------------------------------
1 | requestId)) {
31 | return $this->requestId;
32 | }
33 | $this->requestId = (string) Str::uuid();
34 |
35 | return $this->requestId;
36 | }
37 |
38 | public function getClientPlatform(): string
39 | {
40 | if (! is_null($this->requestPlatform)) {
41 | return $this->requestPlatform;
42 | }
43 |
44 | $ua = Str::lower($this->userAgent());
45 |
46 | if (str_contains($ua, 'okhttp')) {
47 | $this->requestPlatform = self::CLIENT_PLATFORM_ANDROID;
48 | } elseif (str_contains($ua, 'darwin')) {
49 | $this->requestPlatform = self::CLIENT_PLATFORM_IOS;
50 | } else {
51 | $this->requestPlatform = self::CLIENT_PLATFORM_WEB;
52 | }
53 |
54 | return $this->requestPlatform;
55 | }
56 |
57 | public function getReferrerWithoutDomain(): string
58 | {
59 | return str_replace(config('app.url'), '', (string) $this->server('HTTP_REFERER'));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Requests/RestrictsExtraAttributes.php:
--------------------------------------------------------------------------------
1 | checkForNotAllowedProperties();
33 |
34 | $this->checkForEmptyPayload();
35 |
36 | parent::prepareForValidation();
37 | }
38 |
39 | private function checkForEmptyPayload(): void
40 | {
41 | if (! $this->checkForEmptyPayload) {
42 | return;
43 | }
44 |
45 | if (in_array($this->method(), $this->methodsForEmptyPayload, true)) {
46 | if (empty($this->input())) {
47 | throw ValidationException::withMessages(['general' => [__('validation.payload_is_empty')]]);
48 | }
49 | }
50 | }
51 |
52 | private function checkForNotAllowedProperties(): void
53 | {
54 | if (! $this->checkForExtraProperties) {
55 | return;
56 | }
57 |
58 | $validationData = $this->getValidationData();
59 |
60 | // Ignore marked properties
61 | if (! empty($this->ignoreExtraProperties)) {
62 | foreach ($this->ignoreExtraProperties as $deleleValue) {
63 | if (($key = array_search($deleleValue, $validationData)) !== false) {
64 | unset($validationData[$key]);
65 | }
66 | }
67 | }
68 |
69 | $extraAttributes = array_diff(
70 | $validationData,
71 | $this->getAllowedAttributes(),
72 | );
73 |
74 | if (! empty($extraAttributes)) {
75 | $messages = [];
76 | foreach ($extraAttributes as $attribute) {
77 | $message = __('validation.restrict_extra_attributes', ['attribute' => $attribute]);
78 | if (config('app.debug')) {
79 | $message .= ' Request Class: ' . static::class;
80 | }
81 |
82 | $messages[$attribute] = $message;
83 | }
84 |
85 | throw ValidationException::withMessages($messages);
86 | }
87 | }
88 |
89 | private function getValidationData(): array
90 | {
91 | $data = Arr::dot($this->validationData());
92 |
93 | $return = [];
94 | foreach ($data as $key => $value) {
95 | $key = preg_replace('#\.\d+#', '.*', (string) $key);
96 | $return[] = $key;
97 | }
98 |
99 | return $return;
100 | }
101 |
102 | private function getAllowedAttributes(): array
103 | {
104 | if (method_exists($this, 'possibleAttributes')) {
105 | return $this->possibleAttributes();
106 | }
107 |
108 | $rules = app()->call([$this, 'rules']);
109 |
110 | return array_keys($rules);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/ArrayResource.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
12 | }
13 |
14 | public function getTransformed(): array
15 | {
16 | return $this->resource;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/ErrorResource.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
16 |
17 | $this->setDataWrapper('');
18 | }
19 |
20 | public function toArray($request): array
21 | {
22 | /**
23 | * Merge additional info and unset it, due to it causing
24 | * `errors` array to be wrapped in unnecessary `data` property
25 | */
26 | $result = array_merge(
27 | $this->resource,
28 | $this->additional ?? [],
29 | );
30 |
31 | $this->additional([]);
32 |
33 | return $result;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/JsonResource.php:
--------------------------------------------------------------------------------
1 | resource instanceof TransformableContract) {
39 | return $this->transformToApi($this->resource);
40 | }
41 |
42 | return [];
43 | }
44 |
45 | public function toArray($request): array
46 | {
47 | $data = $this->getResourceData();
48 |
49 | $relationsData = $this->getRelationsData();
50 |
51 | if (! empty($relationsData)) {
52 | $data['relationships'] = $relationsData;
53 | }
54 |
55 | if (! empty($this->getDataWrapper())) {
56 | return [$this->getDataWrapper() => $data];
57 | }
58 |
59 | return $data;
60 | }
61 |
62 | public function getDataWrapper(): string
63 | {
64 | return $this->dataWrapper;
65 | }
66 |
67 | public function setDataWrapper(string $dataWrapper): void
68 | {
69 | $this->dataWrapper = $dataWrapper;
70 | }
71 |
72 | public function withRelations(array $relations): self
73 | {
74 | $this->includedRelations = array_merge($this->includedRelations, $relations);
75 |
76 | return $this;
77 | }
78 |
79 | public function toResponse($request): JsonResponse
80 | {
81 | return (new ResourceResponse($this))->toResponse($request);
82 | }
83 |
84 | public function withResourceType(string $resourceType): self
85 | {
86 | $this->resourceType = $resourceType;
87 |
88 | return $this;
89 | }
90 |
91 | public function appendAdditional(array $data): self
92 | {
93 | $this->additional = array_merge_recursive($this->additional, $data);
94 |
95 | return $this;
96 | }
97 |
98 | public function jsonOptions(): int
99 | {
100 | return JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
101 | }
102 |
103 | protected function getResourceData(): array
104 | {
105 | if ($this->resource instanceof TransformableContract) {
106 | $data = [
107 | 'id' => $this->getResourceId(),
108 | 'type' => $this->getResourceType(),
109 | 'attributes' => $this->getTransformed(),
110 | ];
111 | } elseif (is_null($this->resource)) {
112 | $data = [
113 | 'id' => null,
114 | 'type' => null,
115 | 'attributes' => [],
116 | ];
117 | } else {
118 | $data = [
119 | 'id' => $this->getResourceId(),
120 | 'type' => $this->getResourceType(),
121 | 'attributes' => $this->getTransformed(),
122 | ];
123 | }
124 |
125 | return $data;
126 | }
127 |
128 | protected function getRelationsData(): array
129 | {
130 | if (is_null($this->resource)) {
131 | return [];
132 | }
133 |
134 | $relations = [];
135 |
136 | $relationsChain = self::undot($this->includedRelations);
137 | foreach ($relationsChain as $currentRelation => $remainingRelationChain) {
138 | $methodName = 'include' . ucfirst($currentRelation);
139 |
140 | if (! method_exists($this, $methodName)) {
141 | throw new Exception('Relation method does not exist: ' . $methodName);
142 | }
143 |
144 | $remainingRelations = [];
145 | if (! empty($remainingRelationChain)) {
146 | $remainingRelations = array_keys(Arr::dot($remainingRelationChain));
147 | }
148 |
149 | /** @var \Longman\LaravelLodash\Http\Resources\JsonResource $resource */
150 | $resource = $this->$methodName();
151 | if (is_null($resource)) {
152 | continue;
153 | }
154 |
155 | $resource->withRelations($remainingRelations);
156 |
157 | $relations[$currentRelation] = $resource;
158 | }
159 |
160 | return $relations;
161 | }
162 |
163 | protected function getResourceType(): string
164 | {
165 | if (! $this->resource instanceof TransformableContract) {
166 | return $this->resourceType;
167 | }
168 |
169 | $reflection = new ReflectionClass($this->resource->getModel());
170 |
171 | return $reflection->getShortName();
172 | }
173 |
174 | protected function getResourceId(): ?string
175 | {
176 | if ($this->resource instanceof UuidAsPrimaryContract) {
177 | return $this->resource->getUidString();
178 | }
179 |
180 | if ($this->resource instanceof TransformableContract) {
181 | return (string) $this->resource->getKey();
182 | }
183 |
184 | return null;
185 | }
186 |
187 | private static function undot(array $array): array
188 | {
189 | $result = [];
190 | foreach ($array as $item) {
191 | Arr::set($result, $item, []);
192 | }
193 |
194 | return $result;
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/JsonResourceCollection.php:
--------------------------------------------------------------------------------
1 | collection as $item) {
22 | $item->setDataWrapper('');
23 | }
24 |
25 | return ['data' => $this->collection];
26 | }
27 |
28 | public function withRelations(array $relations = []): self
29 | {
30 | /** @var \Longman\LaravelLodash\Http\Resources\JsonResource $item */
31 | foreach ($this->collection as $item) {
32 | $item->withRelations($relations);
33 | }
34 |
35 | return $this;
36 | }
37 |
38 | public function appendAdditional(array $data): self
39 | {
40 | $this->additional = array_merge_recursive($this->additional, $data);
41 |
42 | return $this;
43 | }
44 |
45 | public function jsonOptions(): int
46 | {
47 | return JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
48 | }
49 |
50 | protected function preparePaginatedResponse($request)
51 | {
52 | if ($this->preserveAllQueryParameters) {
53 | $this->resource->appends($request->query());
54 | } elseif (! is_null($this->queryParameters)) {
55 | $this->resource->appends($this->queryParameters);
56 | }
57 |
58 | return (new PaginatedResourceResponse($this))->toResponse($request);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/Response/PaginatedResourceResponse.php:
--------------------------------------------------------------------------------
1 | json(
31 | $this->wrap(
32 | $this->resource->resolve($request),
33 | array_merge_recursive(
34 | $this->paginationInformation($request),
35 | $this->resource->with($request),
36 | $this->resource->additional,
37 | ),
38 | ),
39 | $this->calculateStatus(),
40 | [],
41 | $jsonOptions,
42 | ), function ($response) use ($request) {
43 | $response->original = $this->resource->resource->map(static function ($item) {
44 | return is_array($item) ? Arr::get($item, 'resource') : $item->resource;
45 | });
46 |
47 | $this->resource->withResponse($request, $response);
48 | });
49 | }
50 |
51 | protected function paginationInformation($request): array
52 | {
53 | $paginated = $this->resource->resource->toArray();
54 |
55 | return [
56 | 'meta' => $this->meta($paginated),
57 | ];
58 | }
59 |
60 | protected function meta($paginated): array
61 | {
62 | return [
63 | 'pagination' => [
64 | 'total' => $paginated['total'] ?? null,
65 | 'count' => count($paginated['data']) ?? null,
66 | 'perPage' => $paginated['per_page'],
67 | 'currentPage' => $paginated['current_page'] ?? null,
68 | 'totalPages' => $paginated['last_page'] ?? null,
69 | 'links' => [
70 | 'next' => $paginated['next_page_url'] ?? null,
71 | 'previous' => $paginated['prev_page_url'] ?? null,
72 | ],
73 | ],
74 | ];
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/Response/ResourceResponse.php:
--------------------------------------------------------------------------------
1 | json(
27 | $this->wrap(
28 | $this->resource->resolve($request),
29 | $this->resource->with($request),
30 | $this->resource->additional,
31 | ),
32 | $this->calculateStatus(),
33 | [],
34 | $jsonOptions,
35 | ), function ($response) use ($request) {
36 | $response->original = $this->resource->resource;
37 |
38 | $this->resource->withResponse($request, $response);
39 | });
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/SuccessResource.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
16 |
17 | $this->setDataWrapper('');
18 | }
19 |
20 | public function toArray($request): array
21 | {
22 | /**
23 | * Merge additional info and unset it
24 | */
25 | $result = array_merge(
26 | $this->resource,
27 | $this->additional ?? [],
28 | );
29 |
30 | $this->additional([]);
31 |
32 | return $result;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/TransformableArrayResource.php:
--------------------------------------------------------------------------------
1 | getTransformFields() as $from => $to) {
13 | $data[$to] = $this->resource[$from];
14 | }
15 |
16 | return $data;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Lodash/Http/Resources/TransformableContract.php:
--------------------------------------------------------------------------------
1 | getterMethod]).
23 | * If static getterMethod is defined in the resource class, it will be called and as a first argument will be passed TransformableContract $model,
24 | * Otherwise, the model's method will be used.
25 | */
26 | protected static array $transformMapping = [];
27 |
28 | /**
29 | * Fields list for merging with general mapping during transformation o internal. Used for updating some fields.
30 | * Array values should be a column name from the database.
31 | */
32 | protected static array $internalMapping = [];
33 |
34 | /**
35 | * Fields list for hiding in output.
36 | * Array values should be a column name from the database.
37 | */
38 | protected static array $hideInOutput = [];
39 |
40 | public static function getTransformFields(): array
41 | {
42 | return static::$transformMapping;
43 | }
44 |
45 | public static function getInternalFields(): array
46 | {
47 | return static::$internalMapping;
48 | }
49 |
50 | public static function getHideInOutput(): array
51 | {
52 | return static::$hideInOutput;
53 | }
54 |
55 | public static function transformToApi(TransformableContract $model): array
56 | {
57 | $fields = static::getTransformFields();
58 | $hiddenProperties = $model->getHidden();
59 | $hideInOutput = static::getHideInOutput();
60 | $transformed = [];
61 | foreach ($fields as $internalField => $transformValue) {
62 | if (in_array($internalField, $hiddenProperties, true)) {
63 | continue;
64 | }
65 |
66 | if (in_array($internalField, $hideInOutput, true)) {
67 | continue;
68 | }
69 |
70 | [$key, $value] = self::parseKeyValue($internalField, $transformValue, $model);
71 |
72 | $transformed[$key] = $value;
73 | }
74 |
75 | return $transformed;
76 | }
77 |
78 | public static function transformToInternal(array $fields): array
79 | {
80 | $transformFields = static::getTransformFields();
81 | $transformFields = array_replace($transformFields, static::getInternalFields());
82 |
83 | $modelTransformedFields = [];
84 | foreach ($transformFields as $key => $transformField) {
85 | if (is_array($transformField)) {
86 | $modelTransformedFields[key($transformField)] = $key;
87 | } else {
88 | $modelTransformedFields[$transformField] = $key;
89 | }
90 | }
91 |
92 | $transformed = [];
93 | foreach ($fields as $fieldKey => $postValue) {
94 | if (isset($modelTransformedFields[$fieldKey])) {
95 | $transformed[$modelTransformedFields[$fieldKey]] = $postValue;
96 | }
97 | }
98 |
99 | return $transformed;
100 | }
101 |
102 | private static function parseKeyValue(string $internalField, $transformValue, TransformableContract $model): array
103 | {
104 | if (is_array($transformValue)) {
105 | $key = key($transformValue);
106 | $method = $transformValue[$key];
107 | if (method_exists(static::class, $method)) { // Check if getter exists in the resource class
108 | $value = call_user_func_array([static::class, $method], ['model' => $model]);
109 | } elseif (method_exists($model, $method)) { // Check if getter exists in the model class
110 | $value = $model->$method();
111 | } else {
112 | throw new LogicException('Method ' . $method . ' does not available not for resource ' . static::class . ', not for model ' . $model::class);
113 | }
114 | } else {
115 | // Try to find getter for external field
116 | $method = 'get' . Str::snakeCaseToPascalCase($transformValue);
117 | if (method_exists($model, $method)) {
118 | $key = $transformValue;
119 | $value = $model->$method();
120 | } else {
121 | // Call getter for internal field
122 | $method = 'get' . Str::snakeCaseToPascalCase($internalField);
123 | if (! method_exists($model, $method)) {
124 | throw new LogicException('Field ' . $internalField . ' getter (' . $method . ') does not available for model ' . $model::class);
125 | }
126 | $key = $transformValue;
127 | $value = $model->$method();
128 | }
129 | }
130 |
131 | return [$key, $value];
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Lodash/Log/DateTimeFormatter.php:
--------------------------------------------------------------------------------
1 | getLogger()->getHandlers() as $handler) {
18 | $handler->setFormatter(
19 | new LineFormatter(
20 | LineFormatter::SIMPLE_FORMAT,
21 | 'Y-m-d H:i:s.u',
22 | ),
23 | );
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Lodash/Middlewares/ApiLocale.php:
--------------------------------------------------------------------------------
1 | wantsJson()) {
23 | $locales = config('lodash.locales', []);
24 | $locale = $request->getPreferredLanguage(array_keys($locales));
25 | app()->setLocale($locale);
26 | }
27 |
28 | /** @var \Symfony\Component\HttpFoundation\Response $response */
29 | $response = $next($request);
30 | $locale = app()->getLocale();
31 | $response->headers->set('Content-Language', $locale, true);
32 |
33 | if (! empty($locales[$locale])) {
34 | setlocale(LC_ALL, $locales[$locale]['full_locale']);
35 | }
36 |
37 | return $response;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Lodash/Middlewares/SetRequestContext.php:
--------------------------------------------------------------------------------
1 | getRequestId();
18 | $requestPlatform = $request->getClientPlatform();
19 |
20 | logger()->withContext([
21 | 'request-id' => $requestId,
22 | 'request-platform' => $requestPlatform,
23 | ]);
24 |
25 | app()->instance('request-id', $requestId);
26 | app()->instance('request-platform', $requestPlatform);
27 |
28 | /** @var \Illuminate\Http\Response $response */
29 | $response = $next($request);
30 | $response->headers->set('Request-Id', $requestId, true);
31 |
32 | return $response;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Lodash/Middlewares/SimpleBasicAuth.php:
--------------------------------------------------------------------------------
1 | getUser() !== $config['user'] || $request->getPassword() !== $config['password']) {
21 | $header = ['WWW-Authenticate' => 'Basic'];
22 |
23 | return response('You have to supply your credentials to access this resource.', Response::HTTP_UNAUTHORIZED, $header);
24 | }
25 | }
26 |
27 | return $next($request);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Lodash/Middlewares/XssSecurity.php:
--------------------------------------------------------------------------------
1 | getUri();
21 | $excluded = config('lodash.xss.exclude_uris');
22 | if (! empty($excluded)) {
23 | foreach ($excluded as $uri) {
24 | if (str_contains($requestUri, $uri)) {
25 | return $response;
26 | }
27 | }
28 | }
29 |
30 | /** @see http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx */
31 | $response->headers->set('X-Frame-Options', config('lodash.xss.x_frame_options'), true);
32 |
33 | /** @see http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx */
34 | $response->headers->set('X-Content-Type-Options', config('lodash.xss.x_content_type_options'), true);
35 |
36 | /** @see http://msdn.microsoft.com/en-us/library/dd565647(v=vs.85).aspx */
37 | $response->headers->set('X-XSS-Protection', config('lodash.xss.x_xss_protection'), true);
38 |
39 | return $response;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Lodash/Queue/QueueServiceProvider.php:
--------------------------------------------------------------------------------
1 | {'register' . $connector . 'Connector'}($manager);
23 | }
24 | }
25 |
26 | protected function registerSqsFifoConnector(QueueManager $manager): void
27 | {
28 | $manager->addConnector('sqs.fifo', static function (): SqsFifoConnector {
29 | return new SqsFifoConnector();
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Lodash/Queue/SqsFifo/Connectors/SqsFifoConnector.php:
--------------------------------------------------------------------------------
1 | getDefaultConfiguration($config);
24 |
25 | if ($config['key'] && $config['secret']) {
26 | $config['credentials'] = Arr::only($config, ['key', 'secret']);
27 | }
28 |
29 | $options = $config['options'] ?? [];
30 | unset($config['options']);
31 |
32 | $queue = Arr::get($options, 'type') === 'fifo' ? new SqsFifoQueue(
33 | new SqsClient($config),
34 | $config['queue'],
35 | $config['prefix'] ?? '',
36 | $options,
37 | ) : new SqsQueue(
38 | new SqsClient($config),
39 | $config['queue'],
40 | $config['prefix'] ?? '',
41 | $options,
42 | );
43 |
44 | return $queue;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Lodash/Queue/SqsFifo/SqsFifoQueue.php:
--------------------------------------------------------------------------------
1 | sqs = $sqs;
32 | $this->prefix = $prefix;
33 | $this->default = $default;
34 | $this->options = $options;
35 |
36 | if (Arr::get($this->options, 'polling') !== 'long') {
37 | return;
38 | }
39 |
40 | $this->sqs->setQueueAttributes([
41 | 'Attributes' => [
42 | 'ReceiveMessageWaitTimeSeconds' => Arr::get($this->options, 'wait_time', 20),
43 | ],
44 | 'QueueUrl' => $this->getQueue($default),
45 | ]);
46 | }
47 |
48 | /**
49 | * Push a raw payload onto the queue.
50 | *
51 | * @see http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/general-recommendations.html
52 | * @see http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queue-recommendations.html
53 | *
54 | * @param string $payload
55 | * @param string $queue
56 | * @param array $options
57 | * @return mixed
58 | */
59 | public function pushRaw($payload, $queue = null, array $options = [])
60 | {
61 | $messageGroupId = $this->getMessageGroupId();
62 | $messageDeduplicationId = $this->getMessageDeduplicationId($payload);
63 |
64 | $messageId = $this->sqs->sendMessage([
65 | 'QueueUrl' => $this->getQueue($queue),
66 | 'MessageBody' => $payload,
67 | 'MessageGroupId' => $messageGroupId,
68 | 'MessageDeduplicationId' => $messageDeduplicationId,
69 | ])->get('MessageId');
70 |
71 | return $messageId;
72 | }
73 |
74 | /**
75 | * FIFO queues don't support per-message delays, only per-queue delays
76 | *
77 | * @see http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html
78 | */
79 | public function later($delay, $job, $data = '', $queue = null)
80 | {
81 | throw new BadMethodCallException('FIFO queues don\'t support per-message delays, only per-queue delays');
82 | }
83 |
84 | /**
85 | * Pop the next job off of the queue.
86 | *
87 | * @param string $queue
88 | * @return \Illuminate\Contracts\Queue\Job|null
89 | */
90 | public function pop($queue = null)
91 | {
92 | $response = $this->sqs->receiveMessage([
93 | 'QueueUrl' => $queue = $this->getQueue($queue),
94 | 'AttributeNames' => ['ApproximateReceiveCount'],
95 | 'MaxNumberOfMessages' => 1,
96 | 'WaitTimeSeconds' => Arr::get($this->options, 'wait_time', 20),
97 | ]);
98 |
99 | if (! is_null($response['Messages']) && count($response['Messages']) > 0) {
100 | return new SqsJob(
101 | $this->container,
102 | $this->sqs,
103 | $response['Messages'][0],
104 | $this->connectionName,
105 | $queue,
106 | );
107 | }
108 | }
109 |
110 | protected function getMessageGroupId(): string
111 | {
112 | $messageGroupId = session()->getId();
113 | if (empty($messageGroupId)) {
114 | $messageGroupId = str_random(40);
115 | }
116 |
117 | return $messageGroupId;
118 | }
119 |
120 | protected function getMessageDeduplicationId(string $payload): string
121 | {
122 | return config('app.debug') ? str_random(40) : sha1($payload);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Lodash/Redis/Connections/PhpRedisArrayConnection.php:
--------------------------------------------------------------------------------
1 | client->_hosts() as $master) {
26 | $async
27 | ? $this->command('rawCommand', [$master, 'flushdb', 'async'])
28 | : $this->command('flushdb', [$master]);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Lodash/Redis/Connectors/PhpRedisConnector.php:
--------------------------------------------------------------------------------
1 | createRedisClusterInstance(
31 | array_map([$this, 'buildClusterConnectionString'], $config),
32 | $options,
33 | ));
34 | }
35 |
36 | // Use client-side sharding
37 | return new PhpRedisArrayConnection($this->createRedisArrayInstance(
38 | array_map([$this, 'buildRedisArrayConnectionString'], $config),
39 | $options,
40 | ));
41 | }
42 |
43 | protected function createClient(array $config): Redis
44 | {
45 | return tap(new Redis(), function (Redis $client) use ($config) {
46 | if ($client instanceof RedisFacade) {
47 | throw new LogicException(
48 | 'Please remove or rename the Redis facade alias in your "app" configuration file in order to avoid collision with the PHP Redis extension.',
49 | );
50 | }
51 |
52 | $this->establishConnection($client, $config);
53 |
54 | if (! empty($config['password'])) {
55 | $client->auth((string) $config['password']);
56 | }
57 |
58 | if (! empty($config['database'])) {
59 | $client->select((int) $config['database']);
60 | }
61 |
62 | if (! empty($config['prefix'])) {
63 | $client->setOption(Redis::OPT_PREFIX, (string) $config['prefix']);
64 | }
65 |
66 | if (! empty($config['read_timeout'])) {
67 | $client->setOption(Redis::OPT_READ_TIMEOUT, (string) $config['read_timeout']);
68 | }
69 |
70 | if (array_key_exists('serializer', $config)) {
71 | $client->setOption(Redis::OPT_SERIALIZER, (string) $config['serializer']);
72 | }
73 |
74 | if (array_key_exists('compression', $config)) {
75 | $client->setOption(Redis::OPT_COMPRESSION, (string) $config['compression']);
76 | }
77 |
78 | if (array_key_exists('compression_level', $config)) {
79 | $client->setOption(Redis::OPT_COMPRESSION_LEVEL, (string) $config['compression_level']);
80 | }
81 |
82 | if (empty($config['scan'])) {
83 | $client->setOption(Redis::OPT_SCAN, (string) Redis::SCAN_RETRY);
84 | }
85 | });
86 | }
87 |
88 | protected function buildRedisArrayConnectionString(array $server): string
89 | {
90 | return $server['host'] . ':' . $server['port'];
91 | }
92 |
93 | protected function createRedisArrayInstance(array $servers, array $options): RedisArray
94 | {
95 | $client = new RedisArray($servers, Arr::only($options, [
96 | 'function',
97 | 'previous',
98 | 'retry_interval',
99 | 'lazy_connect',
100 | 'connect_timeout',
101 | 'read_timeout',
102 | 'algorithm',
103 | 'consistent',
104 | 'distributor',
105 | ]));
106 |
107 | if (! empty($options['password'])) {
108 | // @TODO: Remove after this will be implemented
109 | // https://github.com/phpredis/phpredis/issues/1508
110 | throw new InvalidArgumentException('RedisArray does not support authorization');
111 | //$client->auth((string) $options['password']);
112 | }
113 |
114 | if (! empty($options['database'])) {
115 | $client->select((int) $options['database']);
116 | }
117 |
118 | if (! empty($options['prefix'])) {
119 | $client->setOption(Redis::OPT_PREFIX, (string) $options['prefix']);
120 | }
121 |
122 | if (array_key_exists('serializer', $options)) {
123 | $client->setOption(Redis::OPT_SERIALIZER, (string) $options['serializer']);
124 | }
125 |
126 | if (array_key_exists('compression', $options)) {
127 | $client->setOption(Redis::OPT_COMPRESSION, (string) $options['compression']);
128 | }
129 |
130 | if (array_key_exists('compression_level', $options)) {
131 | $client->setOption(Redis::OPT_COMPRESSION_LEVEL, (string) $options['compression_level']);
132 | }
133 |
134 | return $client;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/Lodash/Redis/RedisManager.php:
--------------------------------------------------------------------------------
1 | driver) {
22 | 'predis' => new PredisConnector(),
23 | 'phpredis' => new PhpRedisConnector(),
24 | default => throw new InvalidArgumentException('Redis driver ' . $this->driver . ' does not exists'),
25 | };
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Lodash/Redis/RedisServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton('redis', static function ($app) {
15 | $config = $app->make('config')->get('database.redis');
16 |
17 | return new RedisManager($app, Arr::pull($config, 'client', 'phpredis'), $config);
18 | });
19 |
20 | $this->app->bind('redis.connection', static function ($app) {
21 | return $app['redis']->connection();
22 | });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Lodash/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | ClearAll::class,
33 | 'command.lodash.db.clear' => DbClear::class,
34 | 'command.lodash.db.dump' => DbDump::class,
35 | 'command.lodash.db.restore' => DbRestore::class,
36 | 'command.lodash.log.clear' => LogClear::class,
37 | 'command.lodash.user.add' => UserAdd::class,
38 | 'command.lodash.user.password' => UserPassword::class,
39 | ];
40 |
41 | public function boot(): void
42 | {
43 | $this->publishes([
44 | __DIR__ . '/../config/config.php' => config_path('lodash.php'),
45 | ]);
46 |
47 | $this->registerBladeDirectives();
48 |
49 | $this->loadTranslations();
50 |
51 | $this->loadValidations();
52 | }
53 |
54 | public function register(): void
55 | {
56 | $this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'lodash');
57 |
58 | $this->registerCommands();
59 |
60 | $this->registerRequestMacros();
61 | }
62 |
63 | protected function registerCommands(): void
64 | {
65 | if (! config('lodash.register.commands')) {
66 | return;
67 | }
68 |
69 | foreach ($this->commands as $name => $class) {
70 | $this->app->singleton($name, $class);
71 | }
72 |
73 | $this->commands(array_keys($this->commands));
74 | }
75 |
76 | protected function registerBladeDirectives(): void
77 | {
78 | if (! config('lodash.register.blade_directives')) {
79 | return;
80 | }
81 |
82 | // Display relative time
83 | app('blade.compiler')->directive('datetime', static function ($expression) {
84 | return "toIso8601String()
85 | . '\' title=\'' . $expression . '\'>'
86 | . with($expression)->diffForHumans() . '' ?>";
87 | });
88 |
89 | // Pluralization helper
90 | app('blade.compiler')->directive('plural', static function ($expression) {
91 | $expression = trim($expression, '()');
92 | [$count, $str, $spacer] = array_pad(preg_split('/,\s*/', $expression), 3, "' '");
93 |
94 | return "";
95 | });
96 | }
97 |
98 | protected function registerRequestMacros(): void
99 | {
100 | if (! config('lodash.register.request_macros')) {
101 | return;
102 | }
103 |
104 | Request::macro('getInt', function (string $name, int $default = 0): int {
105 | return (int) $this->get($name, $default);
106 | });
107 |
108 | Request::macro('getBool', function (string $name, bool $default = false): bool {
109 | return (bool) $this->get($name, $default);
110 | });
111 |
112 | Request::macro('getFloat', function (string $name, float $default = 0): float {
113 | return (float) $this->get($name, $default);
114 | });
115 |
116 | Request::macro('getString', function (string $name, string $default = ''): string {
117 | return (string) $this->get($name, $default);
118 | });
119 | }
120 |
121 | protected function loadTranslations(): void
122 | {
123 | if (! config('lodash.register.translations')) {
124 | return;
125 | }
126 |
127 | $this->loadTranslationsFrom(__DIR__ . '/../translations', 'lodash');
128 |
129 | $this->publishes([
130 | __DIR__ . '/../translations' => resource_path('lang/vendor/lodash'),
131 | ]);
132 | }
133 |
134 | protected function loadValidations(): void
135 | {
136 | if (! config('lodash.register.validation_rules')) {
137 | return;
138 | }
139 |
140 | Validator::extend('type', function ($attribute, $value, $parameters, $validator): bool {
141 | $validator->addReplacer('type', static function ($message, $attribute, $rule, $parameters): string {
142 | return str_replace(':type', $parameters[0], $message);
143 | });
144 |
145 | /** @var \Longman\LaravelLodash\Validation\StrictTypesValidator $validator */
146 | $customValidator = $this->app->make(StrictTypesValidator::class);
147 |
148 | return $customValidator->validate($attribute, $value, $parameters);
149 | }, 'The :attribute must be of type :type');
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Lodash/Support/Arr.php:
--------------------------------------------------------------------------------
1 | [ // სახელობითი
25 | '*' => 'ი',
26 | ],
27 | self::DECLENSION_2 => [ // მოთხრობითი
28 | '*' => 'მა',
29 | ],
30 | self::DECLENSION_3 => [ // მიცემითი
31 | 'ადმინისტრაცია' => 'ადმინისტრაციას',
32 | 'თანამშრომელი' => 'თანამშრომელს',
33 | 'ინჟინერი' => 'ინჟინერს',
34 | 'ოფიცერი' => 'ოფიცერს',
35 | 'ბიბლიოთეკა' => 'ბიბლიოთეკას',
36 | 'დამლაგებელი' => 'დამლაგებელს',
37 | 'ბუღალტერი' => 'ბუღალტერს',
38 | 'ად' => 'ადს',
39 | 'ან' => 'ანს',
40 | 'ამ' => 'ამს',
41 | 'ბა' => 'ბას',
42 | 'გე' => 'გეს',
43 | 'დე' => 'დეს',
44 | 'დი' => 'დის',
45 | 'ვა' => 'ვას',
46 | 'ვი' => 'ვს',
47 | 'ია' => 'იას',
48 | 'კა' => 'კას',
49 | 'კი' => 'კს',
50 | 'კო' => 'კოს',
51 | 'ლა' => 'ლას',
52 | 'ლი' => 'ლს',
53 | 'ლე' => 'ლეს',
54 | 'ნი' => 'ნს',
55 | 'რი' => 'რს',
56 | 'სი' => 'სს',
57 | 'ტი' => 'ტს',
58 | 'უა' => 'უას',
59 | 'ში' => 'შს',
60 | 'ცი' => 'ცს',
61 | 'ძე' => 'ძეს',
62 | 'წე' => 'წეს',
63 | 'ხი' => 'ხს',
64 | '*' => 'ს',
65 | ],
66 | self::DECLENSION_4 => [ // ნათესაობითი
67 | 'ადმინისტრაცია' => 'ადმინისტრაციის',
68 | 'თანამშრომელი' => 'თანამშრომლის',
69 | 'ინჟინერი' => 'ინჟინრის',
70 | 'ოფიცერი' => 'ოფიცრის',
71 | 'ბიბლიოთეკა' => 'ბიბლიოთეკის',
72 | 'დამლაგებელი' => 'დამლაგებლის',
73 | 'ბუღალტერი' => 'ბუღალტრის',
74 | 'ად' => 'ადის',
75 | 'ან' => 'ანის',
76 | 'ამ' => 'ამის',
77 | 'ბა' => 'ბის',
78 | 'გე' => 'გის',
79 | 'დე' => 'დის',
80 | 'დი' => 'დის',
81 | 'ვა' => 'ვას',
82 | 'ვი' => 'ვის',
83 | 'ია' => 'იას',
84 | 'კა' => 'კას',
85 | 'კი' => 'კის',
86 | 'კო' => 'კოს',
87 | 'ლა' => 'ლის',
88 | 'ლი' => 'ლის',
89 | 'ლე' => 'ლის',
90 | 'ნი' => 'ნის',
91 | 'რი' => 'რის',
92 | 'სი' => 'სის',
93 | 'ტი' => 'ტის',
94 | 'უა' => 'უას',
95 | 'ში' => 'შის',
96 | 'ცი' => 'ცის',
97 | 'ძე' => 'ძის',
98 | 'წე' => 'წის',
99 | 'ხი' => 'ხის',
100 | '*' => 'ს', // ის
101 | ],
102 | self::DECLENSION_5 => [ // მოქმედებითი
103 | '*' => 'ით',
104 | ],
105 | self::DECLENSION_6 => [ // ვითარებითი
106 | '*' => 'ად',
107 | ],
108 | self::DECLENSION_7 => [ // წოდებითი
109 | '*' => 'ო',
110 | ],
111 | ];
112 |
113 | public static function getAvailableDeclensions(): array
114 | {
115 | return [self::DECLENSION_1, self::DECLENSION_2, self::DECLENSION_3, self::DECLENSION_4, self::DECLENSION_5, self::DECLENSION_6, self::DECLENSION_7];
116 | }
117 |
118 | public static function applyDeclension(string $word, int $declension): string
119 | {
120 | if (! in_array($declension, self::getAvailableDeclensions(), true)) {
121 | throw new InvalidArgumentException('Declension "' . $declension . '" is invalid');
122 | }
123 |
124 | $rules = self::$declensionRules[$declension];
125 | $turnedWord = null;
126 | foreach ($rules as $suffix1 => $suffix2) {
127 | if (Str::substr($word, -Str::length($suffix1)) === $suffix1) {
128 | $turnedWord = Str::substr($word, 0, -Str::length($suffix1)) . $suffix2;
129 | break;
130 | }
131 | }
132 |
133 | if (is_null($turnedWord)) {
134 | $turnedWord = $word . $rules['*'];
135 | }
136 |
137 | return $turnedWord;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Lodash/Support/Str.php:
--------------------------------------------------------------------------------
1 | = $finalLength) {
39 | return $value;
40 | }
41 | $diff = $finalLength - $length;
42 | $value = $dir === 'left' ? str_repeat('0', $diff) . $value : $value . str_repeat('0', $diff);
43 |
44 | return $value;
45 | }
46 |
47 | public static function formatBalance(?int $amount = 0, int $d = 2): string
48 | {
49 | //$amount = str_replace([',', ' '], ['.', ''], $amount);
50 | $amount = (float) $amount;
51 | $amount /= 100;
52 | $amount = number_format($amount, $d, '.', '');
53 |
54 | return $amount;
55 | }
56 |
57 | public static function snakeCaseToPascalCase(string $string): string
58 | {
59 | $str = str_replace('_', '', ucwords($string, '_'));
60 |
61 | return $str;
62 | }
63 |
64 | public static function pascalCaseToSnakeCase(string $string): string
65 | {
66 | $string = strtolower(preg_replace('/(? $strLength) {
99 | $start = $strLength;
100 | }
101 |
102 | if ($length < 0) {
103 | $length = max(0, $strLength - $start + $length);
104 | } elseif ((is_null($length) === true) || ($length > $strLength)) {
105 | $length = $strLength;
106 | }
107 |
108 | if (($start + $length) > $strLength) {
109 | $length = $strLength - $start;
110 | }
111 |
112 | return mb_substr($string, 0, $start) . $replacement . mb_substr($string, $start + $length, $strLength - $start - $length);
113 | }
114 |
115 | public static function limitMiddle(string $value, int $limit = 100, string $separator = '...'): string
116 | {
117 | $length = self::length($value);
118 |
119 | if ($length <= $limit) {
120 | return $value;
121 | }
122 |
123 | return self::substrReplaceUnicode($value, $separator, $limit / 2, $length - $limit);
124 | }
125 |
126 | public static function toDotNotation(string $value): string
127 | {
128 | $value = trim($value);
129 | $value = preg_replace('/\[(.+)\]/U', '.$1', $value);
130 |
131 | return $value;
132 | }
133 |
134 | /**
135 | * @param mixed $data
136 | * @return string
137 | */
138 | public static function hash($data): string
139 | {
140 | if (is_array($data)) {
141 | sort($data);
142 | $data = serialize($data);
143 | }
144 |
145 | if (is_object($data)) {
146 | $data = serialize($data);
147 | }
148 |
149 | if (is_null($data)) {
150 | $data = 'NULL';
151 | }
152 |
153 | return sha1((string) $data);
154 | }
155 |
156 | public static function convertToUtf8(string $string): string
157 | {
158 | if (! class_exists(Encoding::class)) {
159 | throw new RuntimeException('To use this method, package "neitanod/forceutf8" should be installed!');
160 | }
161 |
162 | /** @link https://github.com/neitanod/forceutf8 */
163 | $string = Encoding::toUTF8($string);
164 |
165 | $string = stripslashes(trim($string));
166 |
167 | $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
168 |
169 | return $string;
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/Lodash/Support/Uuid.php:
--------------------------------------------------------------------------------
1 | toString();
30 | }
31 |
32 | public static function toBinary(string $uuid): string
33 | {
34 | $uuid = UuidFactory::fromString(strtolower($uuid));
35 |
36 | return $uuid->getBytes();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Lodash/Testing/Attributes.php:
--------------------------------------------------------------------------------
1 | */
23 | private array $relations = [];
24 | private string $relationName;
25 | private int $count;
26 |
27 | public function __construct(array $params, string $relationName = 'root', int $count = 1)
28 | {
29 | $this->params = $params;
30 | $this->relationName = $relationName;
31 | $this->count = $count;
32 | $this->parseParameters($params);
33 | }
34 |
35 | public function getAttributes(array $extraAttrs = [], array $except = []): array
36 | {
37 | $array = array_replace_recursive($this->attributes, $extraAttrs);
38 |
39 | return Arr::except($array, $except);
40 | }
41 |
42 | public function hasAttribute(string $name): bool
43 | {
44 | return array_key_exists($name, $this->attributes);
45 | }
46 |
47 | public function getAttribute(string $name, mixed $default = null): mixed
48 | {
49 | return $this->attributes[$name] ?? $default;
50 | }
51 |
52 | public function setAttribute(string $name, $value): void
53 | {
54 | $this->attributes[$name] = $value;
55 | }
56 |
57 | public function getCount(): int
58 | {
59 | return $this->count;
60 | }
61 |
62 | public function getRelationName(): string
63 | {
64 | return $this->relationName;
65 | }
66 |
67 | public function getRelations(): array
68 | {
69 | return $this->relations;
70 | }
71 |
72 | public function hasRelation(string $name): bool
73 | {
74 | return array_key_exists($name, $this->getRelations());
75 | }
76 |
77 | public function getRelation(string $name): self
78 | {
79 | if (! $this->hasRelation($name)) {
80 | throw new InvalidArgumentException('Relation "' . $name . '" does not found');
81 | }
82 |
83 | return $this->getRelations()[$name];
84 | }
85 |
86 | public function addRelation(string $name, int $count = 1, array $data = []): void
87 | {
88 | $this->params = array_replace_recursive($this->params, [self::RELATION_MARKER . $name . ':' . $count => $data]);
89 | }
90 |
91 | public function toArray(array $extraParams = [], array $except = []): array
92 | {
93 | $array = array_replace_recursive($this->params, $extraParams);
94 |
95 | return Arr::except($array, $except);
96 | }
97 |
98 | private function parseParameters(array $params): void
99 | {
100 | $attributes = [];
101 | $relations = [];
102 | foreach ($params as $key => $data) {
103 | if (str_starts_with((string) $key, self::RELATION_MARKER)) {
104 | $ex = explode(':', $key);
105 | if (! isset($ex[1])) {
106 | throw new InvalidArgumentException('Relation is empty');
107 | }
108 |
109 | $relName = $ex[1];
110 | $count = 1;
111 | if (isset($ex[2])) {
112 | $count = $ex[2] === self::DYNAMIC_COUNT_MARKER ? count($data) : (int) $ex[2];
113 | }
114 | $attrs = $data;
115 |
116 | $relations[$relName] = new self($attrs, $relName, $count);
117 | } else {
118 | $attributes[$key] = $data;
119 | }
120 | }
121 |
122 | $this->attributes = $attributes;
123 | $this->relations = $relations;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Lodash/Testing/DataStructuresProvider.php:
--------------------------------------------------------------------------------
1 | connection($name);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Lodash/Testing/Response.php:
--------------------------------------------------------------------------------
1 | getDecodedContent();
41 |
42 | PHPUnit::assertCount($count, $response['data'] ?? []);
43 |
44 | return $this;
45 | }
46 |
47 | public function assertJsonDataPagination(array $data): self
48 | {
49 | $response = $this->getDecodedContent();
50 |
51 | PHPUnit::assertEquals($data['currentPage'], $response['meta']['pagination']['currentPage']);
52 | PHPUnit::assertEquals($data['perPage'], $response['meta']['pagination']['perPage']);
53 | PHPUnit::assertEquals($data['count'], $response['meta']['pagination']['count']);
54 | PHPUnit::assertEquals($data['total'], $response['meta']['pagination']['total']);
55 |
56 | return $this;
57 | }
58 |
59 | public function assertJsonDataCollectionStructure(array $data, bool $includePagerMeta = true): self
60 | {
61 | $struct = self::$successResponseStructure;
62 | $struct['data'] = [$data];
63 |
64 | if ($includePagerMeta) {
65 | $struct['meta'] = [
66 | 'pagination' => self::$pagerMetaStructure,
67 | ];
68 | }
69 |
70 | PHPUnit::assertNotEmpty($this->getDecodedContent()['data'], 'Data collection is empty.');
71 |
72 | $this->assertJsonStructure($struct);
73 |
74 | return $this;
75 | }
76 |
77 | public function assertJsonDataItemStructure(array $data): self
78 | {
79 | $struct = ['data' => $data];
80 |
81 | $this->assertJsonStructure($struct);
82 |
83 | return $this;
84 | }
85 |
86 | public function assertJsonErrorStructure(?string $message = null, bool $includeMeta = false): self
87 | {
88 | $structure = self::$errorResponseStructure;
89 | if (! $includeMeta) {
90 | $structure = Arr::except($structure, 'meta');
91 | }
92 | $this->assertJsonStructure($structure);
93 | $this->assertJson(['status' => 'error']);
94 | if ($message) {
95 | $this->assertJson(['message' => $message]);
96 | }
97 |
98 | return $this;
99 | }
100 |
101 | public function assertJsonValidationErrorStructure(array $errors = [], bool $includeMeta = false): self
102 | {
103 | $structure = self::$errorResponseStructure;
104 | if (! $includeMeta) {
105 | $structure = Arr::except($structure, 'meta');
106 | }
107 | $structure = array_merge($structure, ['errors']);
108 | $this->assertJsonStructure($structure);
109 | $this->assertJson(['message' => __('validation.error'), 'status' => 'error']);
110 | if ($errors) {
111 | $this->assertJsonValidationErrors($errors);
112 | }
113 |
114 | return $this;
115 | }
116 |
117 | public function assertJsonSuccessStructure(?string $message = null, bool $includeMeta = false): self
118 | {
119 | $structure = self::$successResponseStructure;
120 | if (! $includeMeta) {
121 | $structure = Arr::except($structure, 'meta');
122 | }
123 | $this->assertJsonStructure($structure);
124 | $this->assertJson(['status' => 'ok']);
125 | if ($message) {
126 | $this->assertJson(['message' => $message]);
127 | }
128 |
129 | return $this;
130 | }
131 |
132 | public function getDecodedContent(): array
133 | {
134 | $content = $this->getContent();
135 |
136 | return json_decode($content, true);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/Lodash/Validation/StrictTypesValidator.php:
--------------------------------------------------------------------------------
1 | self::NATIVE_TYPE_INT,
18 | 'integer' => self::NATIVE_TYPE_INT,
19 | 'float' => self::NATIVE_TYPE_FLOAT,
20 | 'double' => self::NATIVE_TYPE_FLOAT,
21 | 'bool' => self::NATIVE_TYPE_BOOL,
22 | 'boolean' => self::NATIVE_TYPE_BOOL,
23 | ];
24 |
25 | public function validate($attribute, $value, $parameters): bool
26 | {
27 | if (empty($parameters)) {
28 | return false;
29 | }
30 |
31 | $valueType = gettype($value);
32 | $requiredType = $parameters[0];
33 |
34 | if (empty(static::TYPE_MAP[$requiredType]) || $this->isNativeTypeString($requiredType)) {
35 | return $valueType === $requiredType;
36 | }
37 |
38 | return $valueType === static::TYPE_MAP[$requiredType];
39 | }
40 |
41 | protected function isNativeTypeString(string $type): bool
42 | {
43 | return in_array($type, [
44 | static::NATIVE_TYPE_INT,
45 | static::NATIVE_TYPE_FLOAT,
46 | static::NATIVE_TYPE_BOOL,
47 | ]);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | [
7 | 'en' => [
8 | 'name' => 'English',
9 | 'native_name' => 'English',
10 | 'flag' => 'gb',
11 | 'locale' => 'en',
12 | 'canonical_locale' => 'en_GB',
13 | 'full_locale' => 'en_GB.UTF-8',
14 | ],
15 | 'ka' => [
16 | 'name' => 'Georgian',
17 | 'native_name' => 'ქართული',
18 | 'flag' => 'ge',
19 | 'locale' => 'ka',
20 | 'canonical_locale' => 'ka_GE',
21 | 'full_locale' => 'ka_GE.UTF-8',
22 | ],
23 | ],
24 |
25 | 'debug' => [
26 | 'ips' => explode(',', env('DEBUG_IP_LIST', '')), // IP list for enabling debug mode
27 | ],
28 |
29 | 'xss' => [
30 | 'exclude_uris' => [], // Excluded URI's for Xss middleware
31 | 'x_frame_options' => 'DENY', // X-Frame-Options header value
32 | 'x_content_type_options' => 'nosniff', // X-Content-Type-Options header value
33 | 'x_xss_protection' => '1; mode=block', // X-XSS-Protection header value
34 | ],
35 |
36 | 'register' => [
37 | 'blade_directives' => false,
38 | 'request_macros' => false,
39 | 'translations' => true,
40 | 'validation_rules' => true,
41 | 'commands' => true,
42 | ],
43 | ];
44 |
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 | addMessage($value, 'debug');
12 | }
13 | }
14 | }
15 |
16 | if (! function_exists('get_db_query')) {
17 | function get_db_query(): string
18 | {
19 | if (! app()->bound('debugbar')) {
20 | return '';
21 | }
22 |
23 | /** @var \Barryvdh\Debugbar\LaravelDebugbar $debugbar */
24 | $debugbar = app('debugbar');
25 |
26 | try {
27 | $collector = $debugbar->getCollector('queries');
28 | } catch (Throwable $e) {
29 | return '';
30 | }
31 |
32 | $queries = $collector->collect();
33 | if (empty($queries['statements'])) {
34 | return '';
35 | }
36 |
37 | $query = end($queries['statements']);
38 |
39 | return $query['sql'];
40 | }
41 | }
42 |
43 | if (! function_exists('get_db_queries')) {
44 | function get_db_queries(): array
45 | {
46 | if (! app()->bound('debugbar')) {
47 | return [];
48 | }
49 | /** @var \Barryvdh\Debugbar\LaravelDebugbar $debugbar */
50 | $debugbar = app('debugbar');
51 |
52 | try {
53 | $collector = $debugbar->getCollector('queries');
54 | } catch (Throwable $e) {
55 | return [];
56 | }
57 |
58 | $queries = $collector->collect();
59 | if (empty($queries['statements'])) {
60 | return [];
61 | }
62 |
63 | $list = [];
64 | foreach ($queries['statements'] as $query) {
65 | $list[] = $query['sql'];
66 | }
67 |
68 | return $list;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/Bootstrap.php:
--------------------------------------------------------------------------------
1 | getMockForTrait(UsesUuidAsPrimary::class);
18 |
19 | $uuidString = '055a40ec-94a1-4cd7-891f-11604409055e';
20 | $uuid = Uuid::fromString($uuidString);
21 | $uuidBinary = $uuid->getBytes();
22 |
23 | $this->assertTrue($mock->isUuidBinary($uuidBinary));
24 | }
25 |
26 | #[Test]
27 | public function it_should_check_uuid_is_invalid_binary(): void
28 | {
29 | $mock = $this->getMockForTrait(UsesUuidAsPrimary::class);
30 |
31 | $uuidString = '055a40ec-94a1-4cd7-891f-11604409055e';
32 | $uuid = Uuid::fromString($uuidString);
33 | $uuidBinary = $uuid->toString(); // Not binary
34 |
35 | $this->assertFalse($mock->isUuidBinary($uuidBinary));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Unit/Http/Requests/CustomRequest.php:
--------------------------------------------------------------------------------
1 | createValidator($rules, $attributes);
28 |
29 | try {
30 | $formRequest->validateResolved();
31 | } catch (ValidationException $e) {
32 | if (! empty($errorAttributes)) {
33 | $errors = Arr::dot($e->errors());
34 |
35 | foreach ($errorAttributes as $errorAttr) {
36 | $found = false;
37 | foreach ($errors as $error) {
38 | if (strpos($error, $errorAttr) !== false) {
39 | $found = true;
40 | break;
41 | }
42 | }
43 | $this->assertTrue($found, 'Failed asserting that validation errors contains "' . $errorAttr . '" string');
44 | }
45 | }
46 | }
47 |
48 | $this->assertInstanceOf(CustomRequest::class, $formRequest);
49 | }
50 |
51 | public static function provideData(): array
52 | {
53 | return [
54 | [
55 | [
56 | 'field1' => 'required',
57 | 'field2' => 'required',
58 | ],
59 | [
60 | 'field1' => 'Some Data 1',
61 | 'field2' => 'Some Data 2',
62 | 'field3' => 'Some Data 3',
63 | ],
64 | [
65 | 'field3',
66 | ],
67 | ],
68 | [
69 | [
70 | 'field1' => 'required',
71 | 'field2.subfield1' => 'required',
72 | 'field2.subfield2' => 'required',
73 | 'field2.subfield3' => 'required',
74 | ],
75 | [
76 | 'field1' => 'Some Data 1',
77 | 'field2' => [
78 | 'subfield1' => 'Some Data 2-1',
79 | 'subfield2' => 'Some Data 2-2',
80 | 'subfield3' => 'Some Data 2-3',
81 | 'subfield4' => 'Some Data 2-3',
82 | ],
83 | 'field3' => 'Some Data 3',
84 | ],
85 | [
86 | 'field2.subfield4',
87 | 'field3',
88 | ],
89 | ],
90 | [
91 | [
92 | 'field1' => 'required',
93 | 'field2.*.subfield1' => 'required',
94 | 'field2.*.subfield2' => 'required',
95 | 'field2.*.subfield3' => 'required',
96 | 'field3' => 'required',
97 | 'field4' => 'required',
98 | ],
99 | [
100 | 'field1' => 'Some Data 1',
101 | 'field2' => [
102 | [
103 | 'subfield1' => 'Some Data 2-1',
104 | 'subfield2' => 'Some Data 2-2',
105 | ],
106 | [
107 | 'subfield3' => 'Some Data 2-3',
108 | 'subfield4' => 'Some Data 2-3',
109 | ],
110 | ],
111 | 'field3' => 'Some Data 3',
112 | 'field4' => 'Some Data 4',
113 | 'field5' => 'Some Data 3',
114 | ],
115 | [
116 | 'field2.*.subfield4',
117 | 'field5',
118 | ],
119 | ],
120 | [
121 | [
122 | 'field1' => 'required',
123 | 'field2' => 'required',
124 | 'field2.*' => 'required',
125 | ],
126 | [
127 | 'field1' => 'Some Data 1',
128 | 'field2' => [
129 | [
130 | 'subfield1' => 'Some Data 2-1',
131 | 'subfield2' => 'Some Data 2-2',
132 | ],
133 | [
134 | 'subfield3' => 'Some Data 2-3',
135 | 'subfield4' => 'Some Data 2-3',
136 | ],
137 | ],
138 | 'field3' => 'Some Data 3',
139 | ],
140 | [
141 | 'field3',
142 | ],
143 | ],
144 | ];
145 | }
146 |
147 | private function createValidator(array $rules, array $attributes): CustomRequest
148 | {
149 | /** @var \Mockery\MockInterface|\Tests\Unit\Http\Requests\CustomRequest $formRequest */
150 | $formRequest = Mockery::mock(CustomRequest::class)->makePartial();
151 | $formRequest->shouldReceive('rules')->andReturn($rules);
152 | $formRequest->setContainer($this->app);
153 | $formRequest->initialize($attributes);
154 | $formRequest->setValidator(Validator::make($formRequest->all(), $rules));
155 |
156 | app(Translator::class)->addLines([
157 | 'validation.restrict_extra_attributes' => 'The :attribute key is not allowed in the request body.',
158 | ], 'en');
159 |
160 | return $formRequest;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/tests/Unit/Http/Resources/ArrayResourceTest.php:
--------------------------------------------------------------------------------
1 | 1,
22 | 'aa2' => 2,
23 | 'aa3' => 3,
24 | ]);
25 |
26 | $request = app(Request::class);
27 | $response = $resource->additional(['custom' => '1'])->withResourceType('customType')->toResponse($request);
28 |
29 | $expected = '{"data":{"id":null,"type":"customType","attributes":{"aa":1,"aa2":2,"aa3":3}},"custom":"1"}';
30 |
31 | $this->assertInstanceOf(JsonResponse::class, $response);
32 | $this->assertSame($expected, $response->content());
33 | }
34 |
35 | #[Test]
36 | public function it_should_return_unecaped_json(): void
37 | {
38 | $resource = new ArrayResource([
39 | 'aa' => 'ერთი',
40 | 'aa2' => 'ორი',
41 | 'aa3' => 'სამი',
42 | ]);
43 |
44 | $request = app(Request::class);
45 | $response = $resource->additional(['custom' => '1'])->withResourceType('customType')->toResponse($request);
46 |
47 | $expected = '{"data":{"id":null,"type":"customType","attributes":{"aa":"ერთი","aa2":"ორი","aa3":"სამი"}},"custom":"1"}';
48 |
49 | $this->assertInstanceOf(JsonResponse::class, $response);
50 | $this->assertSame($expected, $response->content());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Unit/Http/Resources/ErrorResourceTest.php:
--------------------------------------------------------------------------------
1 | 1,
22 | 'aa2' => 2,
23 | 'aa3' => 3,
24 | ]);
25 |
26 | $request = app(Request::class);
27 | $response = $resource->additional(['custom' => '1'])->toResponse($request);
28 |
29 | $expected = '{"errors":{"general":{"aa":1,"aa2":2,"aa3":3}},"custom":"1"}';
30 |
31 | $this->assertInstanceOf(JsonResponse::class, $response);
32 | $this->assertSame($expected, $response->content());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Unit/Http/Resources/User.php:
--------------------------------------------------------------------------------
1 | id;
17 | }
18 |
19 | public function getName(): string
20 | {
21 | return $this->name;
22 | }
23 |
24 | public function getMail(): string
25 | {
26 | return $this->mail;
27 | }
28 |
29 | public function getHomeAddress(): string
30 | {
31 | return $this->home_address;
32 | }
33 |
34 | public function getCalculatedField(): int
35 | {
36 | return 7;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Unit/Http/Resources/UserResource.php:
--------------------------------------------------------------------------------
1 | 'name',
13 | 'mail' => 'mail',
14 | 'home_address' => 'homeAddress',
15 | 'calculated_field' => ['calculatedField' => 'getCalculatedField'],
16 | ];
17 |
18 | public function __construct(User $resource)
19 | {
20 | $this->resource = $resource;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Unit/Http/Resources/UserResourceWithHidden.php:
--------------------------------------------------------------------------------
1 | set('auth.simple', [
19 | 'enabled' => true,
20 | 'user' => 'testuser',
21 | 'password' => 'testpass',
22 | ]);
23 |
24 | $response = $this->call('GET', 'url1', [], [], [], []);
25 | $response->assertStatus(401);
26 | }
27 |
28 | #[Test]
29 | public function it_should_return_access_denied_on_wrong_credentials()
30 | {
31 | config()->set('auth.simple', [
32 | 'enabled' => true,
33 | 'user' => 'testuser',
34 | 'password' => 'testpass',
35 | ]);
36 |
37 | $response = $this->call('GET', 'url1', [], [], [], ['PHP_AUTH_USER' => 'testuser', 'PHP_AUTH_PW' => 'wrongpass']);
38 | $response->assertStatus(401);
39 | }
40 |
41 | #[Test]
42 | public function it_should_return_ok_on_disabled_auth()
43 | {
44 | config()->set('auth.simple', [
45 | 'enabled' => false,
46 | 'user' => 'testuser',
47 | 'password' => 'testpass',
48 | ]);
49 |
50 | $response = $this->call('GET', 'url1', [], [], [], []);
51 | $response->assertStatus(200);
52 | $response->assertSeeText('ok');
53 | }
54 |
55 | #[Test]
56 | public function it_should_return_ok_with_credentials()
57 | {
58 | config()->set('auth.simple', [
59 | 'enabled' => true,
60 | 'user' => 'testuser',
61 | 'password' => 'testpass',
62 | ]);
63 | $response = $this->call('GET', 'url1', [], [], [], ['PHP_AUTH_USER' => 'testuser', 'PHP_AUTH_PW' => 'testpass']);
64 | $response->assertStatus(200);
65 | $response->assertSeeText('ok');
66 | }
67 |
68 | protected function getEnvironmentSetUp($app)
69 | {
70 | /** @var \Illuminate\Routing\Router $router */
71 | $router = $app['router'];
72 |
73 | $router->get('url1', static function () {
74 | return 'ok';
75 | })->middleware(SimpleBasicAuth::class);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Unit/RedisTest.php:
--------------------------------------------------------------------------------
1 | createConnection('phpredis', [
21 | 'cluster' => false,
22 | 'default' => [
23 | 'host' => getenv('REDIS_HOST') ?: '127.0.0.1',
24 | 'port' => getenv('REDIS_PORT') ?: 6379,
25 | 'database' => 5,
26 | 'options' => [
27 | 'prefix' => 'lodash:',
28 | 'serializer' => REDIS::SERIALIZER_IGBINARY,
29 | ],
30 | 'timeout' => 0.5,
31 | 'read_timeout' => 1.5,
32 | 'serializer' => 'igbinary',
33 | ],
34 | ]);
35 | /** @var \Redis $client */
36 | $client = $redis->connection()->client();
37 |
38 | $data = ['name' => 'Georgia'];
39 | $redis->set('country', $data, null, 60);
40 |
41 | $this->assertInstanceOf(Redis::class, $client);
42 | $this->assertEquals($client->getOption(Redis::OPT_SERIALIZER), Redis::SERIALIZER_IGBINARY);
43 | $this->assertEquals($redis->get('country'), $data);
44 | }
45 |
46 | #[Test]
47 | public function it_should_set_custom_serializer_for_cluster()
48 | {
49 | $redis = $this->createConnection('phpredis', [
50 | 'clusters' => [
51 | 'options' => [
52 | 'lazy_connect' => true,
53 | 'connect_timeout' => 1,
54 | 'read_timeout' => 3,
55 | 'password' => getenv('REDIS_PASSWORD') ?: null,
56 | 'database' => 5,
57 | 'prefix' => 'lodash:',
58 | 'serializer' => REDIS::SERIALIZER_IGBINARY,
59 | ],
60 |
61 | 'default' => [
62 | [
63 | 'host' => getenv('REDIS_HOST') ?: '127.0.0.1',
64 | 'port' => getenv('REDIS_PORT') ?: 6379,
65 | ],
66 | ],
67 | ],
68 | ]);
69 | /** @var \RedisArray $client */
70 | $client = $redis->connection()->client();
71 |
72 | $data = ['name' => 'Georgia'];
73 | $redis->set('country2', $data, null, 60);
74 |
75 | $this->assertInstanceOf(RedisArray::class, $client);
76 | $this->assertEquals($redis->get('country2'), $data);
77 | foreach ($client->getOption(Redis::OPT_SERIALIZER) as $serializer) {
78 | $this->assertEquals($serializer, Redis::SERIALIZER_IGBINARY);
79 | }
80 | }
81 |
82 | private function createConnection(string $driver, array $config = []): RedisManager
83 | {
84 | if (version_compare($this->app->version(), '5.7', '<')) {
85 | $redis = new RedisManager($driver, $config);
86 | } else {
87 | $redis = new RedisManager($this->app, $driver, $config);
88 | }
89 |
90 | return $redis;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/tests/Unit/ServiceProviderTest.php:
--------------------------------------------------------------------------------
1 | 'clear-all',
20 | 'command.lodash.db.clear' => 'db:clear',
21 | 'command.lodash.db.dump' => 'db:dump',
22 | 'command.lodash.db.restore' => 'db:restore',
23 | 'command.lodash.log.clear' => 'log:clear',
24 | 'command.lodash.user.add' => 'user:add',
25 | 'command.lodash.user.password' => 'user:password',
26 | ];
27 |
28 | $registered = $this->app[Kernel::class]->all();
29 | foreach ($commands as $command => $name) {
30 | $this->assertTrue($this->app->bound($command));
31 | $this->assertContains($name, array_keys($registered));
32 | }
33 | }
34 |
35 | #[Test]
36 | public function check_if_request_has_macros()
37 | {
38 | $this->assertTrue(Request::hasMacro('getInt'));
39 | $this->assertTrue(Request::hasMacro('getBool'));
40 | $this->assertTrue(Request::hasMacro('getFloat'));
41 | $this->assertTrue(Request::hasMacro('getString'));
42 | }
43 |
44 | #[Test]
45 | public function check_blade_directives()
46 | {
47 | $directives = [
48 | 'datetime',
49 | 'plural',
50 | ];
51 |
52 | $registered = app('blade.compiler')->getCustomDirectives();
53 | foreach ($directives as $directive) {
54 | $this->assertContains($directive, array_keys($registered));
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/tests/Unit/Support/StrTest.php:
--------------------------------------------------------------------------------
1 | assertSame('0012345678', Str::addZeros($string, 10, 'left'));
22 | $this->assertSame('1234567800', Str::addZeros($string, 10, 'right'));
23 | }
24 |
25 | #[Test]
26 | public function format_balance(): void
27 | {
28 | $int = 12345;
29 |
30 | $this->assertSame('123.45', Str::formatBalance($int, 2));
31 | $this->assertSame('123.450', Str::formatBalance($int, 3));
32 | }
33 |
34 | #[Test]
35 | public function snake_case_to_pascal_case(): void
36 | {
37 | $string = 'Lorem_ipsum_dolores';
38 | $this->assertSame('LoremIpsumDolores', Str::snakeCaseToPascalCase($string));
39 | }
40 |
41 | #[Test]
42 | public function pascal_case_to_snake_case(): void
43 | {
44 | $string = 'LoremIpsumDolores';
45 | $this->assertSame('lorem_ipsum_dolores', Str::camelCaseToSnakeCase($string));
46 |
47 | $string = 'არჩევანისგარემოსუზრუნველყოფისსისტემა';
48 | $this->assertSame('არჩევანისგარემოსუზრუნველყოფისსისტემა', Str::camelCaseToSnakeCase($string));
49 | }
50 |
51 | #[Test]
52 | public function snake_case_to_camel_case(): void
53 | {
54 | $string = 'Lorem_ipsum_dolores';
55 | $this->assertSame('loremIpsumDolores', Str::snakeCaseToCamelCase($string));
56 | }
57 |
58 | #[Test]
59 | public function camel_case_to_snake_case(): void
60 | {
61 | $string = 'loremIpsumDolores';
62 | $this->assertSame('lorem_ipsum_dolores', Str::camelCaseToSnakeCase($string));
63 |
64 | $string = 'არჩევანისგარემოსუზრუნველყოფისსისტემა';
65 | $this->assertSame('არჩევანისგარემოსუზრუნველყოფისსისტემა', Str::camelCaseToSnakeCase($string));
66 | }
67 |
68 | #[Test]
69 | public function convert_spaces_to_dashes(): void
70 | {
71 | $string = 'Lorem Ipsum Dolores';
72 | $this->assertSame('Lorem-Ipsum-Dolores', Str::convertSpacesToDashes($string));
73 |
74 | $string = 'არჩევანის გარემოს უზრუნველყოფის სისტემა';
75 | $this->assertSame('არჩევანის-გარემოს-უზრუნველყოფის-სისტემა', Str::convertSpacesToDashes($string));
76 | }
77 |
78 | #[Test]
79 | public function limit_middle(): void
80 | {
81 | $string = 'არჩევანის გარემოს უზრუნველყოფის სისტემა';
82 |
83 | $this->assertSame('არჩევანის ...ის სისტემა', Str::limitMiddle($string, 20, '...'));
84 | $this->assertSame(23, mb_strlen(Str::limitMiddle($string, 20, '...')));
85 | }
86 |
87 | #[Test]
88 | public function hash(): void
89 | {
90 | $data = ['aa' => 1, 'bb' => 2, 'cc' => 3];
91 | $this->assertSame('899a999da95e9f021fc63c6af006933fd4dc3aa1', Str::hash($data));
92 |
93 | $data = new stdClass();
94 | $data->aaa = 1;
95 | $data->bbb = 2;
96 | $data->ccc = 3;
97 | $this->assertSame('41d162b72eab4e7cfb6bb853d651fbaa2ae0573b', Str::hash($data));
98 |
99 | $data = null;
100 | $this->assertSame('eef19c54306daa69eda49c0272623bdb5e2b341f', Str::hash($data));
101 | }
102 |
103 | #[Test]
104 | public function to_dot_notation(): void
105 | {
106 | $string = 'data[first][]';
107 | $this->assertSame('data.first[]', Str::toDotNotation($string));
108 |
109 | $string = 'data[first][second]';
110 | $this->assertSame('data.first.second', Str::toDotNotation($string));
111 |
112 | $string = 'data[first][second]third';
113 | $this->assertSame('data.first.secondthird', Str::toDotNotation($string));
114 |
115 | $string = 'data[first][second][0]';
116 | $this->assertSame('data.first.second.0', Str::toDotNotation($string));
117 | }
118 |
119 | #[Test]
120 | public function convert_to_utf8(): void
121 | {
122 | $data = 'hello žš, გამარჯობა';
123 | $this->assertSame('hello žš, გამარჯობა', Str::convertToUtf8($data));
124 |
125 | $data = 'Hírek';
126 | $this->assertSame('Hírek', Str::convertToUtf8($data));
127 |
128 | $data = 'H�rek';
129 | $this->assertSame('H�rek', Str::convertToUtf8($data));
130 |
131 | $data = "Fédération Camerounaise de Football\n";
132 | $this->assertSame('Fédération Camerounaise de Football', Str::convertToUtf8($data));
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/tests/Unit/TestCase.php:
--------------------------------------------------------------------------------
1 | 'User Name',
18 | 'name2' => 'User Name 2',
19 | 'relations:choices:5' => [
20 | 'choice_name' => 'Choice Name',
21 | 'choice_name2' => 'Choice Name 2',
22 | 'relations:groups:1' => [
23 | 'group_name' => 'Group Name',
24 | 'group_name2' => 'Group Name 2',
25 | 'relations:lecturers:2' => [
26 | 'lecturer_name' => 'Lecturer Name',
27 | 'lecturer_name2' => 'Lecturer Name 2',
28 | 'relations:settings:1' => [
29 | 'settings_name' => 'Settings Name',
30 | 'settings_name2' => 'Settings Name 2',
31 | ],
32 | ],
33 | ],
34 | ],
35 | 'relations:profiles:3' => [
36 | 'profile_name' => 'Profile Name',
37 | 'profile_name2' => 'Profile Name 2',
38 | 'relations:settings:1' => [
39 | 'settings_name' => 'Settings Name',
40 | 'settings_name2' => 'Settings Name 2',
41 | ],
42 | ],
43 | ];
44 |
45 | $parser = new Attributes($attributes);
46 |
47 | $this->assertEquals(
48 | [
49 | 'name' => 'User Name',
50 | 'name2' => 'User Name 2',
51 | ],
52 | $parser->getAttributes(),
53 | );
54 | $this->assertEquals(
55 | [
56 | 'name' => 'User Name',
57 | 'name2' => 'User Name 2',
58 | 'custom_attr' => 'Custom Attr for Merging',
59 | ],
60 | $parser->getAttributes(['custom_attr' => 'Custom Attr for Merging']),
61 | );
62 |
63 | $this->assertEquals(false, $parser->hasRelation('missing_relation'));
64 | $this->assertEquals(true, $parser->hasRelation('choices'));
65 |
66 | // Choices
67 | $relation = $parser->getRelation('choices');
68 | $this->assertEquals(
69 | [
70 | 'choice_name' => 'Choice Name',
71 | 'choice_name2' => 'Choice Name 2',
72 | ],
73 | $relation->getAttributes(),
74 | );
75 | $this->assertEquals(5, $relation->getCount());
76 |
77 | // Groups
78 | $relation = $relation->getRelation('groups');
79 | $this->assertEquals(
80 | [
81 | 'group_name' => 'Group Name',
82 | 'group_name2' => 'Group Name 2',
83 | ],
84 | $relation->getAttributes(),
85 | );
86 | $this->assertEquals(1, $relation->getCount());
87 |
88 | // Lecturers
89 | $relation = $relation->getRelation('lecturers');
90 | $this->assertEquals(
91 | [
92 | 'lecturer_name' => 'Lecturer Name',
93 | 'lecturer_name2' => 'Lecturer Name 2',
94 | ],
95 | $relation->getAttributes(),
96 | );
97 | $this->assertEquals(2, $relation->getCount());
98 |
99 | // Settings
100 | $relation = $relation->getRelation('settings');
101 | $this->assertEquals(
102 | [
103 | 'settings_name' => 'Settings Name',
104 | 'settings_name2' => 'Settings Name 2',
105 | ],
106 | $relation->getAttributes(),
107 | );
108 | $this->assertEquals(1, $relation->getCount());
109 |
110 | // Profiles
111 | $relations2 = $parser->getRelations();
112 | $this->assertEquals(
113 | [
114 | 'profile_name' => 'Profile Name',
115 | 'profile_name2' => 'Profile Name 2',
116 | ],
117 | $relations2['profiles']->getAttributes(),
118 | );
119 | $this->assertEquals(3, $relations2['profiles']->getCount());
120 |
121 | // Profiles
122 | $relations2 = $relations2['profiles']->getRelations();
123 | $this->assertEquals(
124 | [
125 | 'settings_name' => 'Settings Name',
126 | 'settings_name2' => 'Settings Name 2',
127 | ],
128 | $relations2['settings']->getAttributes(),
129 | );
130 | $this->assertEquals(1, $relations2['settings']->getCount());
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/tests/Unit/Testing/ItemStructuresProvider.php:
--------------------------------------------------------------------------------
1 | [
16 | 'uid',
17 | 'firstName',
18 | 'lastName',
19 | 'fullName',
20 | 'email',
21 | 'avatar',
22 | 'photoUrl',
23 | ],
24 | ];
25 |
26 | private array $user_profile_structure = [
27 | 'type',
28 | 'id',
29 | 'attributes' => [
30 | 'type',
31 | 'degree',
32 | ],
33 | ];
34 |
35 | private array $profile_status_structure = [
36 | 'type',
37 | 'id',
38 | 'attributes' => [
39 | 'status',
40 | 'message',
41 | ],
42 | ];
43 |
44 | // phpcs:enable
45 |
46 | public function getUserStructure(array $relations = []): array
47 | {
48 | $structure = $this->user_structure;
49 |
50 | DataStructuresBuilder::includeNestedRelations($this, $structure, $relations);
51 |
52 | return $structure;
53 | }
54 |
55 | public function getUserProfileStructure(array $relations = []): array
56 | {
57 | $structure = $this->user_profile_structure;
58 |
59 | DataStructuresBuilder::includeNestedRelations($this, $structure, $relations);
60 |
61 | return $structure;
62 | }
63 |
64 | public function getProfileStatusStructure(array $relations = []): array
65 | {
66 | $structure = $this->profile_status_structure;
67 |
68 | DataStructuresBuilder::includeNestedRelations($this, $structure, $relations);
69 |
70 | return $structure;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------