├── .DS_Store ├── .gitattributes ├── .github ├── .DS_Store ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .php-cs-fixer.cache ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── docs ├── .DS_Store └── api │ ├── .DS_Store │ ├── classes │ ├── Scrawler-Arca-Collection-CollectionInterface.html │ ├── Scrawler-Arca-Collection.html │ ├── Scrawler-Arca-Database.html │ ├── Scrawler-Arca-Exception-ConstructModelException.html │ ├── Scrawler-Arca-Exception-KeyNotFoundException.html │ ├── Scrawler-Arca-Facade-Database.html │ ├── Scrawler-Arca-Manager-ModelManager.html │ ├── Scrawler-Arca-Manager-RecordManager.html │ ├── Scrawler-Arca-Manager-TableManager.html │ ├── Scrawler-Arca-Model.html │ └── Scrawler-Arca-QueryBuilder.html │ ├── css │ ├── base.css │ ├── normalize.css │ └── template.css │ ├── files │ ├── src-collection-collectioninterface.html │ ├── src-collection.html │ ├── src-database.html │ ├── src-exception-constructmodelexception.html │ ├── src-exception-keynotfoundexception.html │ ├── src-facade-database.html │ ├── src-manager-modelmanager.html │ ├── src-manager-recordmanager.html │ ├── src-manager-tablemanager.html │ ├── src-model.html │ └── src-querybuilder.html │ ├── graphs │ └── classes.html │ ├── index.html │ ├── indices │ └── files.html │ ├── js │ ├── search.js │ └── searchIndex.js │ ├── namespaces │ ├── default.html │ ├── scrawler-arca-collection.html │ ├── scrawler-arca-exception.html │ ├── scrawler-arca-facade.html │ ├── scrawler-arca-manager.html │ ├── scrawler-arca.html │ └── scrawler.html │ ├── packages │ ├── Application.html │ └── default.html │ └── reports │ ├── deprecated.html │ ├── errors.html │ └── markers.html ├── phpstan.neon ├── phpunit.xml ├── rector.php ├── src ├── .DS_Store ├── Collection.php ├── Collection │ └── CollectionInterface.php ├── Config.php ├── Database.php ├── Exception │ ├── InvalidIdException.php │ ├── InvalidModelException.php │ └── KeyNotFoundException.php ├── Facade │ └── Database.php ├── Factory │ └── DatabaseFactory.php ├── Manager │ ├── ModelManager.php │ ├── RecordManager.php │ ├── TableConstraint.php │ ├── TableManager.php │ └── WriteManager.php ├── Model.php ├── QueryBuilder.php └── Traits │ └── Model │ ├── ArrayAccess.php │ ├── Getter.php │ ├── Iterator.php │ ├── Setter.php │ └── Stringable.php └── tests ├── Datasets └── DatabaseTest.php ├── Pest.php └── Unit ├── Database ├── DatabaseSaveTest.php └── DatabaseTest.php ├── FacadeTest.php ├── Factory └── DatabaseFactoryTest.php ├── Manager ├── ContstraintsTest.php └── TableManagerTest.php ├── Model ├── EagerLoadingTest.php ├── ExceptionTest.php ├── ModelTest.php ├── PropertyTest.php ├── RelationsTest.php ├── TraitsTest.php └── TypeTest.php └── QueryBuilder ├── EagerLoadingTest.php └── QueryBuilderTest.php /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrawler-labs/arca-orm/a6fd66f99ec8a2a15d2842b5bc471d97d7ff671b/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrawler-labs/arca-orm/a6fd66f99ec8a2a15d2842b5bc471d97d7ff671b/.github/.DS_Store -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "composer" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request,workflow_dispatch] 3 | jobs: 4 | arca-tests: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | operating-system: ['ubuntu-latest'] 9 | php-versions: ['8.3'] 10 | phpunit-versions: ['latest'] 11 | include: 12 | - operating-system: 'ubuntu-latest' 13 | php-versions: '8.3' 14 | services: 15 | mysql: 16 | image: mysql:latest 17 | env: 18 | MYSQL_DATABASE: test_database 19 | MYSQL_HOST: 127.0.0.1 20 | MYSQL_USER: admin 21 | MYSQL_PASSWORD: rootpass 22 | MYSQL_ROOT_PASSWORD: rootpass 23 | ports: 24 | - 3306:3306 25 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: '8.3' 31 | - name: Update Composer 32 | run: sudo composer self-update --no-interaction 33 | - name: Run Composer Install 34 | run: composer install --no-interaction 35 | - name: run tests 36 | run: vendor/bin/pest --coverage-clover ./clover.xml 37 | - name: run static analysis 38 | run: vendor/bin/phpstan analyse src --level 8 39 | - name: Upload to Codecov 40 | uses: codecov/codecov-action@v4 41 | with: 42 | token: ${{ secrets.CODECOV_TOKEN }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | .DS_Store 4 | /.phpunit.cache/ 5 | index.php 6 | test_result 7 | 8 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 9 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 10 | # composer.lock 11 | -------------------------------------------------------------------------------- /.php-cs-fixer.cache: -------------------------------------------------------------------------------- 1 | {"php":"8.3.17","version":"3.68.5","indent":" ","lineEnding":"\n","rules":{"align_multiline_comment":true,"backtick_to_shell_exec":true,"binary_operator_spaces":true,"blank_line_before_statement":{"statements":["return"]},"braces_position":{"allow_single_line_anonymous_functions":true,"allow_single_line_empty_anonymous_classes":true},"class_attributes_separation":{"elements":{"method":"one"}},"class_definition":{"single_line":true},"class_reference_name_casing":true,"clean_namespace":true,"concat_space":true,"declare_parentheses":true,"echo_tag_syntax":true,"empty_loop_body":{"style":"braces"},"empty_loop_condition":true,"fully_qualified_strict_types":true,"function_declaration":true,"general_phpdoc_tag_rename":{"replacements":{"inheritDocs":"inheritDoc"}},"global_namespace_import":{"import_classes":false,"import_constants":false,"import_functions":false},"include":true,"increment_style":true,"integer_literal_case":true,"lambda_not_used_import":true,"linebreak_after_opening_tag":true,"magic_constant_casing":true,"magic_method_casing":true,"method_argument_space":{"on_multiline":"ignore"},"native_function_casing":true,"native_type_declaration_casing":true,"no_alias_language_construct_call":true,"no_alternative_syntax":true,"no_binary_string":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":{"tokens":["attribute","case","continue","curly_brace_block","default","extra","parenthesis_brace_block","square_brace_block","switch","throw","use"]},"no_leading_namespace_whitespace":true,"no_mixed_echo_print":true,"no_multiline_whitespace_around_double_arrow":true,"no_null_property_initialization":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_superfluous_phpdoc_tags":{"allow_hidden_params":true,"remove_inheritdoc":true},"no_trailing_comma_in_singleline":true,"no_unneeded_braces":{"namespaces":true},"no_unneeded_control_parentheses":{"statements":["break","clone","continue","echo_print","others","return","switch_case","yield","yield_from"]},"no_unneeded_import_alias":true,"no_unset_cast":true,"no_unused_imports":true,"no_useless_concat_operator":true,"no_useless_nullsafe_operator":true,"no_whitespace_before_comma_in_array":true,"normalize_index_brace":true,"nullable_type_declaration":true,"nullable_type_declaration_for_default_null_value":true,"object_operator_without_whitespace":true,"operator_linebreak":{"only_booleans":true},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"alpha"},"ordered_types":{"null_adjustment":"always_last","sort_algorithm":"none"},"php_unit_fqcn_annotation":true,"php_unit_method_casing":true,"phpdoc_align":true,"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag_normalizer":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_order":{"order":["param","return","throws"]},"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_separation":{"groups":[["Annotation","NamedArgumentConstructor","Target"],["author","copyright","license"],["category","package","subpackage"],["property","property-read","property-write"],["deprecated","link","see","since"]]},"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_tag_type":{"tags":{"inheritDoc":"inline"}},"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_trim_consecutive_blank_line_separation":true,"phpdoc_types":true,"phpdoc_types_order":{"null_adjustment":"always_last","sort_algorithm":"none"},"phpdoc_var_without_name":true,"semicolon_after_instruction":true,"simple_to_complex_string_variable":true,"single_class_element_per_statement":true,"single_import_per_statement":true,"single_line_comment_spacing":true,"single_line_comment_style":{"comment_types":["hash"]},"single_line_throw":true,"single_quote":true,"single_space_around_construct":true,"space_after_semicolon":{"remove_in_empty_for_expressions":true},"standardize_increment":true,"standardize_not_equals":true,"statement_indentation":{"stick_comment_to_next_continuous_control_statement":true},"switch_continue_to_break":true,"trailing_comma_in_multiline":{"after_heredoc":true,"elements":["array_destructuring","arrays","match","parameters"]},"trim_array_spaces":true,"type_declaration_spaces":true,"types_spaces":true,"unary_operator_spaces":true,"whitespace_after_comma_in_array":true,"yoda_style":true,"array_indentation":true,"array_syntax":true,"cast_spaces":true,"new_with_parentheses":{"anonymous_class":false},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"no_blank_lines_after_class_opening":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"return_type_declaration":true,"short_scalar_cast":true,"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_line_after_imports":true,"spaces_inside_parentheses":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"src\/Collection\/CollectionInterface.php":"fb0ea7e1c3eaddde3f2a4c5d0a7f4c48","src\/Traits\/Model\/ArrayAccess.php":"d38055282a82f53ec43871d9dae354f8","src\/Traits\/Model\/Getter.php":"c735fe1562b966f84b5d14d7b09b5a33","src\/Traits\/Model\/Setter.php":"53821011d871be2bb3049f78d65a1feb","src\/Traits\/Model\/Stringable.php":"317e7fdc392d409a9e293779d0b5a18d","src\/Traits\/Model\/Iterator.php":"664118a36822949f1257774e91294191","src\/Database.php":"fb4cd8b281f3cc44dc2b90aa9b6043a5","src\/Facade\/Database.php":"495507f0bed8a896a2811c4c749d9378","src\/Model.php":"2767245e754eb11b20f2adeb1dad4a36","src\/Config.php":"38f2547f007d7a16761056941d49a47e","src\/Manager\/RecordManager.php":"eecccfce49d58b90d006913c6ce77ea5","src\/Manager\/ModelManager.php":"085351c9679ca67c699d21f2b7b7bd88","src\/Manager\/WriteManager.php":"dbe227957122d62d05528e6f1e14c386","src\/Manager\/TableManager.php":"fa424f96d11317acf249d4921967dfc5","src\/Manager\/TableConstraint.php":"dd353943f55a66802fa61090d0ade9ce","src\/Collection.php":"329ccc5ce538c94548a489a08a3d8a7e","src\/QueryBuilder.php":"78dab3298fb2d3cd7eede0456b19a61e","src\/Exception\/InvalidModelException.php":"17a384cb5d0bd5aa4e958686adcd9210","src\/Exception\/InvalidIdException.php":"1500cb4298505f8c3a7002c20b0f0df1","src\/Exception\/KeyNotFoundException.php":"07af93dc973f3cd56197717b027fffdb","src\/Factory\/DatabaseFactory.php":"6f9062a84687e5d1594b82f6f68bf730"}} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Pranjal Pandey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

🚀 ARCA ORM

3 | GitHub Workflow Status  4 | Codecov  5 | Scrutinizer code quality (GitHub/Bitbucket)  6 | PHPStan Enabled 7 | Packagist Version (including pre-releases)  8 | GitHub License 9 |

10 | 🔥 Low code , Zero Configuration ORM that creates models, config, database and tables on the fly. 🔥
11 | 🇮🇳 Made in India 🇮🇳 12 |

13 | 14 | ![arca-orm](https://user-images.githubusercontent.com/7591484/170266248-62e23e46-241c-4063-93b8-772eb0de51b0.gif) 15 | 16 | Complete documentation can be found [here](http://component.scrawlerlabs.com/arca-orm) 17 |
18 |

19 | 20 | ## 🤔 Why use Arca Orm ? 21 | - Automatically creates tables and columns as you go 22 | - No configuration, just fire and forget 23 | - Save loads of time while working on database 24 | - Built upon stable foundation of Doctrine Dbal and extensively tested 25 | - Thanks to [loophp](https://github.com/loophp/collection) Arca comes with Lazy collection and tons of helper collection functions 26 | - Supports lots database platforms , you can see the complete list [here](https://component.scrawlerlabs.com/arca-orm/database/) 27 | - Supports concurrent queries and connection pooling with swoole and async with amphp. Check out integration docs [here](https://component.scrawlerlabs.com/arca-orm/swoole/) 28 |

29 | 30 | ## ❗Requirements 31 | - PHP 8.1 or greater 32 | - PHP PDO or other supported database adapter 33 | - Mysql, MariaDB, Sqlite or any other supported database. check the list [here](https://component.scrawlerlabs.com/arca-orm/database/) 34 |

35 | ## 💻 Installation 36 | You can install Arca ORM via Composer. If you don't have composer installed , you can download composer from [here](https://getcomposer.org/download/) 37 | 38 | ``` 39 | composer require scrawler/arca 40 | ``` 41 |
42 | 43 | ## 🏁 QuickStart 44 | 45 | ### ✨ Setup 46 | ```php 47 | 'YOUR_DB_NAME', 52 | 'user' => 'YOUR_DB_USER', 53 | 'password' => 'YOUR_DB_PASSWORD', 54 | 'host' => 'YOUR_DB_HOST', 55 | 'driver' => 'pdo_mysql', //You can use other supported driver this is the most basic mysql driver 56 | ); 57 | 58 | 59 | $db = \Scrawler\Arca\Facade\Database::connect($connectionParams); 60 | 61 | //If you dont want to use facade , directly build from factory 62 | $factory = \Scrawler\Arca\Factory\DatabaseFactory() 63 | $db = $factory->build($connectionParams) 64 | 65 | ``` 66 | For complete list of driver check [here](https://component.scrawlerlabs.com/arca-orm/database/) 67 | 68 | ### ✏️ CRUD 69 | ```php 70 | 71 | // Create new record 72 | // The below code will automatically create user table and store the record 73 | 74 | $user = $db->create('user'); 75 | $user->name = "Pranja Pandey"; 76 | $user->age = 24 77 | $user->gender = "male" 78 | $user->save() 79 | 80 | // Get record with id 1 81 | 82 | $user = $db->getOne('user',1); 83 | 84 | //Get all records 85 | 86 | $users = $db->get('user'); 87 | 88 | // Update a record 89 | $user = $db->getOne('user',1); 90 | $user->name = "Mr Pranjal"; 91 | $user->save(); 92 | 93 | // Delete a record 94 | $user = $db->getOne('user',1); 95 | $user->delete(); 96 | 97 | ``` 98 | For complete CRUD documentaion visit [here](https://component.scrawlerlabs.com/arca-orm/crud/) 99 | 100 | ### 🔎 Finding data with query 101 | ```php 102 | 103 | // Using where clause 104 | $users = $db->find('user') 105 | ->where('name = "Pranjal Pandey"') 106 | ->get(); 107 | // If where input in unsafe or user defined 108 | $name = "Pranjal" 109 | $users = $db->find('user') 110 | ->where('name = ?') 111 | ->setParameter(0,$name) 112 | ->get(); 113 | 114 | foreach ($users as $user){ 115 | // Some logic here 116 | } 117 | 118 | // Get only single record 119 | $users = $db->find('user') 120 | ->where('name = "Pranjal Pandey"') 121 | ->first(); 122 | 123 | // Using limit in query 124 | $users = $db->find('user') 125 | ->setFirstResult(10) 126 | ->setMaxResults(20); 127 | ->get() 128 | 129 | ``` 130 | For complete Query documentaion visit [here](https://component.scrawlerlabs.com/arca-orm/finding/) 131 | 132 |

133 | ## 👏 Supporters 134 | If you have reached here consider giving a star to help this project ❤️ 135 | [![Stargazers repo roster for @scrawler-labs/arca-orm](https://reporoster.com/stars/dark/notext/scrawler-labs/arca-orm)](https://github.com/scrawler-labs/arca-orm/stargazers) 136 |

137 | 138 | ## ✅ Roadmap 139 | Here is list of few things that i would like to add in upcoming release 140 | - [ ] Models should be extendible with custom models 141 | - [ ] Validations for custom models 142 | - [ ] Automatically create migrations when table is updated or created 143 | - [X] Support eager loading for relations 144 | - [X] Better documentaions 145 |

146 | 147 | ## 👍 Similar projects and inspiration 148 | - [Eloquent ORM](https://laravel.com/docs/5.0/eloquent) 149 | - [Redbean PHP](https://redbeanphp.com/index.php) 150 | - [Doctrine ORM](https://www.doctrine-project.org/projects/doctrine-orm/en/2.11/index.html) 151 |

152 | 153 | ## 📄 License 154 | Arca ORM is created by [Pranjal Pandey](https://www.github.com/ipranjal) and released under the [Apache 2.0 License](https://github.com/scrawler-labs/arca-orm/blob/main/LICENSE). 155 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scrawler/arca", 3 | "description": "Arca ORM by Scrawler", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Pranjal Pandey", 8 | "email": "pranjal@corpusvision.com", 9 | "homepage": "https://github.com/ipranjal", 10 | "role": "Owner" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "Scrawler\\Arca\\": "src/" 16 | } 17 | }, 18 | "require": { 19 | "php": ">=8.1.0", 20 | "doctrine/dbal": "^4.0", 21 | "loophp/collection": "^7.5", 22 | "ramsey/uuid": "^4.3", 23 | "thecodingmachine/safe": "^3.0", 24 | "dunglas/doctrine-json-odm": "^1.4", 25 | "php-di/php-di": "^7.0" 26 | }, 27 | "require-dev": { 28 | "phpstan/phpstan": "^2.0", 29 | "pestphp/pest": "^3.0", 30 | "pestphp/pest-plugin-faker": "^3.0", 31 | "thecodingmachine/phpstan-safe-rule": "^1.2", 32 | "rector/rector": "^2.0" 33 | }, 34 | "config": { 35 | "allow-plugins": { 36 | "pestphp/pest-plugin": true 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrawler-labs/arca-orm/a6fd66f99ec8a2a15d2842b5bc471d97d7ff671b/docs/.DS_Store -------------------------------------------------------------------------------- /docs/api/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrawler-labs/arca-orm/a6fd66f99ec8a2a15d2842b5bc471d97d7ff671b/docs/api/.DS_Store -------------------------------------------------------------------------------- /docs/api/css/template.css: -------------------------------------------------------------------------------- 1 | .phpdocumentor-summary { 2 | font-style: italic; 3 | } 4 | .phpdocumentor-description { 5 | margin-bottom: var(--spacing-md); 6 | } 7 | .phpdocumentor-element { 8 | position: relative; 9 | } 10 | 11 | .phpdocumentor .phpdocumentor-element__name { 12 | line-height: 1; 13 | } 14 | 15 | .phpdocumentor-element__package, 16 | .phpdocumentor-element__extends, 17 | .phpdocumentor-element__implements { 18 | display: block; 19 | font-size: var(--text-xxs); 20 | font-weight: normal; 21 | opacity: .7; 22 | } 23 | 24 | .phpdocumentor-element__package .phpdocumentor-breadcrumbs { 25 | display: inline; 26 | } 27 | 28 | .phpdocumentor-element:not(:last-child) { 29 | border-bottom: 1px solid var(--primary-color-lighten); 30 | padding-bottom: var(--spacing-lg); 31 | } 32 | 33 | .phpdocumentor-element.-deprecated .phpdocumentor-element__name { 34 | text-decoration: line-through; 35 | } 36 | 37 | .phpdocumentor-element__modifier { 38 | font-size: var(--text-xxs); 39 | padding: calc(var(--spacing-base-size) / 4) calc(var(--spacing-base-size) / 2); 40 | color: var(--text-color); 41 | background-color: var(--light-gray); 42 | border-radius: 3px; 43 | text-transform: uppercase; 44 | } 45 | .phpdocumentor-signature { 46 | display: inline-block; 47 | font-size: var(--text-sm); 48 | margin-bottom: var(--spacing-md); 49 | } 50 | 51 | .phpdocumentor-signature.-deprecated .phpdocumentor-signature__name { 52 | text-decoration: line-through; 53 | } 54 | .phpdocumentor-table-of-contents { 55 | } 56 | 57 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry { 58 | padding-top: var(--spacing-xs); 59 | margin-left: 2rem; 60 | display: flex; 61 | } 62 | 63 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry > a { 64 | flex: 0 1 auto; 65 | } 66 | 67 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry > span { 68 | flex: 1; 69 | white-space: nowrap; 70 | text-overflow: ellipsis; 71 | overflow: hidden; 72 | } 73 | 74 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry:after { 75 | content: ''; 76 | height: 12px; 77 | width: 12px; 78 | left: 16px; 79 | position: absolute; 80 | } 81 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-private:after { 82 | background: url('data:image/svg+xml;utf8,') no-repeat; 83 | } 84 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-protected:after { 85 | left: 13px; 86 | background: url('data:image/svg+xml;utf8,') no-repeat; 87 | } 88 | 89 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry:before { 90 | width: 1.25rem; 91 | height: 1.25rem; 92 | line-height: 1.25rem; 93 | background: transparent url('data:image/svg+xml;utf8,') no-repeat center center; 94 | content: ''; 95 | position: absolute; 96 | left: 0; 97 | border-radius: 50%; 98 | font-weight: 600; 99 | color: white; 100 | text-align: center; 101 | font-size: .75rem; 102 | margin-top: .2rem; 103 | } 104 | 105 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-method:before { 106 | content: 'M'; 107 | background-image: url('data:image/svg+xml;utf8,'); 108 | } 109 | 110 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-function:before { 111 | content: 'M'; 112 | background-image: url('data:image/svg+xml;utf8,'); 113 | } 114 | 115 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-property:before { 116 | content: 'P' 117 | } 118 | 119 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-constant:before { 120 | content: 'C'; 121 | background-color: transparent; 122 | background-image: url('data:image/svg+xml;utf8,'); 123 | } 124 | 125 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-class:before { 126 | content: 'C' 127 | } 128 | 129 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-interface:before { 130 | content: 'I' 131 | } 132 | 133 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-trait:before { 134 | content: 'T' 135 | } 136 | 137 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-namespace:before { 138 | content: 'N' 139 | } 140 | 141 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-package:before { 142 | content: 'P' 143 | } 144 | 145 | .phpdocumentor-table-of-contents .phpdocumentor-table-of-contents__entry.-enum:before { 146 | content: 'E' 147 | } 148 | 149 | .phpdocumentor-table-of-contents dd { 150 | font-style: italic; 151 | margin-left: 2rem; 152 | } 153 | .phpdocumentor-element-found-in { 154 | position: absolute; 155 | top: 0; 156 | right: 0; 157 | font-size: var(--text-sm); 158 | color: gray; 159 | } 160 | 161 | .phpdocumentor-element-found-in .phpdocumentor-element-found-in__source { 162 | flex: 0 1 auto; 163 | display: inline-flex; 164 | } 165 | 166 | .phpdocumentor-element-found-in .phpdocumentor-element-found-in__source:after { 167 | width: 1.25rem; 168 | height: 1.25rem; 169 | line-height: 1.25rem; 170 | background: transparent url('data:image/svg+xml;utf8,') no-repeat center center; 171 | content: ''; 172 | left: 0; 173 | border-radius: 50%; 174 | font-weight: 600; 175 | text-align: center; 176 | font-size: .75rem; 177 | margin-top: .2rem; 178 | } 179 | .phpdocumentor-class-graph { 180 | width: 100%; height: 600px; border:1px solid black; overflow: hidden 181 | } 182 | 183 | .phpdocumentor-class-graph__graph { 184 | width: 100%; 185 | } 186 | .phpdocumentor-tag-list__definition { 187 | display: flex; 188 | } 189 | 190 | .phpdocumentor-tag-link { 191 | margin-right: var(--spacing-sm); 192 | } 193 | -------------------------------------------------------------------------------- /docs/api/files/src-collection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |

Documentation

28 | 29 | 32 | 42 | 43 | 47 |
48 | 49 |
50 |
51 | 52 | 55 | 89 | 90 |
91 |
    92 |
93 | 94 |
95 |

Collection.php

96 | 97 | 98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 |

106 | Interfaces, Classes, Traits and Enums 107 | 108 |

109 | 110 |
111 | 112 |
Collection
113 |
Extension of LoopPHP collection
114 | 115 | 116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 |
127 |
128 |
129 |

Search results

130 | 131 |
132 |
133 |
    134 |
    135 |
    136 |
    137 |
    138 |
    139 | 140 | 141 |
    142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/api/files/src-database.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
    27 |

    Documentation

    28 | 29 | 32 | 42 | 43 | 47 |
    48 | 49 |
    50 |
    51 | 52 | 55 | 89 | 90 |
    91 |
      92 |
    93 | 94 |
    95 |

    Database.php

    96 | 97 | 98 |
    99 | 100 | 101 | 102 | 103 | 104 | 105 |

    106 | Interfaces, Classes, Traits and Enums 107 | 108 |

    109 | 110 |
    111 | 112 |
    Database
    113 |
    Class that manages all interaction with database
    114 | 115 | 116 |
    117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
    126 |
    127 |
    128 |
    129 |

    Search results

    130 | 131 |
    132 |
    133 |
      134 |
      135 |
      136 |
      137 |
      138 |
      139 | 140 | 141 |
      142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/api/files/src-facade-database.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
      27 |

      Documentation

      28 | 29 | 32 | 42 | 43 | 47 |
      48 | 49 |
      50 |
      51 | 52 | 55 | 89 | 90 |
      91 |
        92 |
      93 | 94 |
      95 |

      Database.php

      96 | 97 | 98 |
      99 | 100 | 101 | 102 | 103 | 104 | 105 |

      106 | Interfaces, Classes, Traits and Enums 107 | 108 |

      109 | 110 |
      111 | 112 |
      Database
      113 |
      114 | 115 | 116 |
      117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
      126 |
      127 |
      128 |
      129 |

      Search results

      130 | 131 |
      132 |
      133 |
        134 |
        135 |
        136 |
        137 |
        138 |
        139 | 140 | 141 |
        142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/api/files/src-manager-modelmanager.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
        27 |

        Documentation

        28 | 29 | 32 | 42 | 43 | 47 |
        48 | 49 |
        50 |
        51 | 52 | 55 | 89 | 90 |
        91 |
          92 |
        93 | 94 |
        95 |

        ModelManager.php

        96 | 97 | 98 |
        99 | 100 | 101 | 102 | 103 | 104 | 105 |

        106 | Interfaces, Classes, Traits and Enums 107 | 108 |

        109 | 110 |
        111 | 112 |
        ModelManager
        113 |
        Class for initializing and managing models
        114 | 115 | 116 |
        117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
        126 |
        127 |
        128 |
        129 |

        Search results

        130 | 131 |
        132 |
        133 |
          134 |
          135 |
          136 |
          137 |
          138 |
          139 | 140 | 141 |
          142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/api/files/src-manager-tablemanager.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
          27 |

          Documentation

          28 | 29 | 32 | 42 | 43 | 47 |
          48 | 49 |
          50 |
          51 | 52 | 55 | 89 | 90 |
          91 |
            92 |
          93 | 94 |
          95 |

          TableManager.php

          96 | 97 | 98 |
          99 | 100 | 101 | 102 | 103 | 104 | 105 |

          106 | Interfaces, Classes, Traits and Enums 107 | 108 |

          109 | 110 |
          111 | 112 |
          TableManager
          113 |
          Class resposible for creating and modifing table
          114 | 115 | 116 |
          117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
          126 |
          127 |
          128 |
          129 |

          Search results

          130 | 131 |
          132 |
          133 |
            134 |
            135 |
            136 |
            137 |
            138 |
            139 | 140 | 141 |
            142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/api/files/src-model.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
            27 |

            Documentation

            28 | 29 | 32 | 42 | 43 | 47 |
            48 | 49 |
            50 |
            51 | 52 | 55 | 89 | 90 |
            91 |
              92 |
            93 | 94 |
            95 |

            Model.php

            96 | 97 | 98 |
            99 | 100 | 101 | 102 | 103 | 104 | 105 |

            106 | Interfaces, Classes, Traits and Enums 107 | 108 |

            109 | 110 |
            111 | 112 |
            Model
            113 |
            Model class that represents single record in database
            114 | 115 | 116 |
            117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
            126 |
            127 |
            128 |
            129 |

            Search results

            130 | 131 |
            132 |
            133 |
              134 |
              135 |
              136 |
              137 |
              138 |
              139 | 140 | 141 |
              142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/api/files/src-querybuilder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
              27 |

              Documentation

              28 | 29 | 32 | 42 | 43 | 47 |
              48 | 49 |
              50 |
              51 | 52 | 55 | 89 | 90 |
              91 |
                92 |
              93 | 94 |
              95 |

              QueryBuilder.php

              96 | 97 | 98 |
              99 | 100 | 101 | 102 | 103 | 104 | 105 |

              106 | Interfaces, Classes, Traits and Enums 107 | 108 |

              109 | 110 |
              111 | 112 |
              QueryBuilder
              113 |
              Extended implementation of \Doctrine\DBAL\Query\QueryBuilder
              114 | 115 | 116 |
              117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
              126 |
              127 |
              128 |
              129 |

              Search results

              130 | 131 |
              132 |
              133 |
                134 |
                135 |
                136 |
                137 |
                138 |
                139 | 140 | 141 |
                142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /docs/api/graphs/classes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
                16 |

                Documentation

                17 | 18 | 21 | 31 | 32 | 36 |
                37 | 38 |
                39 |
                40 | 41 | 44 | 78 | 79 |
                80 |
                81 | 82 |
                83 | 91 |
                92 |
                93 |
                94 |

                Search results

                95 | 96 |
                97 |
                98 |
                  99 |
                  100 |
                  101 |
                  102 |
                  103 |
                  104 | 105 | 106 |
                  107 | 108 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
                  27 |

                  Documentation

                  28 | 29 | 32 | 42 | 43 | 47 |
                  48 | 49 |
                  50 |
                  51 | 52 | 55 | 89 | 90 |
                  91 |

                  Documentation

                  92 | 93 | 94 |

                  95 | Packages 96 | 97 |

                  98 | 99 |
                  100 |
                  Application
                  101 |
                  102 | 103 |

                  104 | Namespaces 105 | 106 |

                  107 | 108 |
                  109 |
                  Scrawler
                  110 |
                  111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
                  119 |
                  120 |
                  121 |

                  Search results

                  122 | 123 |
                  124 |
                  125 |
                    126 |
                    127 |
                    128 |
                    129 |
                    130 |
                    131 | 132 | 133 |
                    134 | 135 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/api/namespaces/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
                    27 |

                    Documentation

                    28 | 29 | 32 | 42 | 43 | 47 |
                    48 | 49 |
                    50 |
                    51 | 52 | 55 | 89 | 90 |
                    91 |
                      92 |
                    93 | 94 |
                    95 |

                    API Documentation

                    96 | 97 | 98 |

                    99 | Namespaces 100 | 101 |

                    102 | 103 |
                    104 |
                    Scrawler
                    105 |
                    106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
                    114 |
                    115 |
                    116 |
                    117 |

                    Search results

                    118 | 119 |
                    120 |
                    121 |
                      122 |
                      123 |
                      124 |
                      125 |
                      126 |
                      127 | 128 | 129 |
                      130 | 131 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/api/namespaces/scrawler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
                      27 |

                      Documentation

                      28 | 29 | 32 | 42 | 43 | 47 |
                      48 | 49 |
                      50 |
                      51 | 52 | 55 | 89 | 90 |
                      91 |
                        92 |
                      93 | 94 |
                      95 |

                      Scrawler

                      96 | 97 | 98 |

                      99 | Namespaces 100 | 101 |

                      102 | 103 |
                      104 |
                      Arca
                      105 |
                      106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
                      114 |
                      115 |
                      116 |
                      117 |

                      Search results

                      118 | 119 |
                      120 |
                      121 |
                        122 |
                        123 |
                        124 |
                        125 |
                        126 |
                        127 | 128 | 129 |
                        130 | 131 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/api/packages/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
                        27 |

                        Documentation

                        28 | 29 | 32 | 42 | 43 | 47 |
                        48 | 49 |
                        50 |
                        51 | 52 | 55 | 89 | 90 |
                        91 |
                          92 |
                        93 | 94 |
                        95 |

                        API Documentation

                        96 | 97 |

                        98 | Packages 99 | 100 |

                        101 | 102 |
                        103 |
                        Application
                        104 |
                        105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
                        114 |
                        115 |
                        116 |
                        117 |

                        Search results

                        118 | 119 |
                        120 |
                        121 |
                          122 |
                          123 |
                          124 |
                          125 |
                          126 |
                          127 | 128 | 129 |
                          130 | 131 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /docs/api/reports/deprecated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation » Deprecated elements 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
                          28 |

                          Documentation

                          29 | 30 | 33 | 43 | 44 | 48 |
                          49 | 50 |
                          51 |
                          52 | 53 | 56 | 90 | 91 |
                          92 | 95 | 96 |
                          97 |

                          Deprecated

                          98 | 99 | 100 |
                          101 | No deprecated elements have been found in this project. 102 |
                          103 |
                          104 |
                          105 |
                          106 |
                          107 |

                          Search results

                          108 | 109 |
                          110 |
                          111 |
                            112 |
                            113 |
                            114 |
                            115 |
                            116 |
                            117 | 118 | 119 |
                            120 | 121 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /docs/api/reports/errors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation » Compilation errors 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
                            28 |

                            Documentation

                            29 | 30 | 33 | 43 | 44 | 48 |
                            49 | 50 |
                            51 |
                            52 | 53 | 56 | 90 | 91 |
                            92 | 95 | 96 |
                            97 |

                            Errors

                            98 | 99 | 100 |
                            No errors have been found in this project.
                            101 | 102 |
                            103 |
                            104 |
                            105 |
                            106 |

                            Search results

                            107 | 108 |
                            109 |
                            110 |
                              111 |
                              112 |
                              113 |
                              114 |
                              115 |
                              116 | 117 | 118 |
                              119 | 120 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/api/reports/markers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation » Markers 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
                              28 |

                              Documentation

                              29 | 30 | 33 | 43 | 44 | 48 |
                              49 | 50 |
                              51 |
                              52 | 53 | 56 | 90 | 91 |
                              92 | 95 | 96 |
                              97 |

                              Markers

                              98 | 99 |
                              100 | No markers have been found in this project. 101 |
                              102 | 103 |
                              104 |
                              105 |
                              106 |
                              107 |

                              Search results

                              108 | 109 |
                              110 |
                              111 |
                                112 |
                                113 |
                                114 |
                                115 |
                                116 |
                                117 | 118 | 119 |
                                120 | 121 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - phar://phpstan.phar/conf/bleedingEdge.neon 3 | - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon 4 | 5 | parameters: 6 | ignoreErrors: 7 | - 8 | identifier: missingType.generics -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests 6 | 7 | 8 | 9 | 10 | 11 | ./app 12 | ./src 13 | 14 | 15 | ./src/Collection.php 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 9 | __DIR__ . '/src', 10 | __DIR__ . '/tests', 11 | ]) 12 | // uncomment to reach your current PHP version 13 | ->withPhpSets(php83:true) 14 | ->withAttributesSets() 15 | ->withPreparedSets(deadCode:true,typeDeclarations:true,codeQuality:true); 16 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrawler-labs/arca-orm/a6fd66f99ec8a2a15d2842b5bc471d97d7ff671b/src/.DS_Store -------------------------------------------------------------------------------- /src/Collection.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca; 13 | 14 | use loophp\collection\Collection as LoopCollection; 15 | use loophp\collection\Contract\Collection as LoopCollectionInterface; 16 | use Scrawler\Arca\Collection\CollectionInterface; 17 | 18 | /** 19 | * Extension of LoopPHP collection. 20 | * 21 | * @template TKey 22 | * @template T 23 | */ 24 | final class Collection implements CollectionInterface 25 | { 26 | /** 27 | * @var LoopCollectionInterface 28 | */ 29 | private readonly LoopCollectionInterface $collection; 30 | 31 | private function __construct(?LoopCollectionInterface $collection = null) 32 | { 33 | $this->collection = $collection ?? LoopCollection::empty(); 34 | } 35 | 36 | /** 37 | * /** 38 | * 39 | * @template UKey 40 | * @template U 41 | * 42 | * @param iterable $iterable 43 | */ 44 | public static function fromIterable(iterable $iterable): self 45 | { 46 | return new self(LoopCollection::fromIterable($iterable)); 47 | } 48 | 49 | /** 50 | * @return array 51 | */ 52 | public function jsonSerialize(): array 53 | { 54 | return $this->all(false); 55 | } 56 | 57 | public function __toString(): string 58 | { 59 | return $this->toString(); 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function toArray(): array 66 | { 67 | $toArray = static fn ($val) => $val->toArray(); 68 | 69 | return $this->map($toArray)->jsonSerialize(); 70 | } 71 | 72 | public function toString(): string 73 | { 74 | return \Safe\json_encode($this->toArray()); 75 | } 76 | 77 | /** 78 | * @return array 79 | */ 80 | public function all(bool $normalize = false): array 81 | { 82 | return $this->collection->all($normalize); 83 | } 84 | 85 | public function apply(callable $callbacks): Collection 86 | { 87 | return new self($this->collection->apply($callbacks)); 88 | } 89 | 90 | /** 91 | * @param mixed[] $other 92 | */ 93 | public function equals(iterable $other): bool 94 | { 95 | return $this->collection->equals($other); 96 | } 97 | 98 | public function first(): ?Model 99 | { 100 | return $this->collection->first(); 101 | } 102 | 103 | public function init(): Collection 104 | { 105 | return new self($this->collection->init()); 106 | } 107 | 108 | public function inits(): Collection 109 | { 110 | return new self($this->collection->inits()); 111 | } 112 | 113 | /** 114 | * @param array|\Traversable $sources 115 | */ 116 | public function merge(iterable ...$sources): Collection 117 | { 118 | return new self($this->collection->merge(...$sources)); 119 | } 120 | 121 | public function map(callable $callback): Collection 122 | { 123 | return new self($this->collection->map($callback)); 124 | } 125 | 126 | /** 127 | * @param callable(T, TKey, iterable): bool ...$callbacks 128 | */ 129 | public function filter(callable ...$callbacks): Collection 130 | { 131 | return new self($this->collection->filter(...$callbacks)); 132 | } 133 | 134 | public function count(): int 135 | { 136 | return iterator_count(iterator: $this); 137 | } 138 | 139 | /** 140 | * @param callable(T, TKey, iterable): bool ...$callbacks 141 | */ 142 | public function find(mixed $default = null, callable ...$callbacks): mixed 143 | { 144 | return $this->collection->find($default, ...$callbacks); 145 | } 146 | 147 | public function limit(int $count = -1, int $offset = 0): Collection 148 | { 149 | return new self($this->collection->limit($count, $offset)); 150 | } 151 | 152 | public function last(): ?Model 153 | { 154 | return $this->collection->last(); 155 | } 156 | 157 | public function getIterator(): \Iterator 158 | { 159 | yield from $this->collection->getIterator(); 160 | } 161 | 162 | public function current(int $index = 0, mixed $default = null): mixed 163 | { 164 | return $this->collection->current($index, $default); 165 | } 166 | 167 | /** 168 | * @return TKey|null 169 | */ 170 | public function key(int $index = 0) 171 | { 172 | return $this->collection->key($index); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Collection/CollectionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Collection; 13 | 14 | /** 15 | * @template TKey 16 | * @template T 17 | */ 18 | interface CollectionInterface extends \IteratorAggregate, \Countable 19 | { 20 | public function __toString(): string; 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function toArray(): array; 26 | 27 | public function toString(): string; 28 | 29 | public function getIterator(): \Iterator; 30 | 31 | public function apply(callable $callables): CollectionInterface; 32 | 33 | public function map(callable $callable): CollectionInterface; 34 | 35 | /** 36 | * @param mixed[] $array 37 | */ 38 | public static function fromIterable(iterable $array): CollectionInterface; 39 | 40 | /** 41 | * @return array 42 | */ 43 | public function jsonSerialize(): array; 44 | } 45 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca; 13 | 14 | /** 15 | * Class to store the configuration. 16 | */ 17 | class Config 18 | { 19 | public function __construct( 20 | private readonly bool $isUUID = false, 21 | private bool $isFrozen = false) 22 | { 23 | } 24 | 25 | /** 26 | * Get if the connection is using UUID. 27 | */ 28 | public function isUsingUUID(): bool 29 | { 30 | return $this->isUUID; 31 | } 32 | 33 | /** 34 | * Set if the connection is frozen. 35 | */ 36 | public function setFrozen(bool $isFrozen): void 37 | { 38 | $this->isFrozen = $isFrozen; 39 | } 40 | 41 | /** 42 | * Get if the connection is frozen. 43 | */ 44 | public function isFrozen(): bool 45 | { 46 | return $this->isFrozen; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Database.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Scrawler\Arca; 15 | 16 | use Doctrine\DBAL\Connection; 17 | use Doctrine\DBAL\Types\Type; 18 | use Dunglas\DoctrineJsonOdm\Serializer; 19 | use Dunglas\DoctrineJsonOdm\Type\JsonDocumentType; 20 | use Scrawler\Arca\Manager\ModelManager; 21 | use Scrawler\Arca\Manager\RecordManager; 22 | use Scrawler\Arca\Manager\WriteManager; 23 | use Symfony\Component\Serializer\Encoder\JsonEncoder; 24 | use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; 25 | use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; 26 | use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; 27 | use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; 28 | use Symfony\Component\Serializer\Normalizer\UidNormalizer; 29 | 30 | /** 31 | * Class that manages all interaction with database. 32 | */ 33 | final class Database 34 | { 35 | /** 36 | * Create a new Database instance. 37 | */ 38 | public function __construct( 39 | private readonly Connection $connection, 40 | private readonly RecordManager $recordManager, 41 | private readonly WriteManager $writeManager, 42 | private readonly ModelManager $modelManager, 43 | private readonly Config $config, 44 | ) { 45 | $this->registerJsonDocumentType(); 46 | } 47 | 48 | /** 49 | * Executes an SQL query and returns the number of row affected. 50 | * 51 | * @param array $params 52 | * 53 | * @return int|numeric-string 54 | */ 55 | public function exec(string $sql, array $params = []): int|string 56 | { 57 | return $this->connection->executeStatement($sql, $params); 58 | } 59 | 60 | /** 61 | * Returns array of data from SQL select statement. 62 | * 63 | * @param array $params 64 | * 65 | * @return array> 66 | */ 67 | public function getAll(string $sql, array $params = []): array 68 | { 69 | return $this->connection->executeQuery($sql, $params)->fetchAllAssociative(); 70 | } 71 | 72 | /** 73 | * Creates model from name. 74 | */ 75 | public function create(string $name): Model 76 | { 77 | return $this->modelManager->create($name); 78 | } 79 | 80 | /** 81 | * Save record to database. 82 | */ 83 | public function save(Model $model): mixed 84 | { 85 | return $this->writeManager->save($model); 86 | } 87 | 88 | /** 89 | * Delete record from database. 90 | */ 91 | public function delete(Model $model): mixed 92 | { 93 | return $this->recordManager->delete($model); 94 | } 95 | 96 | /** 97 | * Get collection of all records from table. 98 | */ 99 | public function get(string $table): Collection 100 | { 101 | return $this->recordManager->getAll($table); 102 | } 103 | 104 | /** 105 | * Get single record. 106 | */ 107 | public function getOne(string $table, mixed $id): ?Model 108 | { 109 | return $this->recordManager->getById($table, $id); 110 | } 111 | 112 | /** 113 | * Returns QueryBuilder to build query for finding data 114 | * Eg: db()->find('user')->where('active = 1')->get();. 115 | */ 116 | public function find(string $name): QueryBuilder 117 | { 118 | return $this->recordManager->find($name); 119 | } 120 | 121 | /** 122 | * Returns QueryBuilder to build query for finding data 123 | * Eg: db()->select('*')->from('user')->where('active = 1')->get();. 124 | */ 125 | public function select(string $expression): QueryBuilder 126 | { 127 | return $this->recordManager->select($expression); 128 | } 129 | 130 | /** 131 | * Freezes table for production. 132 | */ 133 | public function freeze(): void 134 | { 135 | $this->config->setFrozen(true); 136 | } 137 | 138 | /** 139 | * Helper function to unfreeze table. 140 | */ 141 | public function unfreeze(): void 142 | { 143 | $this->config->setFrozen(false); 144 | } 145 | 146 | /** 147 | * Checks if database is currently using uuid rather than id. 148 | */ 149 | public function isUsingUUID(): bool 150 | { 151 | return $this->config->isUsingUUID(); 152 | } 153 | 154 | /** 155 | * Returns the current connection. 156 | */ 157 | public function getConnection(): Connection 158 | { 159 | return $this->connection; 160 | } 161 | 162 | /** 163 | * Check if tables exist. 164 | * 165 | * @param array $tables 166 | */ 167 | public function tablesExist(array $tables): bool 168 | { 169 | return $this->connection 170 | ->createSchemaManager() 171 | ->tablesExist($tables); 172 | } 173 | 174 | /** 175 | * Check if table exists. 176 | */ 177 | public function tableExists(string $table): bool 178 | { 179 | return $this->connection 180 | ->createSchemaManager() 181 | ->tableExists($table); 182 | } 183 | 184 | /** 185 | * Register additional json_document type. 186 | * Note: storing and retrival of array is being tested 187 | * so ignoring this in coverage. 188 | */ 189 | private function registerJsonDocumentType(): void 190 | { 191 | // @codeCoverageIgnoreStart 192 | if (!Type::hasType('json_document')) { 193 | Type::addType('json_document', JsonDocumentType::class); 194 | // @phpstan-ignore-next-line 195 | Type::getType('json_document')->setSerializer( 196 | new Serializer([new BackedEnumNormalizer(), new UidNormalizer(), new DateTimeNormalizer(), new ArrayDenormalizer(), new ObjectNormalizer()], [new JsonEncoder()]) 197 | ); 198 | } 199 | // @codeCoverageIgnoreEnd 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Exception/InvalidIdException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Exception; 13 | 14 | class InvalidIdException extends \Exception 15 | { 16 | public function __construct() 17 | { 18 | parent::__construct('Force setting of id for model is not allowed'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Exception/InvalidModelException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Exception; 13 | 14 | class InvalidModelException extends \Exception 15 | { 16 | public function __construct() 17 | { 18 | parent::__construct("parameter passed to shared list or own list should be array of class \Arca\Model"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Exception/KeyNotFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Exception; 13 | 14 | class KeyNotFoundException extends \Exception 15 | { 16 | public function __construct() 17 | { 18 | parent::__construct('Key you are trying to access does not exist'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Facade/Database.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Scrawler\Arca\Facade; 15 | 16 | use Scrawler\Arca\Collection; 17 | use Scrawler\Arca\Connection; 18 | use Scrawler\Arca\Database as DB; 19 | use Scrawler\Arca\Factory\DatabaseFactory; 20 | use Scrawler\Arca\Model; 21 | use Scrawler\Arca\QueryBuilder; 22 | 23 | class Database 24 | { 25 | /** 26 | * Store the instance of current connection. 27 | */ 28 | private static DB $database; 29 | 30 | /** 31 | * Create a new Database instance. 32 | * 33 | * @param array $connectionParams 34 | */ 35 | public static function connect(array $connectionParams): DB 36 | { 37 | $factory = new DatabaseFactory(); 38 | self::$database = $factory->build($connectionParams); 39 | 40 | return self::$database; 41 | } 42 | 43 | /** 44 | * Get the instance of current connection. 45 | */ 46 | private static function getDB(): DB 47 | { 48 | return self::$database; 49 | } 50 | 51 | /** 52 | * Create a new model. 53 | */ 54 | public static function create(string $name): Model 55 | { 56 | return self::getDB()->create($name); 57 | } 58 | 59 | /** 60 | * Save a model. 61 | */ 62 | public static function get(string $table): Collection 63 | { 64 | return self::getDB()->get($table); 65 | } 66 | 67 | /** 68 | * Save a model. 69 | */ 70 | public static function getOne(string $table, mixed $id): ?Model 71 | { 72 | return self::getDB()->getOne($table, $id); 73 | } 74 | 75 | /** 76 | * Execure a raw sql query. 77 | * 78 | * @return int|numeric-string 79 | */ 80 | public static function exec(string $sql): int|string 81 | { 82 | return self::getDB()->exec($sql); 83 | } 84 | 85 | /** 86 | * Delete a model. 87 | */ 88 | public static function delete(Model $model): mixed 89 | { 90 | return self::getDB()->delete($model); 91 | } 92 | 93 | /** 94 | * QUery builder to find a model. 95 | */ 96 | public static function find(string $table): QueryBuilder 97 | { 98 | return self::getDB()->find($table); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Factory/DatabaseFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Factory; 13 | 14 | use Doctrine\DBAL\Connection; 15 | use Doctrine\DBAL\DriverManager; 16 | use Scrawler\Arca\Config; 17 | use Scrawler\Arca\Database; 18 | use Scrawler\Arca\Manager\ModelManager; 19 | 20 | class DatabaseFactory 21 | { 22 | private readonly \DI\Container $container; 23 | 24 | public function __construct(?\DI\Container $container = null) 25 | { 26 | $this->container = is_null($container) ? new \DI\Container() : $container; 27 | } 28 | 29 | /** 30 | * Create a new Database instance. 31 | * 32 | * @param array $connectionParams 33 | */ 34 | public function build(array $connectionParams): Database 35 | { 36 | $this->wireContainer($connectionParams); 37 | 38 | return $this->container->make(Database::class); 39 | } 40 | 41 | /** 42 | * Create a new Database instance. 43 | * 44 | * @param array $connectionParams 45 | */ 46 | public function wireContainer(array $connectionParams): void 47 | { 48 | $useUUID = $connectionParams['useUUID'] ?? false; 49 | 50 | $this->createConfig($useUUID); 51 | unset($connectionParams['useUUID']); 52 | $this->createConnection($connectionParams); 53 | $this->createModelManager(); 54 | } 55 | 56 | /** 57 | * Create a new connection. 58 | * 59 | * @param array $connectionParams 60 | */ 61 | private function createConnection(array $connectionParams): void 62 | { 63 | $this->container->set(Connection::class, fn (): Connection => DriverManager::getConnection($connectionParams)); 64 | } 65 | 66 | private function createModelManager(): void 67 | { 68 | $this->container->set(ModelManager::class, fn (): ModelManager => new ModelManager($this->container)); 69 | } 70 | 71 | private function createConfig(bool $useUUID): void 72 | { 73 | $this->container->set(Config::class, fn (): Config => new Config($useUUID)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Manager/ModelManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Scrawler\Arca\Manager; 15 | 16 | use Scrawler\Arca\Model; 17 | 18 | /** 19 | * Class for initializing and managing models. 20 | */ 21 | class ModelManager 22 | { 23 | public function __construct(private readonly \DI\Container $container) 24 | { 25 | } 26 | 27 | /** 28 | * Create a new model. 29 | */ 30 | public function create(string $name): Model 31 | { 32 | return $this->container->make(Model::class, ['table' => $name]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Manager/RecordManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Manager; 13 | 14 | use Doctrine\DBAL\Connection; 15 | use Ramsey\Uuid\Uuid; 16 | use Scrawler\Arca\Collection; 17 | use Scrawler\Arca\Config; 18 | use Scrawler\Arca\Model; 19 | use Scrawler\Arca\QueryBuilder; 20 | 21 | /** 22 | * Class responsible for manging single records. 23 | */ 24 | final class RecordManager 25 | { 26 | /** 27 | * Create RecordManager. 28 | */ 29 | public function __construct( 30 | private readonly Connection $connection, 31 | private readonly ModelManager $modelManager, 32 | private readonly Config $config, 33 | ) { 34 | } 35 | 36 | /** 37 | * Create a new record. 38 | */ 39 | public function insert(Model $model): mixed 40 | { 41 | if ($this->config->isUsingUUID()) { 42 | $model->set('id', Uuid::uuid4()->toString()); 43 | } 44 | $this->connection->insert($model->getName(), $model->getSelfProperties()); 45 | if ($this->config->isUsingUUID()) { 46 | return $model->get('id'); 47 | } 48 | 49 | return (int) $this->connection->lastInsertId(); 50 | } 51 | 52 | /** 53 | * Update a record. 54 | */ 55 | public function update(Model $model): mixed 56 | { 57 | $this->connection->update($model->getName(), $model->getSelfProperties(), ['id' => $model->getId()]); 58 | 59 | return $model->getId(); 60 | } 61 | 62 | /** 63 | * Delete a record. 64 | */ 65 | public function delete(Model $model): mixed 66 | { 67 | $this->connection->delete($model->getName(), ['id' => $model->getId()]); 68 | 69 | return $model->getId(); 70 | } 71 | 72 | /** 73 | * Get single record by id. 74 | */ 75 | public function getById(string $table, mixed $id): ?Model 76 | { 77 | $query = (new QueryBuilder($this->connection, $this->modelManager)) 78 | ->select('*') 79 | ->from($table, 't') 80 | ->where('t.id = ?') 81 | ->setParameter(0, $id); 82 | 83 | return $query->first(); 84 | } 85 | 86 | /** 87 | * Get all records. 88 | */ 89 | public function getAll(string $tableName): Collection 90 | { 91 | return (new QueryBuilder($this->connection, $this->modelManager)) 92 | ->select('*') 93 | ->from($tableName, 't') 94 | ->get(); 95 | } 96 | 97 | /** 98 | * get query builder from db. 99 | */ 100 | public function find(string $name): QueryBuilder 101 | { 102 | return (new QueryBuilder($this->connection, $this->modelManager)) 103 | ->select('*') 104 | ->from($name, 't'); 105 | } 106 | 107 | /** 108 | * get query builder from db. 109 | */ 110 | public function select(string $expression): QueryBuilder 111 | { 112 | return (new QueryBuilder($this->connection, $this->modelManager)) 113 | ->select($expression); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Manager/TableConstraint.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Manager; 13 | 14 | /** 15 | * Class to store table constraints. 16 | */ 17 | class TableConstraint 18 | { 19 | public function __construct( 20 | private readonly string $foreignTableName, 21 | private readonly string $localColumnName, 22 | private readonly string $foreignColumnName, 23 | ) { 24 | } 25 | 26 | public function getForeignTableName(): string 27 | { 28 | return $this->foreignTableName; 29 | } 30 | 31 | public function getLocalColumnName(): string 32 | { 33 | return $this->localColumnName; 34 | } 35 | 36 | public function getForeignColumnName(): string 37 | { 38 | return $this->foreignColumnName; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Manager/TableManager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Manager; 13 | 14 | use Doctrine\DBAL\Connection; 15 | use Doctrine\DBAL\Schema\AbstractSchemaManager; 16 | use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; 17 | use Doctrine\DBAL\Schema\Schema; 18 | use Doctrine\DBAL\Schema\Table; 19 | use Doctrine\DBAL\Types\Type; 20 | use Scrawler\Arca\Config; 21 | use Scrawler\Arca\Model; 22 | 23 | /** 24 | * Class resposible for creating and modifing table. 25 | */ 26 | final class TableManager 27 | { 28 | /** 29 | * Store the instance of SchemaManager. 30 | */ 31 | private readonly AbstractSchemaManager $manager; 32 | 33 | /** 34 | * Store the instance of Platform. 35 | */ 36 | private readonly \Doctrine\DBAL\Platforms\AbstractPlatform $platform; 37 | 38 | /** 39 | * create TableManager. 40 | */ 41 | public function __construct( 42 | private readonly Connection $connection, 43 | private readonly Config $config, 44 | ) { 45 | $this->manager = $this->connection->createSchemaManager(); 46 | $this->platform = $this->connection->getDatabasePlatform(); 47 | } 48 | 49 | /** 50 | * Creates a table schema from table instance. 51 | */ 52 | public function createTableSchema(Table $table): Schema 53 | { 54 | return new Schema([$table]); 55 | } 56 | 57 | /** 58 | * Get Table schema from existing table. 59 | */ 60 | public function getTableSchema(string $table): Schema 61 | { 62 | return new Schema([$this->manager->introspectTable($table)]); 63 | } 64 | 65 | /** 66 | * Get Table detail from existing table. 67 | */ 68 | public function getTable(string $table): Table 69 | { 70 | return $this->manager->introspectTable($table); 71 | } 72 | 73 | /** 74 | * Create table from model. 75 | * 76 | * @param array $constraints 77 | */ 78 | public function createTable(Model $model, array $constraints = []): Table 79 | { 80 | $table = new Table($model->getName()); 81 | $table = $this->addPrimaryKey($table); 82 | $types = $model->getTypes(); 83 | foreach (array_keys($model->getSelfProperties()) as $key) { 84 | $key_array = explode('_', $key); 85 | if ('id' != $key && 'id' === end($key_array)) { 86 | $table = $this->addIdColumn($table, $key); 87 | } elseif ('id' != $key) { 88 | $table->addColumn( 89 | $key, 90 | $types[$key], 91 | ['notnull' => false, 'comment' => $types[$key]] 92 | ); 93 | } 94 | } 95 | 96 | foreach ($constraints as $constraint) { 97 | $table->addForeignKeyConstraint( 98 | $constraint->getForeignTableName(), 99 | [$constraint->getLocalColumnName()], 100 | [$constraint->getForeignColumnName()], 101 | ['onUpdate' => 'CASCADE', 'onDelete' => 'CASCADE'] 102 | ); 103 | } 104 | 105 | return $table; 106 | } 107 | 108 | private function addPrimaryKey(Table $table): Table 109 | { 110 | if ($this->config->isUsingUUID()) { 111 | $table->addColumn( 112 | 'id', 113 | 'string', 114 | ['length' => 36, 'notnull' => true, 'comment' => 'string'] 115 | ); 116 | } else { 117 | $table->addColumn( 118 | 'id', 119 | 'integer', 120 | ['unsigned' => true, 'autoincrement' => true, 'comment' => 'integer'] 121 | ); 122 | } 123 | $table->setPrimaryKey(['id']); 124 | 125 | return $table; 126 | } 127 | 128 | /** 129 | * Add id column to table. 130 | */ 131 | private function addIdColumn(Table $table, string $key): Table 132 | { 133 | if ($this->config->isUsingUUID()) { 134 | $table->addColumn( 135 | $key, 136 | 'string', 137 | ['length' => 36, 'notnull' => true, 'comment' => 'string'] 138 | ); 139 | } else { 140 | $table->addColumn( 141 | $key, 142 | 'integer', 143 | ['unsigned' => true, 'notnull' => true, 'comment' => 'integer'] 144 | ); 145 | } 146 | 147 | return $table; 148 | } 149 | 150 | /** 151 | * Save table to database. 152 | */ 153 | public function saveTable(Table $table): void 154 | { 155 | $schema = $this->createTableSchema($table); 156 | $queries = $schema->toSql($this->platform); 157 | foreach ($queries as $query) { 158 | $this->connection->executeQuery($query); 159 | } 160 | } 161 | 162 | /** 163 | * Add missing column to existing table from given table. 164 | */ 165 | public function updateTable(string $table_name, Table $new_table): void 166 | { 167 | $comparator = $this->manager->createComparator(); 168 | $old_table = $this->getTable($table_name); 169 | $old_schema = $this->getTableSchema($table_name); 170 | 171 | $tableDiff = $comparator->compareTables($old_table, $new_table); 172 | $mod_table = $old_table; 173 | foreach ($tableDiff->getAddedColumns() as $column) { 174 | $mod_table->addColumn( 175 | $column->getName(), 176 | Type::getTypeRegistry()->lookupName($column->getType()), 177 | [ 178 | 'notnull' => $column->getNotnull(), 179 | 'comment' => $column->getComment(), 180 | 'default' => $column->getDefault(), 181 | ] 182 | ); 183 | } 184 | $new_schema = $this->createTableSchema($mod_table); 185 | $schemaDiff = $comparator->compareSchemas($old_schema, $new_schema); 186 | 187 | $queries = $this->platform->getAlterSchemaSQL($schemaDiff); 188 | 189 | foreach ($queries as $query) { 190 | $this->connection->executeQuery($query); 191 | } 192 | } 193 | 194 | /** 195 | * Check if table exists. 196 | */ 197 | public function tableExists(string $table_name): bool 198 | { 199 | try { 200 | $this->getTable($table_name); 201 | 202 | return true; 203 | } catch (TableDoesNotExist) { 204 | return false; 205 | } 206 | } 207 | 208 | /** 209 | * If table exist update it else create it. 210 | */ 211 | public function saveOrUpdateTable(string $table_name, Table $new_table): void 212 | { 213 | if ($this->tableExists($table_name)) { 214 | $this->updateTable($table_name, $new_table); 215 | 216 | return; 217 | } 218 | 219 | $this->saveTable($new_table); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca; 13 | 14 | use Doctrine\DBAL\Connection; 15 | use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder; 16 | use Doctrine\DBAL\Schema\AbstractSchemaManager; 17 | use Scrawler\Arca\Manager\ModelManager; 18 | 19 | /** 20 | * Extended implementation of \Doctrine\DBAL\Query\QueryBuilder. 21 | */ 22 | final class QueryBuilder extends DoctrineQueryBuilder 23 | { 24 | private string $table; 25 | /** 26 | * @var array 27 | */ 28 | private array $relations = []; 29 | 30 | private readonly AbstractSchemaManager $SchemaManager; 31 | 32 | public function __construct( 33 | Connection $connection, 34 | private readonly ModelManager $modelManager, 35 | ) { 36 | $this->SchemaManager = $connection->createSchemaManager(); 37 | parent::__construct($connection); 38 | } 39 | 40 | public function with(string $relation): QueryBuilder 41 | { 42 | $this->relations[] = $relation; 43 | 44 | return $this; 45 | } 46 | 47 | public function from(string $table, ?string $alias = null): QueryBuilder 48 | { 49 | $this->table = $table; 50 | 51 | return parent::from($table, $alias); 52 | } 53 | 54 | public function get(): Collection 55 | { 56 | if (!$this->SchemaManager->tableExists($this->table)) { 57 | return Collection::fromIterable([]); 58 | } 59 | $model = $this->modelManager->create($this->table); 60 | $relations = $this->relations; 61 | $this->relations = []; 62 | $results = $this->fetchAllAssociative(); 63 | 64 | return Collection::fromIterable($results) 65 | ->map(static fn ($value): Model => $model->setLoadedProperties($value)->with($relations)->setLoaded()); 66 | } 67 | 68 | public function first(): ?Model 69 | { 70 | if (!$this->SchemaManager->tableExists($this->table)) { 71 | return null; 72 | } 73 | $relations = $this->relations; 74 | $this->relations = []; 75 | $result = $this->fetchAssociative() ?: []; 76 | if ([] === $result) { 77 | return null; 78 | } 79 | 80 | return $this->modelManager->create($this->table)->setLoadedProperties($result)->with($relations)->setLoaded(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Traits/Model/ArrayAccess.php: -------------------------------------------------------------------------------- 1 | set($offset, $value); 13 | } 14 | } 15 | 16 | public function offsetExists($offset): bool 17 | { 18 | return $this->isset($offset); 19 | } 20 | 21 | public function offsetUnset($offset): void 22 | { 23 | $this->unset($offset); 24 | } 25 | 26 | public function offsetGet($offset): mixed 27 | { 28 | return $this->get($offset); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Traits/Model/Getter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Traits\Model; 13 | 14 | use Scrawler\Arca\Collection; 15 | 16 | /** 17 | * Getter trait to provide getter methods to the model. 18 | */ 19 | trait Getter 20 | { 21 | /** 22 | * Get current model Id or UUID. 23 | */ 24 | public function getId(): mixed 25 | { 26 | return $this->__meta['id']; 27 | } 28 | 29 | /** 30 | * Get current table name of model. 31 | */ 32 | public function getName(): string 33 | { 34 | return $this->table; 35 | } 36 | 37 | /** 38 | * Get all properties with relational models in array form. 39 | * 40 | * @return array 41 | */ 42 | public function getProperties(): array 43 | { 44 | return $this->__properties['all']; 45 | } 46 | 47 | /** 48 | * Get self properties without relations in array form. 49 | * 50 | * @return array 51 | */ 52 | public function getSelfProperties(): array 53 | { 54 | return $this->__properties['self']; 55 | } 56 | 57 | /** 58 | * Get all property types in array form. 59 | * 60 | * @return array 61 | */ 62 | public function getTypes(): array 63 | { 64 | return $this->__properties['type']; 65 | } 66 | 67 | /** 68 | * returns all relational models. 69 | */ 70 | public function getForeignModels(string $type): Collection 71 | { 72 | return is_null($this->__meta['foreign_models'][$type]) ? Collection::fromIterable([]) : $this->__meta['foreign_models'][$type]; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Traits/Model/Iterator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Traits\Model; 13 | 14 | use Scrawler\Arca\Collection; 15 | use Scrawler\Arca\Model; 16 | 17 | /** 18 | * Iterator trait to provide iterator methods to the model. 19 | */ 20 | trait Iterator 21 | { 22 | /** 23 | * Get all properties in array form. 24 | * 25 | * @return array 26 | */ 27 | public function toArray(): array 28 | { 29 | $props = $this->getProperties(); 30 | foreach ($props as $key => $value) { 31 | if ($value instanceof Model) { 32 | $props[$key] = $value->toArray(); 33 | } 34 | if ($value instanceof Collection) { 35 | $props[$key] = $value->toArray(); 36 | } 37 | } 38 | 39 | return $props; 40 | } 41 | 42 | public function getIterator(): \Traversable 43 | { 44 | return new \ArrayIterator($this->toArray()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Traits/Model/Setter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Traits\Model; 13 | 14 | use Doctrine\DBAL\Types\Type; 15 | use Scrawler\Arca\Model; 16 | 17 | /** 18 | * Setter trait to provide setter methods to the model. 19 | */ 20 | trait Setter 21 | { 22 | /** 23 | * Set all properties of model via array. 24 | * 25 | * @param array $properties 26 | */ 27 | public function setProperties(array $properties): Model 28 | { 29 | foreach ($properties as $key => $value) { 30 | $this->set($key, $value); 31 | } 32 | 33 | return $this; 34 | } 35 | 36 | /** 37 | * Set all properties of model loaded via database. 38 | * 39 | * @param array $properties 40 | */ 41 | public function setLoadedProperties(array $properties): Model 42 | { 43 | $this->__properties['self'] = $properties; 44 | $this->setLoadedAllProperties($properties); 45 | foreach (array_keys($properties) as $key) { 46 | $this->__properties['type'][$key] = $this->tableManager->getTable($this->table)->getColumn($key)->getComment(); 47 | } 48 | $this->__meta['id'] = $properties['id']; 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * get php value from database value. 55 | * 56 | * @param array $properties 57 | */ 58 | private function setLoadedAllProperties(array $properties): void 59 | { 60 | foreach ($properties as $key => $value) { 61 | $type = Type::getType($this->tableManager->getTable($this->table)->getColumn($key)->getComment()); 62 | $this->__properties['all'][$key] = $type->convertToPHPValue($value, $this->connection->getDatabasePlatform()); 63 | } 64 | } 65 | 66 | /** 67 | * call when model is loaded from database. 68 | */ 69 | public function setLoaded(): Model 70 | { 71 | $this->__meta['is_loaded'] = true; 72 | $this->__meta['id_error'] = false; 73 | 74 | return $this; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Traits/Model/Stringable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Scrawler\Arca\Traits\Model; 13 | 14 | /** 15 | * Stringable trait to provide string conversion methods to the model. 16 | */ 17 | trait Stringable 18 | { 19 | /** 20 | * Converts model into json object. 21 | */ 22 | public function toString(): string 23 | { 24 | return \Safe\json_encode($this->toArray()); 25 | } 26 | 27 | /** 28 | * Converts model into json object. 29 | */ 30 | public function __toString(): string 31 | { 32 | return $this->toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Datasets/DatabaseTest.php: -------------------------------------------------------------------------------- 1 | 'test_database', 23 | 'user' => 'admin', 24 | 'password' => 'rootpass', 25 | 'host' => '127.0.0.1', 26 | 'driver' => 'pdo_mysql', 27 | ]; 28 | if ($withUUID) { 29 | $config['useUUID'] = 'UUID' == $uuid ? true : false; 30 | } 31 | 32 | return $config; 33 | } 34 | 35 | function populateRandomUser($uuid = 'ID'): void 36 | { 37 | for ($i = 0; $i < 5; ++$i) { 38 | $user = db($uuid)->create('user'); 39 | $user->name = fake()->name(); 40 | $user->email = fake()->email(); 41 | $user->dob = fake()->date(); 42 | $user->age = fake()->randomNumber(2, false); 43 | $user->active = $i % 2; 44 | $user->address = fake()->streetAddress(); 45 | $user->save(); 46 | } 47 | } 48 | 49 | function createRandomUser($uuid = 'ID') 50 | { 51 | $user = db($uuid)->create('user'); 52 | $user->name = fake()->name(); 53 | $user->email = fake()->email(); 54 | $user->dob = fake()->date(); 55 | $user->age = fake()->randomNumber(2, false); 56 | $user->active = fake()->randomNumber(2, false) % 2; 57 | $user->address = fake()->streetAddress(); 58 | 59 | return $user->save(); 60 | } 61 | -------------------------------------------------------------------------------- /tests/Unit/Database/DatabaseTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 15 | }); 16 | afterAll(function (): void { 17 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 18 | }); 19 | 20 | afterEach(function (): void { 21 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 22 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 23 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 24 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS employee CASCADE; '); 25 | }); 26 | 27 | it(' checks db()->isUsingUUID() function ', function ($useUUID): void { 28 | if ('UUID' == $useUUID) { 29 | $this->assertTrue(db($useUUID)->isUsingUUID()); 30 | } else { 31 | $this->assertFalse(db($useUUID)->isUsingUUID()); 32 | } 33 | })->with('useUUID'); 34 | 35 | it(' checks db()->create() function ', function ($useUUID): void { 36 | $user = db($useUUID)->create('user'); 37 | $this->assertInstanceOf(Scrawler\Arca\Model::class, $user); 38 | })->with('useUUID'); 39 | 40 | it('checks if db()->getOne() gets single record', function ($useUUID): void { 41 | populateRandomUser($useUUID); 42 | $id = createRandomUser($useUUID); 43 | $user = db($useUUID)->getOne('user', $id); 44 | 45 | $stmt = db($useUUID)->getConnection()->prepare("SELECT * FROM user WHERE id = '".$id."'"); 46 | $result = json_encode($stmt->executeQuery()->fetchAssociative()); 47 | $this->assertJsonStringEqualsJsonString( 48 | $result, 49 | $user->toString() 50 | ); 51 | $this->assertIsString((string) $user); 52 | $this->assertInstanceOf(Scrawler\Arca\Model::class, $user); 53 | })->with('useUUID'); 54 | 55 | it('checks if db()->get() gets all record', function ($useUUID): void { 56 | populateRandomUser($useUUID); 57 | $users = db($useUUID)->get('user'); 58 | $stmt = db($useUUID)->getConnection()->prepare('SELECT * FROM user'); 59 | $result = json_encode($stmt->executeQuery()->fetchAllAssociative()); 60 | $this->assertJsonStringEqualsJsonString( 61 | $result, 62 | $users->toString() 63 | ); 64 | $this->assertInstanceOf(Scrawler\Arca\Collection::class, $users); 65 | })->with('useUUID'); 66 | 67 | it('checks if db()->find() returns Query Builder', function (): void { 68 | $this->assertInstanceOf(Scrawler\Arca\QueryBuilder::class, db()->find('user')); 69 | $this->assertInstanceOf(Scrawler\Arca\QueryBuilder::class, db()->find('user')->where('id = 2')); 70 | }); 71 | 72 | it('checks if db()->find() returns correct records', function (): void { 73 | populateRandomUser(); 74 | $users = db()->find('user')->where('active = 1')->get(); 75 | $stmt = db()->getConnection()->prepare('SELECT * FROM user WHERE active = 1'); 76 | $result = json_encode($stmt->executeQuery()->fetchAllAssociative()); 77 | $this->assertJsonStringEqualsJsonString( 78 | $result, 79 | $users->toString() 80 | ); 81 | $this->assertInstanceOf(Scrawler\Arca\Collection::class, $users); 82 | }); 83 | 84 | it('checks if all public instance of database files are correct', function (): void { 85 | $this->assertInstanceOf(Doctrine\DBAL\Connection::class, db()->getConnection()); 86 | }); 87 | 88 | it('checks db()->exec() function', function ($useUUID): void { 89 | $user = db($useUUID)->create('user'); 90 | $user->name = fake()->name(); 91 | $user->save(); 92 | if (db($useUUID)->isUsingUUID()) { 93 | db($useUUID)->exec("insert into user (id,name) values ('abc-jfke-dmsk','john')"); 94 | $id = 'abc-jfke-dmsk'; 95 | } else { 96 | db($useUUID)->exec("insert into user (name) values ('john')"); 97 | $id = 2; 98 | } 99 | 100 | $stmt = db($useUUID)->getConnection()->prepare("SELECT * FROM user where id = '".$id."'"); 101 | $result = $stmt->executeQuery()->fetchAssociative(); 102 | $this->assertEquals($result['name'], 'john'); 103 | })->with('useUUID'); 104 | 105 | it('checks db()->getAll() function', function ($useUUID): void { 106 | $user = db($useUUID)->create('user'); 107 | $user->name = fake()->name(); 108 | $user->save(); 109 | 110 | $stmt = db($useUUID)->getConnection()->prepare('SELECT * FROM user'); 111 | $result = $stmt->executeQuery()->fetchAllAssociative(); 112 | 113 | $actual = db($useUUID)->getAll('SELECT * FROM user'); 114 | 115 | $this->assertEquals($result, $actual); 116 | })->with('useUUID'); 117 | 118 | it('checks db()->delete() function', function ($useUUID): void { 119 | $user = db($useUUID)->create('user'); 120 | $user->name = fake()->name(); 121 | $id = $user->save(); 122 | $user->delete(); 123 | $stmt = db($useUUID)->getConnection()->prepare("SELECT * FROM user where id = '".$id."'"); 124 | $result = $stmt->executeQuery()->fetchAssociative(); 125 | $this->assertEmpty($result); 126 | })->with('useUUID'); 127 | 128 | it('checks db()->tableExists() function', function ($useUUID): void { 129 | $user = db($useUUID)->create('user'); 130 | $user->name = fake()->name(); 131 | $user->save(); 132 | 133 | $this->assertTrue(db($useUUID)->tableExists('user')); 134 | })->with('useUUID'); 135 | 136 | it('checks db()->tabelsExist() function', function ($useUUID): void { 137 | $user = db($useUUID)->create('user'); 138 | $user->name = fake()->name(); 139 | $user->save(); 140 | 141 | $emp = db($useUUID)->create('employee'); 142 | $emp->name = fake()->name(); 143 | $emp->save(); 144 | 145 | $this->assertTrue(db($useUUID)->tablesExist(['user', 'employee'])); 146 | })->with('useUUID'); 147 | 148 | it('checks frozen database', function ($useUUID): void { 149 | $user = db($useUUID)->create('user'); 150 | $user->name = fake()->name(); 151 | $user->save(); 152 | 153 | db($useUUID)->freeze(); 154 | $user = db($useUUID)->create('user'); 155 | $user->name = fake()->name(); 156 | $user->email = fake()->email(); 157 | 158 | expect(fn () => $user->save())->toThrow(InvalidFieldNameException::class); 159 | 160 | db($useUUID)->unfreeze(); 161 | })->with('useUUID'); 162 | 163 | it('checks for is UUID', function ($uuid): void { 164 | $val = db($uuid)->isUsingUUID(); 165 | $this->assertEquals($val, 'UUID' == $uuid); 166 | })->with('useUUID'); 167 | 168 | it('tests registration of json type', function (): void { 169 | $db = db(); 170 | $this->assertTrue(Type::hasType('json')); 171 | }); 172 | -------------------------------------------------------------------------------- /tests/Unit/FacadeTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 10 | }); 11 | afterAll(function (): void { 12 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 13 | }); 14 | 15 | afterEach(function (): void { 16 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 17 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 18 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 19 | }); 20 | 21 | it('tests DB::connect()', function (): void { 22 | $db = Scrawler\Arca\Facade\Database::connect(getConnectionParams()); 23 | $this->assertInstanceOf(Scrawler\Arca\Database::class, $db); 24 | }); 25 | 26 | it('tests DB::create()', function (): void { 27 | $user = Scrawler\Arca\Facade\Database::create('user'); 28 | 29 | $this->assertInstanceOf(Scrawler\Arca\Model::class, $user); 30 | }); 31 | 32 | it('tests DB::get()', function (): void { 33 | populateRandomUser(); 34 | $users = Scrawler\Arca\Facade\Database::get('user'); 35 | $stmt = db()->getConnection()->prepare('SELECT * FROM user'); 36 | $result = json_encode($stmt->executeQuery()->fetchAllAssociative()); 37 | $this->assertJsonStringEqualsJsonString( 38 | $result, 39 | $users->toString() 40 | ); 41 | $this->assertInstanceOf(Scrawler\Arca\Collection::class, $users); 42 | }); 43 | 44 | it('tests DB::getOne()', function (): void { 45 | db(); 46 | $id = createRandomUser(); 47 | $user = Scrawler\Arca\Facade\Database::getOne('user', $id); 48 | 49 | $stmt = db()->getConnection()->prepare("SELECT * FROM user WHERE id = '".$id."'"); 50 | $result = json_encode($stmt->executeQuery()->fetchAssociative()); 51 | $this->assertJsonStringEqualsJsonString( 52 | $result, 53 | $user->toString() 54 | ); 55 | $this->assertIsString((string) $user); 56 | $this->assertInstanceOf(Scrawler\Arca\Model::class, $user); 57 | }); 58 | 59 | it('test DB::exec()', function (): void { 60 | $user = Scrawler\Arca\Facade\Database::create('user'); 61 | $user->name = fake()->name(); 62 | $user->save(); 63 | 64 | Scrawler\Arca\Facade\Database::exec("insert into user (name) values ('john')"); 65 | $id = 2; 66 | 67 | $stmt = db()->getConnection()->prepare("SELECT * FROM user where id = '".$id."'"); 68 | $result = $stmt->executeQuery()->fetchAssociative(); 69 | $this->assertEquals($result['name'], 'john'); 70 | }); 71 | 72 | it('test DB::delete()', function (): void { 73 | $user = Scrawler\Arca\Facade\Database::create('user'); 74 | $user->name = fake()->name(); 75 | $id = $user->save(); 76 | Scrawler\Arca\Facade\Database::delete($user); 77 | $stmt = db()->getConnection()->prepare("SELECT * FROM user where id = '".$id."'"); 78 | $result = $stmt->executeQuery()->fetchAssociative(); 79 | $this->assertEmpty($result); 80 | }); 81 | 82 | it('test DB::find()', function (): void { 83 | populateRandomUser(); 84 | $users = Scrawler\Arca\Facade\Database::find('user')->where('active = 1')->get(); 85 | $stmt = db()->getConnection()->prepare('SELECT * FROM user WHERE active = 1'); 86 | $result = json_encode($stmt->executeQuery()->fetchAllAssociative()); 87 | $this->assertJsonStringEqualsJsonString( 88 | $result, 89 | $users->toString() 90 | ); 91 | $this->assertInstanceOf(Scrawler\Arca\Collection::class, $users); 92 | }); 93 | -------------------------------------------------------------------------------- /tests/Unit/Factory/DatabaseFactoryTest.php: -------------------------------------------------------------------------------- 1 | build(getConnectionParams($useUUID)); 15 | $this->assertInstanceOf(Scrawler\Arca\Database::class, $db); 16 | $this->assertEquals('UUID' == $useUUID, $db->isUsingUUID()); 17 | 18 | $db = $factory->build(getConnectionParams($useUUID, false)); 19 | $this->assertInstanceOf(Scrawler\Arca\Database::class, $db); 20 | $this->assertEquals(false, $db->isUsingUUID()); 21 | })->with('useUUID'); 22 | 23 | it('tests proper initialization of database with container provided', function ($useUUID): void { 24 | $factory = new Scrawler\Arca\Factory\DatabaseFactory(container: new DI\Container()); 25 | 26 | $db = $factory->build(getConnectionParams($useUUID)); 27 | $this->assertInstanceOf(Scrawler\Arca\Database::class, $db); 28 | $this->assertEquals('UUID' == $useUUID, $db->isUsingUUID()); 29 | 30 | $db = $factory->build(getConnectionParams($useUUID, false)); 31 | $this->assertInstanceOf(Scrawler\Arca\Database::class, $db); 32 | $this->assertEquals(false, $db->isUsingUUID()); 33 | })->with('useUUID'); 34 | -------------------------------------------------------------------------------- /tests/Unit/Manager/ContstraintsTest.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrawler-labs/arca-orm/a6fd66f99ec8a2a15d2842b5bc471d97d7ff671b/tests/Unit/Manager/ContstraintsTest.php -------------------------------------------------------------------------------- /tests/Unit/Manager/TableManagerTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 10 | }); 11 | afterAll(function (): void { 12 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 13 | }); 14 | 15 | afterEach(function (): void { 16 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 17 | }); 18 | 19 | it('tests table manger update functionality', function ($useUUID): void { 20 | createRandomUser($useUUID); 21 | 22 | $user = db($useUUID)->create('user'); 23 | $user->name = fake()->name(); 24 | $user->email = fake()->email(); 25 | $user->dob = fake()->date(); 26 | $user->age = fake()->randomNumber(2, false); 27 | $user->active = fake()->randomNumber(2, false) % 2; 28 | $user->address = fake()->streetAddress(); 29 | $user->rand = 'abc'; 30 | $user->randbo = 0 === fake()->randomNumber(2, false) % 2; 31 | $id = $user->save(); 32 | 33 | $table = db($useUUID)->getConnection()->createSchemaManager()->introspectTable('user'); 34 | $requiredTable = new Doctrine\DBAL\Schema\Table('user'); 35 | if (db($useUUID)->isUsingUUID()) { 36 | $requiredTable->addColumn('id', 'string', ['length' => 36, 'notnull' => true, 'comment' => 'string']); 37 | } else { 38 | $requiredTable->addColumn('id', 'integer', ['unsigned' => true, 'autoincrement' => true, 'comment' => 'integer']); 39 | } 40 | $requiredTable->addColumn('name', 'text', ['notnull' => false, 'comment' => 'text']); 41 | $requiredTable->addColumn('email', 'text', ['notnull' => false, 'comment' => 'text']); 42 | $requiredTable->addColumn('dob', 'text', ['notnull' => false, 'comment' => 'text']); 43 | $requiredTable->addColumn('age', 'integer', ['notnull' => false, 'comment' => 'integer']); 44 | $requiredTable->addColumn('active', 'integer', ['notnull' => false, 'comment' => 'integer']); 45 | $requiredTable->addColumn('address', 'text', ['notnull' => false, 'comment' => 'text']); 46 | $requiredTable->addColumn('rand', 'text', ['notnull' => false, 'comment' => 'text']); 47 | $requiredTable->addColumn('randbo', 'boolean', ['notnull' => false, 'comment' => 'boolean']); 48 | 49 | $requiredTable->setPrimaryKey(['id']); 50 | 51 | $actual = new Doctrine\DBAL\Schema\Schema([$table]); 52 | $required = new Doctrine\DBAL\Schema\Schema([$requiredTable]); 53 | $comparator = db()->getConnection()->createSchemaManager()->createComparator(); 54 | $diff = $comparator->compareSchemas($actual, $required); 55 | // print_r($diff->toSql(db()->platform)); 56 | 57 | $this->assertEmpty(db($useUUID)->getConnection()->getDatabasePlatform()->getAlterSchemaSQL($diff)); 58 | })->with('useUUID'); 59 | -------------------------------------------------------------------------------- /tests/Unit/Model/EagerLoadingTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 9 | }); 10 | afterAll(function (): void { 11 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 12 | }); 13 | 14 | afterEach(function (): void { 15 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user cascade; '); 16 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent cascade; '); 17 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user cascade; '); 18 | }); 19 | 20 | it('tests for model with() one-to-one relation', function ($useUUID): void { 21 | $user = db($useUUID)->create('user'); 22 | $user->name = fake()->name(); 23 | $user->email = fake()->email(); 24 | $user->dob = fake()->date(); 25 | $user->age = fake()->randomNumber(2, false); 26 | $user->address = fake()->streetAddress(); 27 | // $user->save(); 28 | 29 | $parent = db($useUUID)->create('parent'); 30 | $parent->name = fake()->name(); 31 | $parent->user = $user; 32 | 33 | $id = $parent->save(); 34 | 35 | $parent_retrived_with = db($useUUID)->getOne('parent', $id)->with(['user']); 36 | $parent_retrived = db($useUUID)->getOne('parent', $id); 37 | 38 | $user = db($useUUID)->find('user')->first(); 39 | 40 | $this->assertJsonStringNotEqualsJsonString( 41 | $parent_retrived_with->toString(), 42 | $parent_retrived->toString() 43 | ); 44 | $parent_retrived->user; 45 | $this->assertJsonStringEqualsJsonString( 46 | $parent_retrived_with->toString(), 47 | $parent_retrived->toString() 48 | ); 49 | })->with('useUUID'); 50 | 51 | it('tests for model with() one-to-many relation', function ($useUUID): void { 52 | $user1 = db($useUUID)->create('user'); 53 | $user1->name = fake()->name(); 54 | $user1->email = fake()->email(); 55 | $user1->dob = fake()->date(); 56 | $user1->age = fake()->randomNumber(2, false); 57 | $user1->address = fake()->streetAddress(); 58 | 59 | $user2 = db($useUUID)->create('user'); 60 | $user2->name = fake()->name(); 61 | $user2->email = fake()->email(); 62 | $user2->dob = fake()->date(); 63 | $user2->age = fake()->randomNumber(2, false); 64 | $user2->address = fake()->streetAddress(); 65 | // $user->save(); 66 | 67 | $parent = db($useUUID)->create('parent'); 68 | $parent->name = fake()->name(); 69 | $parent->ownUserList = [$user1, $user2]; 70 | 71 | $id = $parent->save(); 72 | 73 | $parent_retrived_with = db($useUUID)->getOne('parent', $id)->with(['ownUserList']); 74 | $parent_retrived = db($useUUID)->getOne('parent', $id); 75 | 76 | $user = db($useUUID)->find('user')->first(); 77 | 78 | $this->assertJsonStringNotEqualsJsonString( 79 | $parent_retrived_with->toString(), 80 | $parent_retrived->toString() 81 | ); 82 | $parent_retrived->ownUserList; 83 | $this->assertJsonStringEqualsJsonString( 84 | $parent_retrived_with->toString(), 85 | $parent_retrived->toString() 86 | ); 87 | })->with('useUUID'); 88 | 89 | it('tests for model with() many-to-many relation', function ($useUUID): void { 90 | $user1 = db($useUUID)->create('user'); 91 | $user1->name = fake()->name(); 92 | $user1->email = fake()->email(); 93 | $user1->dob = fake()->date(); 94 | $user1->age = fake()->randomNumber(2, false); 95 | $user1->address = fake()->streetAddress(); 96 | 97 | $user2 = db($useUUID)->create('user'); 98 | $user2->name = fake()->name(); 99 | $user2->email = fake()->email(); 100 | $user2->dob = fake()->date(); 101 | $user2->age = fake()->randomNumber(2, false); 102 | $user2->address = fake()->streetAddress(); 103 | // $user->save(); 104 | 105 | $parent1 = db($useUUID)->create('parent'); 106 | $parent1->name = fake()->name(); 107 | $parent1->sharedUserList = [$user1, $user2]; 108 | 109 | $parent2 = db($useUUID)->create('parent'); 110 | $parent2->name = fake()->name(); 111 | $parent2->sharedUserList = [$user1]; 112 | 113 | $id1 = $parent1->save(); 114 | $id2 = $parent2->save(); 115 | 116 | $parent_retrived_with = db($useUUID)->getOne('parent', $id1)->with(['sharedUserList']); 117 | $parent_retrived = db($useUUID)->getOne('parent', $id1); 118 | 119 | $this->assertJsonStringNotEqualsJsonString( 120 | $parent_retrived_with->toString(), 121 | $parent_retrived->toString() 122 | ); 123 | $parent_retrived->sharedUserList; 124 | $this->assertJsonStringEqualsJsonString( 125 | $parent_retrived_with->toString(), 126 | $parent_retrived->toString() 127 | ); 128 | 129 | $parent_retrived_with = db($useUUID)->getOne('parent', $id2)->with(['sharedUserList']); 130 | $parent_retrived = db($useUUID)->getOne('parent', $id2); 131 | 132 | $this->assertJsonStringNotEqualsJsonString( 133 | $parent_retrived_with->toString(), 134 | $parent_retrived->toString() 135 | ); 136 | $parent_retrived->sharedUserList; 137 | $this->assertJsonStringEqualsJsonString( 138 | $parent_retrived_with->toString(), 139 | $parent_retrived->toString() 140 | ); 141 | })->with('useUUID'); 142 | -------------------------------------------------------------------------------- /tests/Unit/Model/ExceptionTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 14 | }); 15 | afterAll(function (): void { 16 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 17 | }); 18 | 19 | afterEach(function (): void { 20 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 21 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 22 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 23 | }); 24 | 25 | it('check exception is thrown when non existent key is accessed', function ($useUUID): void { 26 | $id = createRandomUser($useUUID); 27 | $user = db($useUUID)->getOne('user', $id); 28 | $user->somekey; 29 | })->throws(Scrawler\Arca\Exception\KeyNotFoundException::class, 'Key you are trying to access does not exist')->with('useUUID'); 30 | 31 | it('checks exception is thrown when id is force set on a model', function ($useUUID): void { 32 | $user = db($useUUID)->create('user'); 33 | $user->id = 1; 34 | $user->name = fake()->name(); 35 | $user->email = fake()->email(); 36 | $user->save(); 37 | })->with('useUUID')->throws(Scrawler\Arca\Exception\InvalidIdException::class, 'Force setting of id for model is not allowed'); 38 | 39 | it('checks exception is thrown if share list is not array of model', function ($useUUID): void { 40 | $parent = db($useUUID)->create('parent'); 41 | $parent->name = fake()->name(); 42 | $parent->sharedUserList = ['test', 'test1']; 43 | $id = $parent->save(); 44 | })->with('useUUID')->throws(Scrawler\Arca\Exception\InvalidModelException::class); 45 | 46 | it('checks exception is thrown if own list is not array of model', function ($useUUID): void { 47 | $parent = db($useUUID)->create('parent'); 48 | $parent->name = fake()->name(); 49 | $parent->ownUserList = ['test', 'test1']; 50 | $id = $parent->save(); 51 | })->with('useUUID')->throws(Scrawler\Arca\Exception\InvalidModelException::class, 'parameter passed to shared list or own list should be array of class \Arca\Model'); 52 | -------------------------------------------------------------------------------- /tests/Unit/Model/ModelTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 11 | }); 12 | afterAll(function (): void { 13 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 14 | }); 15 | 16 | afterEach(function (): void { 17 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 18 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 19 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 20 | }); 21 | 22 | it('checks if model is properly populated on retrive', function ($useUUID): void { 23 | $id = createRandomUser($useUUID); 24 | $stmt = db($useUUID)->getConnection()->prepare("SELECT * FROM user WHERE id ='".$id."'"); 25 | $user = db($useUUID)->getOne('user', $id); 26 | $result = $stmt->executeQuery()->fetchAssociative(); 27 | $this->assertEquals($user->name, $result['name']); 28 | $this->assertEquals($user->getProperties(), $result); 29 | })->with('useUUID'); 30 | 31 | it('tests for model equals', function ($useUUID): void { 32 | $id = createRandomUser($useUUID); 33 | $user = db($useUUID)->getOne('user', $id); 34 | $user_two = db($useUUID)->getOne('user', $id); 35 | $this->assertTrue($user->equals($user_two)); 36 | })->with('useUUID'); 37 | 38 | it('tests for model not equals', function ($useUUID): void { 39 | $id = createRandomUser($useUUID); 40 | $user = db($useUUID)->getOne('user', $id); 41 | $user_two = db($useUUID)->getOne('user', $id); 42 | $user_two->name = 'test'; 43 | $this->assertFalse($user->equals($user_two)); 44 | })->with('useUUID'); 45 | 46 | it('checks isset() function of model', function ($useUUID): void { 47 | $id = createRandomUser($useUUID); 48 | $user = db($useUUID)->getOne('user', $id); 49 | $truey = isset($user->name); 50 | $falsey = isset($user->somekey); 51 | $this->assertTrue($truey); 52 | $this->assertFalse($falsey); 53 | })->with('useUUID'); 54 | 55 | it('tests bulk property setting', function ($useUUID): void { 56 | $user = db($useUUID)->create('user'); 57 | $user->setProperties([ 58 | 'name' => fake()->name(), 59 | 'email' => fake()->email(), 60 | 'dob' => fake()->date(), 61 | 'age' => fake()->randomNumber(2, false), 62 | 'address' => fake()->streetAddress(), 63 | ]); 64 | $id = $user->save(); 65 | $user_retrived = db($useUUID)->getOne('user', $id); 66 | $this->assertEquals($user->name, $user_retrived->name); 67 | $this->assertEquals($user->email, $user_retrived->email); 68 | })->with('useUUID'); 69 | 70 | it('tests for model delete() function', function ($useUUID): void { 71 | $user = db($useUUID)->create('user'); 72 | $user->name = fake()->name(); 73 | $user->email = fake()->email(); 74 | $id = $user->save(); 75 | $user->delete(); 76 | $this->assertNull(db()->getOne('user', $id)); 77 | })->with('useUUID'); 78 | 79 | it('tests for model clean on load function', function ($useUUID): void { 80 | $user = db($useUUID)->create('user'); 81 | $user->name = fake()->name(); 82 | $user->email = fake()->email(); 83 | $id = $user->save(); 84 | 85 | $user = db($useUUID)->getOne('user', $id); 86 | $this->assertFalse($user->hasIdError()); 87 | $this->assertFalse($user->hasForeign('oto')); 88 | $this->assertFalse($user->hasForeign('otm')); 89 | $this->assertFalse($user->hasForeign('mtm')); 90 | })->with('useUUID'); 91 | 92 | it('tests id is always 0 before save on a new model', function ($useUUID): void { 93 | $user = db($useUUID)->create('user'); 94 | $this->assertEquals($user->getId(), 0); 95 | $user->name = fake()->name(); 96 | $user->email = fake()->email(); 97 | $id = $user->save(); 98 | $this->assertNotEquals($user->getId(), 0); 99 | $this->assertEquals($user->getId(), $id); 100 | })->with('useUUID'); 101 | 102 | it('tests model refresh', function ($useUUID): void { 103 | $user1 = db($useUUID)->create('user'); 104 | $user1->name = fake()->name(); 105 | $user1->email = fake()->email(); 106 | 107 | $parent = db($useUUID)->create('parent'); 108 | $parent->name = fake()->name(); 109 | $parent->ownUserList = [$user1]; 110 | $id = $parent->save(); 111 | 112 | $parent = db($useUUID)->getOne('parent', $id); 113 | $this->assertEquals($parent->ownUserList->first()->name, $user1->name); 114 | $user = $parent->ownUserList->first(); 115 | $user->name = 'test'; 116 | $user->save(); 117 | $this->assertNotEquals($parent->ownUserList->first()->name, 'test'); 118 | $parent->refresh(); 119 | $this->assertEquals($parent->ownUserList->first()->name, 'test'); 120 | 121 | 122 | 123 | })->with('useUUID'); 124 | -------------------------------------------------------------------------------- /tests/Unit/Model/PropertyTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 13 | }); 14 | afterAll(function (): void { 15 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 16 | }); 17 | 18 | afterEach(function (): void { 19 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS grandparent CASCADE; '); 20 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 21 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 22 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 23 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS child CASCADE; '); 24 | }); 25 | 26 | it('tests model properties with multiple realtions', function ($useUUID): void { 27 | $child1 = db($useUUID)->create('child'); 28 | $child1->name = fake()->name(); 29 | $child1->email = fake()->email(); 30 | $child1->dob = fake()->date(); 31 | $child1->age = fake()->randomNumber(2, false); 32 | 33 | $child2 = db($useUUID)->create('child'); 34 | $child2->name = fake()->name(); 35 | $child2->email = fake()->email(); 36 | $child2->dob = fake()->date(); 37 | $child2->age = fake()->randomNumber(2, false); 38 | 39 | $child3 = db($useUUID)->create('user'); 40 | $child3->name = fake()->name(); 41 | $child3->email = fake()->email(); 42 | $child3->dob = fake()->date(); 43 | $child3->age = fake()->randomNumber(2, false); 44 | 45 | $grandfater = db($useUUID)->create('grandparent'); 46 | $grandfater->name = fake()->name(); 47 | $grandfater->email = fake()->email(); 48 | $grandfater->dob = fake()->date(); 49 | $grandfater->age = fake()->randomNumber(2, false); 50 | 51 | $parent = db($useUUID)->create('parent'); 52 | $parent->name = fake()->name(); 53 | $parent->grandparent = $grandfater; 54 | $parent->ownChildList = [$child1, $child2]; 55 | $parent->sharedUserList = [$child3]; 56 | $id = $parent->save(); 57 | 58 | $this->assertTrue($child1->isLoaded()); 59 | $this->assertTrue($child2->isLoaded()); 60 | $this->assertTrue($child3->isLoaded()); 61 | $this->assertTrue($grandfater->isLoaded()); 62 | 63 | $parent_retrived = db($useUUID)->getOne('parent', $id); 64 | $this->assertEquals($parent->name, $parent_retrived->name); 65 | $this->assertTrue(isset($parent_retrived->grandparent_id)); 66 | $this->assertFalse(isset($parent_retrived->grandparent)); 67 | $this->assertEquals($grandfater->name, $parent_retrived->grandparent->name); 68 | $this->assertFalse(isset($parent_retrived->grandparent_id)); 69 | $this->assertTrue(isset($parent_retrived->grandparent)); 70 | $this->assertEquals(count($parent_retrived->ownChildList), 2); 71 | 72 | $this->assertTrue($parent_retrived->ownChildList->first()->name == $child1->name || $parent_retrived->ownChildList->first()->name == $child2->name); 73 | $this->assertEquals(count($parent_retrived->sharedUserList), 1); 74 | $this->assertEquals($parent_retrived->sharedUserList->first()->name, $child3->name); 75 | })->with('useUUID'); 76 | -------------------------------------------------------------------------------- /tests/Unit/Model/RelationsTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 10 | }); 11 | AfterAll(function (): void { 12 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 13 | }); 14 | 15 | afterEach(function (): void { 16 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 17 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 18 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 19 | }); 20 | 21 | it('checks if model can retrive one-to-one related models', function ($useUUID): void { 22 | $user = db($useUUID)->create('user'); 23 | $user->name = fake()->name(); 24 | $user->email = fake()->email(); 25 | $user->dob = fake()->date(); 26 | $user->age = fake()->randomNumber(2, false); 27 | $user->address = fake()->streetAddress(); 28 | // $id = $user->save(); 29 | 30 | $parent = db($useUUID)->create('parent'); 31 | $parent->name = fake()->name(); 32 | $parent->user = $user; 33 | $id = $parent->save(); 34 | 35 | $user_retrived = $parent->user; 36 | if (!db($useUUID)->isUsingUUID()) { 37 | unset($user_retrived->id); 38 | } 39 | $this->assertJsonStringEqualsJsonString( 40 | $user_retrived->toString(), 41 | $user->toString() 42 | ); 43 | })->with('useUUID'); 44 | 45 | it('checks if model can retrive one-to-many related models', function ($useUUID): void { 46 | $user = db($useUUID)->create('user'); 47 | $user->name = fake()->name(); 48 | $user->email = fake()->email(); 49 | $user->dob = fake()->date(); 50 | $user->age = fake()->randomNumber(2, false); 51 | $user->address = fake()->streetAddress(); 52 | 53 | $user_two = db($useUUID)->create('user'); 54 | $user_two->name = fake()->name(); 55 | $user_two->email = fake()->email(); 56 | $user_two->dob = fake()->date(); 57 | $user_two->age = fake()->randomNumber(2, false); 58 | $user_two->address = fake()->streetAddress(); 59 | // $id = $user->save(); 60 | 61 | $parent = db($useUUID)->create('parent'); 62 | $parent->name = fake()->name(); 63 | $parent->ownUserList = [$user, $user_two]; 64 | $id = $parent->save(); 65 | 66 | $parent_retrived = db($useUUID)->getOne('parent', $id); 67 | $users_retrived = $parent->ownUserList->apply(function ($user): void { 68 | unset($user->id); 69 | }); 70 | if (db($useUUID)->isUsingUUID()) { 71 | unset($user->id); 72 | unset($user_two->id); 73 | } 74 | 75 | $test_collection = Scrawler\Arca\Collection::fromIterable([$user, $user_two]) 76 | ->map(static fn ($model): \Scrawler\Arca\Model => $model->setLoaded()); 77 | $test_collection_two = Scrawler\Arca\Collection::fromIterable([$user_two, $user]) 78 | ->map(static fn ($model): \Scrawler\Arca\Model => $model->setLoaded()); 79 | 80 | $this->assertTrue( 81 | $users_retrived->toString() == $test_collection->toString() || $users_retrived->toString() == $test_collection_two->toString() 82 | ); 83 | })->with('useUUID'); 84 | 85 | it('checks if model can retrive many-to-many related models', function ($useUUID): void { 86 | $user = db($useUUID)->create('user'); 87 | $user->name = fake()->name(); 88 | $user->email = fake()->email(); 89 | $user->dob = fake()->date(); 90 | $user->age = fake()->randomNumber(2, false); 91 | $user->address = fake()->streetAddress(); 92 | 93 | $user_two = db($useUUID)->create('user'); 94 | $user_two->name = fake()->name(); 95 | $user_two->email = fake()->email(); 96 | $user_two->dob = fake()->date(); 97 | $user_two->age = fake()->randomNumber(2, false); 98 | $user_two->address = fake()->streetAddress(); 99 | // $id = $user->save(); 100 | 101 | $parent = db($useUUID)->create('parent'); 102 | $parent->name = fake()->name(); 103 | $parent->sharedUserList = [$user, $user_two]; 104 | $id = $parent->save(); 105 | 106 | $parent_retrived = db()->getOne('parent', $id); 107 | $users_retrived = $parent->sharedUserList->apply(function ($user): void { 108 | unset($user->id); 109 | }); 110 | 111 | if (db($useUUID)->isUsingUUID()) { 112 | unset($user->id); 113 | unset($user_two->id); 114 | } 115 | 116 | $test_collection = Scrawler\Arca\Collection::fromIterable([$user, $user_two]) 117 | ->map(static fn ($model): \Scrawler\Arca\Model => $model->setLoaded()); 118 | 119 | $test_collection_two = Scrawler\Arca\Collection::fromIterable([$user_two, $user]) 120 | ->map(static fn ($model): \Scrawler\Arca\Model => $model->setLoaded()); 121 | $this->assertTrue( 122 | $users_retrived->toString() == $test_collection->toString() || $users_retrived->toString() == $test_collection_two->toString() 123 | ); 124 | })->with('useUUID'); 125 | -------------------------------------------------------------------------------- /tests/Unit/Model/TraitsTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 9 | }); 10 | afterAll(function (): void { 11 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 12 | }); 13 | 14 | afterEach(function (): void { 15 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 16 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 17 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 18 | }); 19 | 20 | it('tests model can be treated as iterable', function (): void { 21 | $model = db()->create('user'); 22 | $model->name = fake()->name(); 23 | $model->email = fake()->email(); 24 | $model->dob = fake()->date(); 25 | $model->age = fake()->randomNumber(2, false); 26 | $model->address = fake()->streetAddress(); 27 | $model->save(); 28 | 29 | $model = db()->find('user')->first(); 30 | foreach ($model as $key => $value) { 31 | $this->assertNotNull($key); 32 | $this->assertNotNull($value); 33 | } 34 | }); 35 | 36 | it('tests model can be treated as Array', function (): void { 37 | $model = db()->create('user'); 38 | $model->name = fake()->name(); 39 | $model->email = fake()->email(); 40 | $model->dob = fake()->date(); 41 | $model->age = fake()->randomNumber(2, false); 42 | $model->address = fake()->streetAddress(); 43 | $model->save(); 44 | 45 | $model = db()->find('user')->first(); 46 | $this->assertIsArray($model->toArray()); 47 | $this->assertEquals($model['name'], $model->name); 48 | $this->assertTrue(isset($model['email'])); 49 | unset($model['age']); 50 | $this->assertFalse($model->isset('age')); 51 | 52 | $model['age'] = 10; 53 | $this->assertEquals($model['age'], $model->age); 54 | 55 | expect(fn (): int => $model[] = 10)->toThrow(Exception::class); 56 | }); 57 | 58 | it('tests if class is stringable', function (): void { 59 | $model = db()->create('user'); 60 | $model->name = fake()->name(); 61 | $model->email = fake()->email(); 62 | $model->save(); 63 | 64 | ob_start(); 65 | echo $model; 66 | $data = ob_get_clean(); 67 | 68 | $this->assertEquals($data, $model->toString()); 69 | }); 70 | -------------------------------------------------------------------------------- /tests/Unit/Model/TypeTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 11 | }); 12 | afterAll(function (): void { 13 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 14 | }); 15 | 16 | afterEach(function (): void { 17 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 18 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 19 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 20 | }); 21 | 22 | it('tests for types when loaded ', function ($useUUID): void { 23 | $id = createRandomUser($useUUID); 24 | $user_retrived = db($useUUID)->getOne('user', $id); 25 | $types = $user_retrived->getTypes(); 26 | $this->assertIsArray($types); 27 | $this->assertEquals($types['name'], 'text'); 28 | $this->assertEquals($types['email'], 'text'); 29 | })->with('useUUID'); 30 | 31 | it('tests boolean type bug', function ($useUUID): void { 32 | $user = db()->create('user'); 33 | $user->name = fake()->name(); 34 | $user->email = fake()->email(); 35 | $user->active = true; 36 | $id = $user->save(); 37 | $user_retrived = db()->getOne('user', $id); 38 | $this->assertTrue($user_retrived->active); 39 | 40 | $user->name = fake()->name(); 41 | $user->email = fake()->email(); 42 | $user->active = false; 43 | $id = $user->save(); 44 | $user_retrived = db()->getOne('user', $id); 45 | $this->assertFalse($user_retrived->active); 46 | })->with('useUUID'); 47 | 48 | it('checks for storing array in db', function ($useUUID): void { 49 | $user = db($useUUID)->create('user'); 50 | $user->name = fake()->name(); 51 | $user->email = fake()->email(); 52 | $user->hobbies = ['swimming', 'cycling', 'running']; 53 | $id = $user->save(); 54 | 55 | $user_retrived = db($useUUID)->getOne('user', $id); 56 | $hobby = $user_retrived->hobbies; 57 | expect($hobby)->toBeArray(); 58 | })->with('useUUID'); 59 | -------------------------------------------------------------------------------- /tests/Unit/QueryBuilder/EagerLoadingTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 10 | }); 11 | afterAll(function (): void { 12 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 13 | }); 14 | 15 | afterEach(function (): void { 16 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS grandparent CASCADE; '); 17 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 18 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 19 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 20 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS child CASCADE; '); 21 | }); 22 | 23 | it('checks if db()->find()->with() eager loads relation', function ($useUUID): void { 24 | $user = db($useUUID)->create('user'); 25 | $user->name = fake()->name(); 26 | $user->email = fake()->email(); 27 | $user->dob = fake()->date(); 28 | $user->age = fake()->randomNumber(2, false); 29 | $user->address = fake()->streetAddress(); 30 | // $user->save(); 31 | 32 | $parent = db($useUUID)->create('parent'); 33 | $parent->name = fake()->name(); 34 | $parent->user = $user; 35 | $parent->save(); 36 | 37 | $parent_retrived = db($useUUID)->find('parent')->with('user')->first(); 38 | $user = db($useUUID)->find('user')->first(); 39 | 40 | $this->assertJsonStringEqualsJsonString( 41 | $parent_retrived->user->toString(), 42 | $user->toString() 43 | ); 44 | })->with('useUUID'); 45 | 46 | it('checks if db()->find()->with() eager loads multiple realtions', function ($useUUID): void { 47 | $child1 = db($useUUID)->create('child'); 48 | $child1->name = fake()->name(); 49 | $child1->email = fake()->email(); 50 | $child1->dob = fake()->date(); 51 | $child1->age = fake()->randomNumber(2, false); 52 | 53 | $child2 = db($useUUID)->create('child'); 54 | $child2->name = fake()->name(); 55 | $child2->email = fake()->email(); 56 | $child2->dob = fake()->date(); 57 | $child2->age = fake()->randomNumber(2, false); 58 | 59 | $child3 = db($useUUID)->create('user'); 60 | $child3->name = fake()->name(); 61 | $child3->email = fake()->email(); 62 | $child3->dob = fake()->date(); 63 | $child3->age = fake()->randomNumber(2, false); 64 | 65 | $grandfater = db($useUUID)->create('grandparent'); 66 | $grandfater->name = fake()->name(); 67 | $grandfater->email = fake()->email(); 68 | $grandfater->dob = fake()->date(); 69 | $grandfater->age = fake()->randomNumber(2, false); 70 | 71 | $parent = db($useUUID)->create('parent'); 72 | $parent->name = fake()->name(); 73 | $parent->grandparent = $grandfater; 74 | $parent->ownChildList = [$child1, $child2]; 75 | $parent->sharedUserList = [$child3]; 76 | $id = $parent->save(); 77 | 78 | $parent_retrived = db($useUUID)->find('parent')->where('id = ?')->setParameter(0, $id)->with('ownChildList')->with('sharedUserList')->with('grandparent')->first(); 79 | $parent_simple = db($useUUID)->getOne('parent', $id); 80 | 81 | $this->assertJsonStringNotEqualsJsonString( 82 | $parent_retrived->toString(), 83 | $parent_simple->toString() 84 | ); 85 | 86 | $parent_simple->ownChildList; 87 | $parent_simple->sharedUserList; 88 | $parent_simple->grandparent; 89 | 90 | $this->assertJsonStringEqualsJsonString( 91 | $parent_retrived->toString(), 92 | $parent_simple->toString() 93 | ); 94 | })->with('useUUID'); 95 | -------------------------------------------------------------------------------- /tests/Unit/QueryBuilder/QueryBuilderTest.php: -------------------------------------------------------------------------------- 1 | getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); 10 | }); 11 | afterAll(function (): void { 12 | db()->getConnection()->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); 13 | }); 14 | 15 | afterEach(function (): void { 16 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent_user CASCADE; '); 17 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS user CASCADE; '); 18 | db()->getConnection()->executeStatement('DROP TABLE IF EXISTS parent CASCADE; '); 19 | }); 20 | 21 | it('checks if db()->find()->first() returns first record', function ($useUUID): void { 22 | $id = createRandomUser($useUUID); 23 | $user = db($useUUID)->find('user')->first(); 24 | $stmt = db($useUUID)->getConnection()->prepare("SELECT * FROM user WHERE id = '".$id."'"); 25 | $result = json_encode($stmt->executeQuery()->fetchAssociative()); 26 | $this->assertJsonStringEqualsJsonString( 27 | $result, 28 | $user->toString() 29 | ); 30 | $this->assertInstanceOf(Scrawler\Arca\Model::class, $user); 31 | })->with('useUUID'); 32 | 33 | it('checks if null is returned if table does not exist', function (): void { 34 | $this->assertNull(db()->find('non_existent_table')->first()); 35 | $this->assertInstanceOf(Scrawler\Arca\Collection::class, db()->find('non_existent_table')->get()); 36 | $this->assertEmpty(db()->find('non_existent_table')->get()->toArray()); 37 | }); 38 | 39 | it('checks if null is returned if table empty', function (): void { 40 | $user = db()->create('user'); 41 | $user->name = fake()->name(); 42 | $user->email = fake()->email(); 43 | $user->save(); 44 | $user->delete(); 45 | 46 | $this->assertNull(db()->find('user')->first()); 47 | $this->assertInstanceOf(Scrawler\Arca\Collection::class, db()->find('user')->get()); 48 | $this->assertEmpty(db()->find('user')->get()->toArray()); 49 | }); 50 | --------------------------------------------------------------------------------