├── .codeclimate.yml ├── .github └── workflows │ └── push.yml ├── .gitignore ├── CONTRIBUTE.md ├── README.md ├── composer.json ├── docs ├── _config.yml ├── _data │ └── menu.yml ├── _layouts │ └── default.html ├── _reference │ ├── class.md.twig │ ├── index.md.twig │ ├── method.md.twig │ └── template.xml ├── bulkInserts.md ├── configuration.md ├── entities.md ├── entityDefinition.md ├── events.md ├── filters.md ├── index.md ├── javascripts │ └── scale.fix.js ├── querybuilder.md ├── reference.md ├── relationDefinition.md ├── relations.md ├── stylesheets │ ├── highlight.css │ └── styles.css ├── testing.md └── validate.md ├── examples ├── bootstrap.php ├── entities.php ├── fetch.php ├── filter.php ├── morphed.php └── observe.php ├── icon.svg ├── logo.svg ├── phpdoc.xml ├── phpunit.xml ├── src ├── BulkInsert.php ├── DbConfig.php ├── Dbal │ ├── Column.php │ ├── Dbal.php │ ├── Error.php │ ├── Error │ │ ├── InvalidJson.php │ │ ├── NoBoolean.php │ │ ├── NoDateTime.php │ │ ├── NoNumber.php │ │ ├── NoString.php │ │ ├── NoTime.php │ │ ├── NotAllowed.php │ │ ├── NotNullable.php │ │ ├── NotValid.php │ │ └── TooLong.php │ ├── Escaping.php │ ├── Expression.php │ ├── Mysql.php │ ├── Other.php │ ├── Pgsql.php │ ├── QueryLanguage │ │ ├── CompositeInExpression.php │ │ ├── CompositeInValuesExpression.php │ │ ├── DeleteStatement.php │ │ ├── InsertStatement.php │ │ ├── UpdateFromStatement.php │ │ ├── UpdateJoinStatement.php │ │ ├── UpdateStatement.php │ │ └── WhereClause.php │ ├── Sqlite.php │ ├── Table.php │ ├── Type.php │ ├── Type │ │ ├── Boolean.php │ │ ├── DateTime.php │ │ ├── Enum.php │ │ ├── Json.php │ │ ├── Number.php │ │ ├── Set.php │ │ ├── Text.php │ │ ├── Time.php │ │ └── VarChar.php │ └── TypeInterface.php ├── EM.php ├── Entity.php ├── Entity │ ├── Booting.php │ ├── EventHandlers.php │ ├── GeneratesPrimaryKeys.php │ ├── Naming.php │ ├── Relations.php │ └── Validation.php ├── EntityFetcher.php ├── EntityFetcher │ ├── AppliesFilters.php │ ├── CallableFilter.php │ ├── ExecutesQueries.php │ ├── FilterInterface.php │ ├── MakesJoins.php │ └── TranslatesClasses.php ├── EntityManager.php ├── Event.php ├── Event │ ├── Changed.php │ ├── Deleted.php │ ├── Deleting.php │ ├── Fetched.php │ ├── Inserted.php │ ├── Inserting.php │ ├── Saved.php │ ├── Saving.php │ ├── UpdateEvent.php │ ├── Updated.php │ └── Updating.php ├── Exception.php ├── Exception │ ├── IncompletePrimaryKey.php │ ├── InvalidArgument.php │ ├── InvalidConfiguration.php │ ├── InvalidName.php │ ├── InvalidRelation.php │ ├── InvalidType.php │ ├── NoConnection.php │ ├── NoEntity.php │ ├── NoEntityManager.php │ ├── NoOperator.php │ ├── NotJoined.php │ ├── NotScalar.php │ ├── UndefinedRelation.php │ ├── UnknownColumn.php │ └── UnsupportedDriver.php ├── Helper.php ├── MockTrait.php ├── Namer.php ├── Observer │ ├── AbstractObserver.php │ └── CallbackObserver.php ├── ObserverInterface.php ├── QueryBuilder │ ├── ExecutesQueries.php │ ├── HasWhereConditions.php │ ├── MakesJoins.php │ ├── Parenthesis.php │ ├── ParenthesisInterface.php │ ├── QueryBuilder.php │ └── QueryBuilderInterface.php ├── Relation.php ├── Relation │ ├── HasOpponent.php │ ├── HasReference.php │ ├── ManyToMany.php │ ├── Morphed.php │ ├── OneToMany.php │ ├── OneToOne.php │ ├── Owner.php │ └── ParentChildren.php └── Testing │ ├── EntityFetcherMock.php │ ├── EntityFetcherMock │ ├── Result.php │ └── ResultRepository.php │ ├── EntityManagerMock.php │ └── MocksEntityManager.php └── tests ├── BulkInsertTest.php ├── ClosureWrapper.php ├── Constraint └── ArraySubset.php ├── DbConfigTest.php ├── Dbal ├── BasicTest.php ├── BulkInsertTest.php ├── ColumnTest.php ├── DataModificationTest.php ├── EscapeValueTest.php ├── Mysql │ ├── DescribeTest.php │ └── UpdateJoinTest.php ├── Other │ └── UpdateTest.php ├── Pgsql │ ├── DescribeTest.php │ └── UpdateFromTest.php ├── Sqlite │ ├── DescribeTest.php │ ├── InsertTest.php │ └── UpdateFromTest.php ├── TransactionTest.php ├── Type │ ├── BooleanTest.php │ ├── Custom │ │ ├── CustomColumn.php │ │ ├── Point.php │ │ └── PointTest.php │ ├── DateTimeTest.php │ ├── EnumTest.php │ ├── JsonTest.php │ ├── NumberTest.php │ ├── SetTest.php │ ├── TextTest.php │ ├── TimeTest.php │ └── VarCharTest.php └── ValidateTest.php ├── Entity ├── BasicTest.php ├── BootTestEntity.php ├── BootTestTrait.php ├── BootingTest.php ├── ColumnNameTest.php ├── DataTest.php ├── Examples │ ├── Article.php │ ├── Category.php │ ├── Concerns │ │ ├── WithCreated.php │ │ ├── WithTimestamps.php │ │ └── WithUpdated.php │ ├── ContactPhone.php │ ├── DamagedABBRVCase.php │ ├── GeneratesUuid.php │ ├── Image.php │ ├── Psr0_StudlyCaps.php │ ├── RelationExample.php │ ├── Snake_Ucfirst.php │ ├── StaticTableName.php │ ├── StudlyCaps.php │ ├── Tag.php │ ├── Taggable.php │ ├── User.php │ └── UserContact.php ├── ExistsTest.php ├── HelperTest.php ├── IssetTest.php ├── RelationsTest.php ├── SaveEntityTest.php ├── TableNameTest.php ├── ToArrayTest.php └── ValidateTest.php ├── EntityFetcher ├── BasicTest.php ├── CountTest.php ├── EagerLoadTest.php ├── Examples │ └── NotDeletedFilter.php ├── ExecutesQueriesTest.php └── FilterTest.php ├── EntityManager ├── BulkInsertTest.php ├── ConnectionsTest.php ├── DataModificationTest.php ├── DescribeTest.php ├── EagerLoadTest.php ├── Examples │ ├── Concrete.php │ ├── Entity.php │ ├── SubNamespace │ │ └── Entity.php │ ├── Unspecified.php │ └── functions.php ├── GetInstanceTest.php ├── MappingTest.php └── OptionsTest.php ├── Examples ├── AuditObserver.php ├── CustomEvent.php └── DateTimeDerivate.php ├── ExceptionsTest.php ├── HelperTest.php ├── NamerTest.php ├── Observer ├── AbstractObserverTest.php ├── CallbackObserverTest.php ├── EventTest.php ├── FireEventTest.php └── ObserverRegistrationTest.php ├── QueryBuilder ├── BasicTest.php ├── FetchTest.php ├── JoinTest.php └── WhereConditionsTest.php ├── Relation ├── ManyToManyTest.php ├── MorphedRelationTest.php ├── OneToManyTest.php ├── OneToOneTest.php ├── OwnerTest.php ├── ParentChildrenTest.php └── RelationsFilterTest.php ├── TestCase.php ├── TestEntity.php ├── TestEntityFetcher.php ├── TestEntityManager.php └── Testing ├── CreateMockedEntityTest.php ├── EntityFetcherMockTest.php ├── EntityFetcherResultTest.php ├── ExpectDeleteTest.php ├── ExpectFetchTest.php ├── ExpectInsertTest.php ├── ExpectUpdateTest.php └── InitMockTest.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | checks: 4 | method-complexity: 5 | config: 6 | threshold: 9 7 | 8 | exclude_patterns: 9 | - "config/" 10 | - "db/" 11 | - "dist/" 12 | - "docs/" 13 | - "features/" 14 | - "**/node_modules/" 15 | - "script/" 16 | - "**/spec/" 17 | - "**/test/" 18 | - "**/tests/" 19 | - "Tests/" 20 | - "example.php" 21 | - "examples/" 22 | - "**/vendor/" 23 | - "**/*_test.go" 24 | - "**/*.d.ts" 25 | 26 | plugins: 27 | phpcodesniffer: 28 | enabled: true 29 | config: 30 | standard: "PSR2" 31 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | jobs: 3 | before: 4 | runs-on: ubuntu-latest 5 | steps: 6 | - name: Setup Code-Climate 7 | uses: amancevice/setup-code-climate@v1 8 | with: 9 | cc_test_reporter_id: ${{ secrets.CC_TEST_REPORTER_ID }} 10 | 11 | - name: Prepare CodeClimate 12 | run: cc-test-reporter before-build 13 | 14 | unit-tests: 15 | needs: [before] 16 | strategy: 17 | matrix: 18 | php-version: ["7.3", "7.4", "8.0", "8.1", "8.2"] 19 | name: PHP Unit Tests on PHP ${{ matrix.php-version }} 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php-version }} 29 | 30 | - name: Setup Code-Climate 31 | uses: amancevice/setup-code-climate@v1 32 | with: 33 | cc_test_reporter_id: ${{ secrets.CC_TEST_REPORTER_ID }} 34 | 35 | - name: Get composer cache directory 36 | id: composer-cache 37 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 38 | 39 | - name: Cache dependencies 40 | uses: actions/cache@v3 41 | with: 42 | path: ${{ steps.composer-cache.outputs.dir }} 43 | key: composer-cache-${{ matrix.php-version }} 44 | 45 | - name: Install dependencies 46 | run: composer install --no-interaction --ansi 47 | 48 | - name: Execute tests 49 | run: | 50 | php -dzend_extension=xdebug.so -dxdebug.mode=coverage vendor/bin/phpunit \ 51 | -c phpunit.xml \ 52 | --coverage-clover=coverage/clover.xml \ 53 | --coverage-text \ 54 | --color=always 55 | 56 | - name: Format Coverage 57 | run: cc-test-reporter format-coverage -t clover -o coverage/cc-${{ matrix.php-version }}.json coverage/clover.xml 58 | 59 | - name: Store Coverage Result 60 | uses: actions/upload-artifact@v3 61 | with: 62 | name: coverage-results 63 | path: coverage/ 64 | 65 | after: 66 | needs: [unit-tests] 67 | runs-on: ubuntu-latest 68 | steps: 69 | - name: Restore Coverage Result 70 | uses: actions/download-artifact@v3 71 | with: 72 | name: coverage-results 73 | path: coverage/ 74 | 75 | - name: Setup Code-Climate 76 | uses: amancevice/setup-code-climate@v1 77 | with: 78 | cc_test_reporter_id: ${{ secrets.CC_TEST_REPORTER_ID }} 79 | 80 | - name: Report Coverage 81 | run: | 82 | cc-test-reporter sum-coverage coverage/cc-*.json -p 5 -o coverage/cc-total.json 83 | cc-test-reporter upload-coverage -i coverage/cc-total.json 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /docs/_site 3 | /composer.lock 4 | /docs/.jekyll-cache 5 | /examples/*.sqlite 6 | /examples/*.sql 7 | /.phpunit.result.cache 8 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Notes for contributors 2 | 3 | Please have in mind that this library is PSR-2 compliant. No changes can be merged that are not PSR-2 compliant. 4 | 5 | ## Testing 6 | 7 | The code should have a 100% code coverage. To run the code coverage is not necessary - it will run on pull request. If 8 | There is something not testable we either find a solution or mark it with @ignoreCodeCoverage. 9 | 10 | To run the tests you just have to install the dev dependencies with `composer install`. Then you can start both: 11 | Unit tests `composer test` and code sniffer `composer code-style`. 12 | 13 | ## Documentation 14 | 15 | You should update the documentation according to your changes. 16 | 17 | In order to update the API reference you will also have to install docker. Use this command to update the 18 | documentation: 19 | 20 | ```console 21 | $ docker run --rm --user $(id -u) -v $(pwd):/data -v $(pwd)/docs/_reference:/opt/phpdoc/data/templates/_reference iras/phpdoc2:2 phpdoc -c phpdoc.xml 22 | ``` 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tflori/orm", 3 | "description": "lightweight object relational mapper", 4 | "license": "MIT", 5 | "require": { 6 | "php": "^7.3 || ^8.0", 7 | "ext-json": "*", 8 | "ext-mbstring": "*", 9 | "ext-pdo": "*" 10 | }, 11 | "require-dev": { 12 | "mockery/mockery": "^1.1", 13 | "phpunit/phpunit": "*", 14 | "tflori/phpunit-printer": "*", 15 | "squizlabs/php_codesniffer": "^3.5" 16 | }, 17 | "suggest": { 18 | "mockery/mockery": "^1.1" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "ORM\\": "src/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "ORM\\Test\\": "tests/" 28 | } 29 | }, 30 | "archive": { 31 | "exclude": ["/tests", "/docs", "/examples"] 32 | }, 33 | "scripts": { 34 | "code-style": [ 35 | "phpcs --standard=PSR2 src", 36 | "phpcs --standard=PSR2 --ignore=Examples tests" 37 | ], 38 | "coverage": "phpunit --coverage-text", 39 | "test": "phpunit --color=always" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | baseurl: /orm 2 | -------------------------------------------------------------------------------- /docs/_data/menu.yml: -------------------------------------------------------------------------------- 1 | Introduction: / 2 | Configuration: /configuration.html 3 | Entity Definition: /entityDefinition.html 4 | Working With Entities: /entities.html 5 | Use Filters: /filters.html 6 | Relation Definition: /relationDefinition.html 7 | Working With Relations: /relations.html 8 | Events and Observers: /events.html 9 | Use QueryBuilder: /querybuilder.html 10 | Validate Data: /validate.html 11 | Bulk Inserts: /bulkInserts.html 12 | Testing: /testing.html 13 | 14 | ' ': /placeholder.html 15 | 16 | API Reference: /reference.html 17 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tflori/orm - {{ page.title }} 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 |
17 |
18 |

tflori/orm

19 |

just another orm...

20 | 21 | 22 |
    23 | {% for link in site.data.menu %} 24 |
  • 25 | {{ link[0] }} 26 |
  • 27 | {% endfor %} 28 |
29 |
30 | 31 |

View the Project on GitHub tflori/orm

32 | 33 | Build Status 34 | Test Coverage 35 | Maintainability 36 | Latest Stable Version 37 | Total Downloads 38 | 39 | 44 |
45 |
46 | {{ content }} 47 |
48 | 53 |
54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/_reference/class.md.twig: -------------------------------------------------------------------------------- 1 | ### {{ node.FullyQualifiedStructuralElementName|trim('\\') }} 2 | 3 | {% if node.parent is not empty %} 4 | **Extends:** {% for parent in node.parent %} 5 | [{{ parent.FullyQualifiedStructuralElementName|trim('\\') }}](#{{ parent.FullyQualifiedStructuralElementName|replace({'\\': ''})|lower }}) 6 | {% else %}[{{ node.parent.FullyQualifiedStructuralElementName|trim('\\') }}](#{{ node.parent.FullyQualifiedStructuralElementName|replace({'\\': ''})|lower }}) 7 | {% endfor %} 8 | {% endif %} 9 | 10 | {% if node.interfaces is not empty %} 11 | **Implements:** {% for interface in node.interfaces %} 12 | [{{ interface.FullyQualifiedStructuralElementName|trim('\\') }}](#{{ interface.FullyQualifiedStructuralElementName|replace({'\\': ''})|lower }}) 13 | {% endfor %} 14 | {% endif %} 15 | 16 | {% if node.summary is not empty and node.summary != 'Class '~node.name %} 17 | #### {{ node.summary|raw }} 18 | {% endif %} 19 | 20 | {{ node.description|raw }} 21 | 22 | {% if node.deprecated %}* **Warning:** this class is **deprecated**. This means that this class will likely be removed in a future version. 23 | {% endif %} 24 | 25 | {% if node.tags.see is not empty or node.tags.link is not empty %} 26 | **See Also:** 27 | 28 | {% for see in node.tags.see %} 29 | * {{ see.reference }} {% if see.description %}- {{ see.description|raw }}{% endif %} 30 | {% endfor %} 31 | {% for link in node.tags.link %} 32 | * [{{ link.description ?: link.link }}]({{ link.link }}) 33 | {% endfor %} 34 | 35 | {% endif %}{# node.tags.see || node.tags.link #} 36 | 37 | {% if node.constants is not empty %} 38 | #### Constants 39 | 40 | | Name | Value | 41 | |------|-------| 42 | {% for constant in node.constants %} 43 | | {{ constant.name }} | `{{ constant.value|raw }}` | 44 | {% endfor %} 45 | 46 | {% endif %} 47 | 48 | {% if (node.inheritedProperties.merge(node.properties)) is not empty %} 49 | #### Properties 50 | 51 | | Visibility | Name | Type | Description | 52 | |------------|------|------|---------------------------------------| 53 | {% for property in node.inheritedProperties.merge(node.properties).sortBy('name') %} 54 | | **{{ property.visibility }}{{ property.isStatic ? ' static' }}** | `${{ property.name }}` | {% if property.types is not empty %}**{{ property.types ? property.types|join(' | ')|replace({'{% endif %} 15 | {% if method.static %}**Static:** this method is **static**. 16 |
{% endif %} 17 | **Visibility:** this method is **{{ method.visibility }}**. 18 |
19 | {% if method.name != '__construct' and method.response %} **Returns**: this method returns **{{ method.response.types[0] == 'self' ? node.name : method.response.types|join('|') }}** 20 |
{% endif %} 21 | {% if method.response.description %}**Response description:** {{ method.response.description|raw }} 22 |
{% endif %} 23 | {% if method.tags.throws is not empty %}**Throws:** this method may throw {% for throws in method.tags.throws %} 24 | {{ not loop.first ? ' or ' }}**{{ throws.types|join('** or **')|raw }}**{% endfor %} 25 |
{% endif %} 26 | 27 | 28 | {% if method.arguments is not empty %} 29 | ##### Parameters 30 | 31 | | Parameter | Type | Description | 32 | |-----------|------|-------------| 33 | {% for argument in method.arguments %} 34 | | `{{ argument.name }}` | {% if argument.types is not empty %}**{{ argument.types ? argument.types|join(' | ')|replace({' 2 | 7 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Introduction 4 | permalink: / 5 | --- 6 | ## Introduction 7 | 8 | **TL;DR** Others suck, we can do it better. 9 | 10 | Why to create another ORM? There are not enough ORM implementations for PHP already? 11 | 12 | Yes, there are a lot of implementations: 13 | 14 | - doctrine/orm 15 | - heavy: 8,8 MB of everything that you don't need, 6 direct dependencies with own dependencies 16 | - annotations that makes it unreadable 17 | - big amount of queries or very slow queries when it comes to join across multiple tables 18 | 19 | - eloquent 20 | - no single entry package (you need illuminate/this, that and database) 21 | - complicated setup 22 | - I **like** this very much in laravel - so not much to say here against it 23 | 24 | - propel/propel 25 | - still not stable 2.0-dev 26 | - even more heavy than doctrine 27 | - requires a lot of configurations 28 | 29 | - j4mie/idiorim and j4mie/paris 30 | - uses a lot of static methods and gets hard to test 31 | - not compatible to existing dependecy injection models 32 | - last update 2 years ago 33 | - everything in one file 34 | - ... 35 | 36 | This implementation will have the following features: 37 | 38 | - no configuration required 39 | - ok some bit for sure (e.g. how to connect to your database?) 40 | - of course this is only possible if you setup your database as we think your database should look like. If not you 41 | should only have to setup the rules of your system and naming conventions. 42 | - simple to use 43 | - lightweight sources 44 | - fast 45 | 46 | How to achieve this features? The main goal of Doctrine seems to abstract everything - at the end you should be able 47 | to replace the whole DBMS behind your app and switch from postgresql to sqlite. That requires not only a lot of 48 | sources. It also requires some extra cycles to get these abstraction to work. 49 | 50 | This library will only produce ANSI-SQL that every SQL database should understand. Other queries have to be written by 51 | hand. This has two reasons: 52 | 53 | 1. You can write much faster and efficient queries 54 | 2. We don't need to write a lot of abstraction (more code; more bugs) 55 | 56 | This library will not fetch any mistake a developer can make. It aims to be a helper to store data in your database. Not 57 | to replace your database and your knowledge how to use this database. You can make a lot of errors - less than without 58 | this library but still a lot. When you make an error that is not catched (mostly we catch only mistakes that would 59 | cause a fatal error instead) you will get a `PDOException`. 60 | -------------------------------------------------------------------------------- /docs/javascripts/scale.fix.js: -------------------------------------------------------------------------------- 1 | var metas = document.getElementsByTagName('meta'); 2 | var i; 3 | if (navigator.userAgent.match(/iPhone/i)) { 4 | for (i=0; i new ORM\DbConfig('sqlite', '/tmp/example.sqlite') 19 | ]); 20 | 21 | // reset the database 22 | $em->getConnection()->query("DROP TABLE IF EXISTS user"); 23 | 24 | $em->getConnection()->query("CREATE TABLE user ( 25 | id INTEGER NOT NULL PRIMARY KEY, 26 | username VARCHAR (20) NOT NULL, 27 | password VARCHAR (32) NOT NULL 28 | )"); 29 | 30 | $em->getConnection()->query("CREATE UNIQUE INDEX user_username ON user (username)"); 31 | 32 | $em->getConnection()->query("INSERT INTO user (username, password) VALUES 33 | ('user_a', '" . md5('password_a') . "'), 34 | ('user_b', '" . md5('password_b') . "'), 35 | ('user_c', '" . md5('password_c') . "') 36 | "); 37 | 38 | $em->getConnection()->query("DROP TABLE IF EXISTS comment"); 39 | $em->getConnection()->query("CREATE TABLE comment ( 40 | id INTEGER PRIMARY KEY AUTOINCREMENT, 41 | parent_type VARCHAR(50) NOT NULL, 42 | parent_id INTEGER NOT NULL, 43 | author VARCHAR(50) DEFAULT 'Anonymous' NOT NULL, 44 | text TEXT 45 | )"); 46 | 47 | $em->getConnection()->query("DROP TABLE IF EXISTS article"); 48 | $em->getConnection()->query("CREATE TABLE article ( 49 | id INTEGER PRIMARY KEY AUTOINCREMENT, 50 | author VARCHAR(50) DEFAULT 'Anonymous' NOT NULL, 51 | title VARCHAR(255) NOT NULL, 52 | text TEXT 53 | )"); 54 | 55 | $em->getConnection()->query("DROP TABLE IF EXISTS image"); 56 | $em->getConnection()->query("CREATE TABLE image ( 57 | id INTEGER PRIMARY KEY AUTOINCREMENT, 58 | author VARCHAR(50) DEFAULT 'Anonymous' NOT NULL, 59 | url VARCHAR(255) NOT NULL, 60 | caption VARCHAR(255) 61 | )"); 62 | -------------------------------------------------------------------------------- /examples/entities.php: -------------------------------------------------------------------------------- 1 | username)); 19 | } 20 | } 21 | 22 | class Comment extends ORM\Entity 23 | { 24 | protected static $relations = [ 25 | 'parent' => [['parentType' => [ 26 | 'article' => Article::class, 27 | 'image' => Image::class, 28 | ]], ['parentId' => 'id']], 29 | ]; 30 | } 31 | 32 | class Article extends ORM\Entity 33 | { 34 | protected static $relations = [ 35 | 'comments' => [Comment::class, 'parent'], 36 | ]; 37 | } 38 | 39 | class Image extends ORM\Entity 40 | { 41 | protected static $relations = [ 42 | 'comments' => [Comment::class, 'parent'], 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /examples/fetch.php: -------------------------------------------------------------------------------- 1 | fetch(User::class) 17 | ->setQuery("SELECT * FROM user WHERE username = ? AND password = ?", [$username, md5($password)]) 18 | ->one(); 19 | 20 | var_dump($user); 21 | 22 | 23 | /******************************* 24 | * Fetch with where conditions * 25 | *******************************/ 26 | $user = $em->fetch(User::class) 27 | ->where('username', 'LIKE', $username) 28 | ->andWhere('password', '=', md5($password)) 29 | ->one(); 30 | 31 | var_dump($user); 32 | 33 | /******************************************* 34 | * Fetch with parenthesis, group and order * 35 | *******************************************/ 36 | try { 37 | $fetcher = $em->fetch(User::class) 38 | ->where(User::class . '::username LIKE ?', 'USER_A') 39 | ->andWhere('password', '=', md5('password_a')) 40 | ->orParenthesis() 41 | ->where(User::class . '::username = ' . $em->getConnection()->quote('user_b')) 42 | ->andWhere('t0.password = \'' . md5('password_b') . '\'') 43 | ->close() 44 | ->groupBy('id') 45 | ->orderBy( 46 | 'CASE WHEN username = ? THEN 1 WHEN username = ? THEN 2 ELSE 3 END', 47 | 'ASC', 48 | ['user_a', 'user_b'] 49 | ); 50 | $users = $fetcher->all(); 51 | 52 | var_dump($users); 53 | } catch (\PDOException $exception) { 54 | file_put_contents('php://stderr', $exception->getMessage() . "\nSQL:" . $fetcher->getQuery()); 55 | } 56 | 57 | /******************* 58 | * Cache an entity * 59 | *******************/ 60 | $cachedUser = serialize($user); 61 | var_dump($cachedUser); 62 | 63 | /****************************** 64 | * Get previously cached User * 65 | ******************************/ 66 | // lets say we cached user3 with password from user1 - so modify the $cachedUser 67 | $cachedUser = str_replace([ 68 | 's:1:"1"', 69 | 's:6:"user_a"' 70 | ], [ 71 | 's:1:"3"', 72 | 's:6:"user_c"' 73 | ], $cachedUser); 74 | /** @var User $user */ 75 | $user = $em->map(unserialize($cachedUser)); 76 | $user = $em->fetch(User::class, 3); 77 | var_dump($user, $user->isDirty(), $user->isDirty('username')); 78 | 79 | /********************************* 80 | * Get a previously fetched user * 81 | *********************************/ 82 | // sqlite returns strings and currently we do not convert to int 83 | $user1 = $em->fetch(User::class, 1); // queries the database again 84 | $user2 = $em->map(new User(['id' => 1])); 85 | $user3 = $em->fetch(User::class, 1); // returns $user2 86 | $user4 = $em->map(new User(['id' => '1'])); 87 | var_dump($user1->username, $user2->username, $user3 === $user2, $user1 === $user4); 88 | 89 | /******************************** 90 | * Validate data for a new user * 91 | ********************************/ 92 | $data = [ 93 | 'username' => 'This username is way to long for a username', 94 | 'password' => null // null is not allowed 95 | ]; 96 | $result = User::validateArray($data); 97 | echo $result['username']->getMessage() . "\n" . $result['password']->getMessage() . "\n"; 98 | -------------------------------------------------------------------------------- /examples/filter.php: -------------------------------------------------------------------------------- 1 | column = $column; 20 | } 21 | 22 | public function getColumn() 23 | { 24 | return $this->column; 25 | } 26 | 27 | public function prepareSearchTerm($searchTerm) 28 | { 29 | return '%' . $searchTerm . '%'; 30 | } 31 | 32 | public function getOperator() 33 | { 34 | return 'LIKE'; 35 | } 36 | } 37 | 38 | class Text extends SearchColumn 39 | { 40 | } 41 | 42 | class FilterBySearchTerm implements FilterInterface 43 | { 44 | /** @var string[]|SearchColumn[] */ 45 | protected $searchColumns; 46 | 47 | /** @var string */ 48 | private $searchTerm; 49 | 50 | /** 51 | * FilterBySearchTerm constructor. 52 | * @param string[]|SearchColumn[] $searchColumns 53 | * @param string $searchTerm 54 | */ 55 | public function __construct(array $searchColumns, $searchTerm) 56 | { 57 | $this->searchColumns = $searchColumns; 58 | $this->searchTerm = $searchTerm; 59 | } 60 | 61 | public function apply(EntityFetcher $fetcher) 62 | { 63 | $searchTerms = preg_split('/\s+/', $this->searchTerm); 64 | foreach ($searchTerms as $searchTerm) { 65 | $parenthesis = $fetcher->parenthesis(); 66 | foreach ($this->searchColumns as $key => $column) { 67 | if (is_string($column)) { 68 | $column = $this->searchColumns[$key] = new Text($column); 69 | } 70 | $parenthesis->orWhere( 71 | $column->getColumn(), 72 | $column->getOperator(), 73 | $column->prepareSearchTerm($searchTerm) 74 | ); 75 | } 76 | $parenthesis->close(); 77 | } 78 | } 79 | } 80 | 81 | // maybe a stpuid example and you probably want to get the query from the request 82 | $query = 'john doe'; 83 | $fetcher = $em->fetch(User::class)->filter(new FilterBySearchTerm(['username', 'password'], $query)); 84 | // creates a query similar to this: 85 | // SELECT * FROM table 86 | // WHERE (username LIKE '%john%' OR password LIKE '%john%') AND (username LIKE '%doe%' OR password LIKE '%doe%') 87 | -------------------------------------------------------------------------------- /examples/morphed.php: -------------------------------------------------------------------------------- 1 | 'iRaS', 13 | 'title' => 'The advantages of tflori/orm', 14 | 'text' => 'it is pretty obvious why this orm is better than others', 15 | ]); 16 | $article->save(); 17 | $image = new Image([ 18 | 'author' => 'iRaS', 19 | 'url' => 'https://cdn.business2community.com/wp-content/uploads/2013/09/best-press-release-example.jpg', 20 | 'caption' => 'This is just an example', 21 | ]); 22 | $image->save(); 23 | 24 | // create some comments 25 | $texts = [ 26 | 'Quod erat demonstrandum.', 27 | 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', 28 | 'Aenean commodo ligula eget dolor. Aenean massa.', 29 | 'Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.', 30 | 'Er hörte leise Schritte hinter sich. Das bedeutete nichts Gutes.', 31 | 'Weit hinten, hinter den Wortbergen, fern der Länder Vokalien und Konsonantien leben die Blindtexte.', 32 | 'Abgeschieden wohnen sie in Buchstabhausen an der Küste des Semantik, eines großen Sprachozeans.', 33 | '204 § ab dem Jahr 2034 Zahlen in 86 der Texte zur Pflicht werden.', 34 | 'Dies ist ein Typoblindtext.', 35 | 'Vogel Quax zwickt Johnys Pferd Bim.', 36 | ]; 37 | $authors = [ 38 | 'iRaS', 39 | 'cat', 40 | 's1mple', 41 | ]; 42 | foreach ([$article, $image] as $parent) { 43 | $count = mt_rand(2, 5); 44 | $em->useBulkInserts(Comment::class); 45 | for ($i = 0; $i < $count; $i++) { 46 | $comment = new Comment(); 47 | $comment->text = $texts[array_rand($texts)]; 48 | $comment->author = $authors[array_rand($authors)]; 49 | $comment->setRelated('parent', $parent); 50 | $comment->save(); 51 | } 52 | $em->finishBulkInserts(Comment::class); 53 | } 54 | 55 | printf('Article "%s" has %d comments:' . PHP_EOL, $article->title, count($article->comments)); 56 | foreach ($article->comments as $comment) { 57 | printf(' %s: %s' . PHP_EOL, $comment->author, $comment->text); 58 | } 59 | 60 | printf('Image "%s" has %d comments:'. PHP_EOL, $image->caption, count($image->comments)); 61 | foreach ($image->comments as $comment) { 62 | printf(' %s: %s'. PHP_EOL, $comment->author, $comment->text); 63 | } 64 | -------------------------------------------------------------------------------- /phpdoc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /tmp/phpdoc/orm 5 | 6 | 7 | docs 8 | 9 | 10 |