├── .codeclimate.yml
├── .coveralls.yml
├── .csslintrc
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── build
└── logs
│ └── .gitignore
├── composer.json
├── doc
├── filemaker
│ ├── .gitignore
│ ├── FMServer_Sample_web.fmp12
│ ├── README.md
│ ├── XmlSchemaDemo.fmp12
│ ├── composer.json
│ ├── fms15_cwp_guide-XML-grammar.pdf
│ ├── fms15_cwp_guide.pdf
│ ├── fms15_getting_started.pdf
│ └── simplefm_example.php
└── src
│ ├── authentication.md
│ ├── debugging.md
│ ├── getting-started.md
│ ├── index.md
│ ├── layout-information.md
│ └── repositories
│ ├── automatic-creation.md
│ ├── introduction.md
│ ├── manual-creation.md
│ ├── metadata.md
│ └── working-with-repositories.md
├── mkdocs.yml
├── phpcs.xml
├── phpunit.xml.dist
├── src
├── Authentication
│ ├── Authenticator.php
│ ├── BlockCipherIdentityHandler.php
│ ├── Exception
│ │ ├── ExceptionInterface.php
│ │ └── InvalidResultException.php
│ ├── Identity.php
│ ├── IdentityHandlerInterface.php
│ └── Result.php
├── Client
│ ├── Exception
│ │ ├── ExceptionInterface.php
│ │ └── FileMakerException.php
│ ├── Layout
│ │ ├── Field.php
│ │ ├── Layout.php
│ │ ├── LayoutClient.php
│ │ ├── LayoutClientInterface.php
│ │ ├── Value.php
│ │ └── ValueList.php
│ └── ResultSet
│ │ ├── Exception
│ │ ├── ExceptionInterface.php
│ │ ├── ParseException.php
│ │ └── UnknownFieldException.php
│ │ ├── ResultSetClient.php
│ │ ├── ResultSetClientInterface.php
│ │ └── Transformer
│ │ ├── ContainerTransformer.php
│ │ ├── DateTimeTransformer.php
│ │ ├── DateTransformer.php
│ │ ├── Exception
│ │ ├── DateTimeException.php
│ │ └── ExceptionInterface.php
│ │ ├── NumberTransformer.php
│ │ ├── StreamProxy.php
│ │ ├── TextTransformer.php
│ │ └── TimeTransformer.php
├── Collection
│ ├── CollectionInterface.php
│ └── ItemCollection.php
├── Connection
│ ├── Command.php
│ ├── Connection.php
│ ├── ConnectionInterface.php
│ └── Exception
│ │ ├── DomainException.php
│ │ ├── ExceptionInterface.php
│ │ └── InvalidResponseException.php
└── Repository
│ ├── Builder
│ ├── Exception
│ │ ├── ExceptionInterface.php
│ │ ├── ExtractionException.php
│ │ └── HydrationException.php
│ ├── Metadata
│ │ ├── Embeddable.php
│ │ ├── Entity.php
│ │ ├── Exception
│ │ │ ├── ExceptionInterface.php
│ │ │ ├── InvalidFileException.php
│ │ │ ├── InvalidTypeException.php
│ │ │ └── MissingInterfaceException.php
│ │ ├── Field.php
│ │ ├── ManyToOne.php
│ │ ├── MetadataBuilder.php
│ │ ├── MetadataBuilderInterface.php
│ │ ├── OneToMany.php
│ │ ├── OneToOne.php
│ │ └── RecordId.php
│ ├── MetadataExtraction.php
│ ├── MetadataHydration.php
│ ├── Proxy
│ │ ├── Exception
│ │ │ ├── ExceptionInterface.php
│ │ │ └── InvalidInterfaceException.php
│ │ ├── ProxyBuilder.php
│ │ ├── ProxyBuilderInterface.php
│ │ └── ProxyInterface.php
│ ├── RepositoryBuilder.php
│ ├── RepositoryBuilderInterface.php
│ └── Type
│ │ ├── BooleanType.php
│ │ ├── DateTimeType.php
│ │ ├── DateType.php
│ │ ├── DecimalType.php
│ │ ├── Exception
│ │ ├── DomainException.php
│ │ └── ExceptionInterface.php
│ │ ├── FloatType.php
│ │ ├── IntegerType.php
│ │ ├── NullableStringType.php
│ │ ├── StreamType.php
│ │ ├── StringType.php
│ │ ├── TimeType.php
│ │ └── TypeInterface.php
│ ├── Exception
│ ├── DomainException.php
│ ├── ExceptionInterface.php
│ └── InvalidResultException.php
│ ├── ExtractionInterface.php
│ ├── HydrationInterface.php
│ ├── LazyLoadedCollection.php
│ ├── Query
│ ├── Exception
│ │ ├── EmptyQueryException.php
│ │ ├── ExceptionInterface.php
│ │ └── InvalidArgumentException.php
│ ├── FindQuery.php
│ └── Query.php
│ ├── Repository.php
│ └── RepositoryInterface.php
├── test
├── Authentication
│ ├── AuthenticationTest.php
│ ├── BlockCipherIdentityHandlerTest.php
│ ├── IdentityTest.php
│ └── ResultTest.php
├── Client
│ ├── Layout
│ │ ├── LayoutClientTest.php
│ │ └── TestAssets
│ │ │ ├── error_fmpxmllayout.xml
│ │ │ └── sample_fmpxmllayout.xml
│ └── ResultSet
│ │ ├── ResultSetClientTest.php
│ │ ├── TestAssets
│ │ ├── ParentChildAssociations
│ │ │ ├── Base-recid1-id2.xml
│ │ │ ├── Base-recid2-id3.xml
│ │ │ ├── Base_sparse-collection.xml
│ │ │ ├── Child-recid1-id1.xml
│ │ │ ├── Child-recid2-id2.xml
│ │ │ ├── Child-recid3-id3.xml
│ │ │ └── Parent-recid1-id2.xml
│ │ ├── deleted-field.xml
│ │ ├── duplicateportals.xml
│ │ ├── invalid.xml
│ │ ├── invalidFieldTypeDate.xml
│ │ ├── invalidFieldTypeFake.xml
│ │ ├── invalidFieldTypeRepeatingNumber.xml
│ │ ├── invalidFieldTypeTime.xml
│ │ ├── invalidFieldTypeTimestamp.xml
│ │ ├── invalidFieldTypes.xml
│ │ ├── projectsampledata.xml
│ │ ├── reservedfieldname.xml
│ │ ├── sample_fmresultset.xml
│ │ ├── sample_fmresultset_empty.xml
│ │ └── sample_fmresultset_fmerror4.xml
│ │ └── Type
│ │ ├── NumberTransformerTest.php
│ │ └── StreamProxyTest.php
├── Collection
│ └── ItemCollectionTest.php
├── Connection
│ ├── CommandTest.php
│ └── ConnectionTest.php
└── Repository
│ ├── Builder
│ ├── Metadata
│ │ ├── EmbeddableTest.php
│ │ ├── EntityTest.php
│ │ ├── FieldTest.php
│ │ ├── ManyToOneTest.php
│ │ ├── MetadataBuilderTest.php
│ │ ├── OneToManyTest.php
│ │ ├── OneToOneTest.php
│ │ ├── RecordIdTest.php
│ │ └── TestAssets
│ │ │ ├── BuiltInTypes.xml
│ │ │ ├── CustomType.xml
│ │ │ ├── EagerHydration.xml
│ │ │ ├── Embeddable.xml
│ │ │ ├── EmbeddedEntity.xml
│ │ │ ├── Empty.xml
│ │ │ ├── InterfaceName.xml
│ │ │ ├── Invalid.Xml.xml
│ │ │ ├── ManyToOne.xml
│ │ │ ├── ManyToOneWithoutInterface.xml
│ │ │ ├── Non.Xml.xml
│ │ │ ├── OneToMany.xml
│ │ │ ├── OneToOneInverse.xml
│ │ │ ├── OneToOneOwning.xml
│ │ │ ├── ReadOnly.xml
│ │ │ ├── RecordId.xml
│ │ │ ├── RelationTarget.xml
│ │ │ ├── RelationTargetWithoutInterface.xml
│ │ │ └── Repeatable.xml
│ ├── MetadataExtractionTest.php
│ ├── MetadataHydrationTest.php
│ ├── Proxy
│ │ ├── ProxyBuilderTest.php
│ │ └── TestAssets
│ │ │ ├── ComplexSetterInterface.php
│ │ │ ├── SimpleGetterInterface.php
│ │ │ ├── SimpleSetterInterface.php
│ │ │ └── VariadicSetterInterface.php
│ ├── RepositoryBuilderTest.php
│ ├── TestAssets
│ │ ├── EmptyEntityInterface.php
│ │ └── EmptyProxyEntityInterface.php
│ └── Type
│ │ ├── BooleanTypeTest.php
│ │ ├── DateTimeTypeTest.php
│ │ ├── DateTypeTest.php
│ │ ├── DecimalTypeTest.php
│ │ ├── FloatTypeTest.php
│ │ ├── IntegerTypeTest.php
│ │ ├── NullableStringTypeTest.php
│ │ ├── StreamTypeTest.php
│ │ ├── StringTypeTest.php
│ │ └── TimeTypeTest.php
│ ├── LazyLoadedCollectionTest.php
│ ├── Query
│ ├── FindQueryTest.php
│ └── QueryTest.php
│ └── RepositoryTest.php
└── xsd
├── entity-metadata-5-0.xsd
└── entity-metadata-5-1.xsd
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | csslint:
4 | enabled: true
5 | duplication:
6 | enabled: true
7 | config:
8 | languages:
9 | - ruby
10 | - javascript
11 | - python
12 | - php
13 | fixme:
14 | enabled: true
15 | phpmd:
16 | enabled: true
17 | ratings:
18 | paths:
19 | - "**.css"
20 | - "**.inc"
21 | - "**.js"
22 | - "**.jsx"
23 | - "**.module"
24 | - "**.php"
25 | - "**.py"
26 | - "**.rb"
27 | exclude_paths:
28 | - documentation/*
29 | - tests/*
30 | - vendor/*
31 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | coverage_clover: clover.xml
2 | json_path: coveralls-upload.json
3 |
--------------------------------------------------------------------------------
/.csslintrc:
--------------------------------------------------------------------------------
1 | --exclude-exts=.min.css
2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /composer.lock
3 | /composer.phar
4 | /clover.xml
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: php
4 |
5 | cache:
6 | directories:
7 | - $HOME/.composer/cache
8 | - $HOME/.local
9 | - vendor
10 |
11 | matrix:
12 | fast_finish: true
13 | include:
14 | - php: 7
15 | env:
16 | - EXECUTE_CS_CHECK=true
17 | - EXECUTE_TEST_COVERALLS=true
18 | - EXECUTE_TEST_CODECLIMATE=true
19 | - PATH="$HOME/.local/bin:$PATH"
20 | - php: nightly
21 | - php: hhvm
22 | allow_failures:
23 | - php: nightly
24 | - php: hhvm
25 |
26 | before_install:
27 | - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]] && [[ $EXECUTE_TEST_CODECLIMATE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi
28 | - composer self-update
29 | - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer require --dev --no-update satooshi/php-coveralls:^1.0.0 ; fi
30 | - if [[ $EXECUTE_TEST_CODECLIMATE == 'true' ]]; then composer require --dev --no-update codeclimate/php-test-reporter:^0.3.2 ; fi
31 |
32 | install:
33 | - travis_retry composer install --no-interaction
34 | - composer info -i
35 |
36 | script:
37 | - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]] || [[ $EXECUTE_TEST_CODECLIMATE == 'true' ]]; then composer test-coverage ; fi
38 | - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then composer test ; fi
39 | - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then composer cs ; fi
40 |
41 | after_script:
42 | - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer coveralls ; fi
43 | - if [[ $EXECUTE_TEST_CODECLIMATE == 'true' ]]; then CODECLIMATE_REPO_TOKEN=a1fa5b53529cf77226b6231a9c0a3d8ce5c0cb9ecc570ac014c1cc60fa07d2df ./vendor/bin/test-reporter --coverage-report=clover.xml --stdout > codeclimate.json ; fi
44 | - "if [[ $EXECUTE_TEST_CODECLIMATE == 'true' ]]; then curl -X POST -d @codeclimate.json -H 'Content-Type: application/json' -H 'User-Agent: Code Climate (PHP Test Reporter v0.1.1)' https://codeclimate.com/test_reports ; fi"
45 |
46 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | We love pull requests from everyone! Here are some basic tips and tricks for constructive contribution.
4 |
5 | ## Fork, Clone and Install
6 |
7 | Fork, then clone the repo:
8 |
9 | git clone git@github.com:your-username/SimpleFM.git
10 |
11 | If you don't have composer installed in your path already, you can install it inside the cloned project with this
12 | command:
13 |
14 | php -r "readfile('https://getcomposer.org/installer');" | php
15 |
16 | We suggest you always use [Composer](https://getcomposer.org/) `update` in the project (as opposed to `install`).
17 | Since this is a library, we `.gitignore` both `composer.phar` and `composer.lock`:
18 |
19 | php ./composer.phar update
20 |
21 | ## Unit Tests and Coding Style Tests
22 |
23 | The project is setup to run all the PHPUnit and PHPCS tests via
24 | [Apache ant](http://ant.apache.org/manual/install.html). (See `build.xml` in the project root.)
25 |
26 | Before you start make sure the tests run using ant:
27 |
28 | ant
29 |
30 | Alternatively, make sure the tests run like this if you don't want to use ant:
31 |
32 | php ./vendor/bin/phpunit -c tests/phpunit.xml.dist
33 | php ./vendor/bin/phpcs -np --standard=PSR2 library/ tests/
34 |
35 | ## Branch, Change and Test
36 |
37 | Before you make your changes, please create a new branch. Example:
38 |
39 | git checkout -b feature/my-thing
40 |
41 | Make your change. Add tests for your change. Make sure the tests pass (or run both manually as above):
42 |
43 | ant
44 |
45 | ## Create Pull Request
46 |
47 | Push your new branch to your fork and [submit a pull request][pr].
48 |
49 | [pr]: https://github.com/soliantconsulting/SimpleFM/compare/
50 |
51 | At this point you're waiting on us. We try to at least comment on pull requests within three business days (and,
52 | typically, one business day). If you don't get any response within three days, feel free to bump it with a comment. We
53 | may suggest some changes or improvements or alternatives.
54 |
55 | ## Tips
56 |
57 | Some things that will increase the chance that your pull request is accepted:
58 |
59 | * Don't break existing tests.
60 | * Write test coverage for your change(s).
61 | * Follow the PSR-2 [coding standards][style].
62 | * Write [good commit messages][commit].
63 | * Explain and/or justify the reason for the change in your PR description.
64 |
65 | [style]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
66 | [commit]: https://git-scm.com/book/ch5-2.html#Commit-Guidelines
67 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2007-2015 Soliant Consulting, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
9 | Software, and to permit persons to whom the Software is furnished to do so,
10 | 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 SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SimpleFM
2 |
3 | [](https://travis-ci.org/soliantconsulting/SimpleFM)
4 | [](https://codeclimate.com/github/soliantconsulting/SimpleFM)
5 | [](https://codeclimate.com/github/soliantconsulting/SimpleFM/coverage)
6 | [](https://packagist.org/packages/soliantconsulting/simplefm)
7 | [](https://packagist.org/packages/soliantconsulting/simplefm)
8 | [](https://packagist.org/packages/soliantconsulting/simplefm)
9 | [](https://packagist.org/packages/soliantconsulting/simplefm)
10 |
11 | SimpleFM is a fast, convenient and free tool designed by [Soliant Consulting, Inc.][1] to facilitate connections between PHP web applications and FileMaker Server.
12 |
13 | SimpleFM is a lightweight PHP package that uses the [FileMaker Server][2] XML API. The FMS XML API is commonly referred to as Custom Web Publishing (CWP for short).
14 |
15 | SimpleFM is [Composer][3] friendly, making it a snap to use with all [PHP-FIG][4] frameworks, including [Zend Framework][5], Symfony, Laravel, Slim, and many more.
16 |
17 | See also, the [SimpleFM-skeleton][6] demo application which illustrates use of SimpleFM in a middleware Zend Framework application.
18 |
19 | ## Features
20 |
21 | ### Easy to Integrate
22 |
23 | - PSR-4 autoloading ([Composer][3] ready).
24 | - Can be used on it's own or with any service or middleware, such as [Apigility][7] or Stratigility.
25 |
26 | ### CWP Debugger
27 |
28 | - Easily see the underlying API command formatted as a URL for easy troubleshooting
29 | - FileMaker error codes are translated to understandable error messages
30 |
31 | ## Simplicity and Performance
32 |
33 | SimpleFM was written with simplicity as the guiding principle. We have informally benchmarked it, and obtained faster results for the same queries compared to the two most common CWP PHP alternatives.
34 |
35 | ## System Requirements
36 |
37 | SimpleFM, the examples and this documentation are tailored for PHP 7.0 and FileMaker Sever 12
38 |
39 | - PHP 7.0+
40 | - FileMaker Server 12+
41 |
42 | With minimum effort, it should theoretically work with any version of FileMaker server that uses fmresultset.xml grammar, however, backward compatibility is not verified or maintained.
43 |
44 | ## License
45 |
46 | SimpleFM is free for commercial and non-commercial use, licensed under the business-friendly standard MIT license.
47 |
48 | ## Installation
49 |
50 | Install via composer:
51 |
52 | ```bash
53 | composer require soliantconsulting/simplefm
54 | ```
55 |
56 | ## Documentation
57 |
58 | Documentation builds are available at:
59 |
60 | - https://simplefm.readthedocs.org
61 |
62 | You can also build the documentation locally via [MkDocs](http://www.mkdocs.org):
63 |
64 | ```bash
65 | $ mkdocs serve
66 | ```
67 |
68 | [1]: http://www.soliantconsulting.com
69 | [2]: http://www.filemaker.com/products/filemaker-server/
70 | [3]: https://getcomposer.org/doc/00-intro.md
71 | [4]: http://www.php-fig.org/
72 | [5]: http://framework.zend.com/
73 | [6]: https://github.com/soliantconsulting/SimpleFM-skeleton
74 | [7]: https://apigility.org/
75 |
--------------------------------------------------------------------------------
/build/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "soliantconsulting/simplefm",
3 | "type": "library",
4 | "description": "FileMaker Server XML API Adapter",
5 | "keywords": [
6 | "FileMaker",
7 | "adapter"
8 | ],
9 | "homepage": "http://github.com/soliantconsulting/SimpleFM",
10 | "license": "MIT",
11 | "authors": [
12 | {"name": "Jeremiah Small", "email": "jsmall@soliantconsulting.com"},
13 | {"name": "Ben Scholzen", "email": "bscholzen@soliantconsulting.com"}
14 | ],
15 | "require": {
16 | "php": "^7.0",
17 | "php-http/httplug": "^1.1",
18 | "zendframework/zend-diactoros": "^1.3",
19 | "litipk/php-bignumbers": "^0.8.4",
20 | "beberlei/assert": "^2.6",
21 | "psr/log": "^1.0",
22 | "zendframework/zend-code": "^3.0",
23 | "psr/cache": "^1.0",
24 | "cache/void-adapter": "^0.3.1",
25 | "cache/adapter-common": "^0.3.3"
26 | },
27 | "require-dev": {
28 | "zendframework/zend-crypt": "^3.1",
29 | "phpunit/phpunit": "^5.5",
30 | "squizlabs/php_codesniffer": "^2.6"
31 | },
32 | "suggest": {
33 | "zendframework/zend-crypt": "For using the block chain identity handler"
34 | },
35 | "autoload": {
36 | "psr-4": {
37 | "Soliant\\SimpleFM\\": "src/"
38 | }
39 | },
40 | "autoload-dev": {
41 | "psr-4": {
42 | "SoliantTest\\SimpleFM\\": "test/"
43 | }
44 | },
45 | "scripts": {
46 | "check": [
47 | "@cs",
48 | "@test"
49 | ],
50 | "coveralls": "coveralls",
51 | "cs": "phpcs",
52 | "cs-fix": "phpcbf",
53 | "test": "phpunit",
54 | "test-coverage": "phpunit --coverage-clover clover.xml",
55 | "html": "phpunit --coverage-html build/logs/coverage.html",
56 | "deploy-xsd": "git subtree push --prefix xsd origin gh-pages"
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/doc/filemaker/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | composer.lock
3 | vendor
4 |
--------------------------------------------------------------------------------
/doc/filemaker/FMServer_Sample_web.fmp12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soliantconsulting/SimpleFM/ab453bc87f8ca61ed3fa75ee61a8ab5851fc9fe7/doc/filemaker/FMServer_Sample_web.fmp12
--------------------------------------------------------------------------------
/doc/filemaker/README.md:
--------------------------------------------------------------------------------
1 | Parent Child Association Test Data
2 | ==================================
3 |
4 | Data Model
5 | ----------
6 |
7 | ```
8 |
9 | -------- ------ -------
10 | | Parent | -< | Base | -< | Child |
11 | -------- ------ -------
12 |
13 | ```
14 |
15 | Static XML Result Test Assets
16 | -----------------------------
17 |
18 | ```
19 | tests/SoliantTest/SimpleFM/TestAssets/ParentChildAssociations
20 | ```
21 |
22 | Example FileMaker File
23 | ----------------------
24 |
25 | ```
26 | documentation/XmlSchemaDemo.fmp12
27 | ```
28 | Developer credentials are admin:admin
29 |
--------------------------------------------------------------------------------
/doc/filemaker/XmlSchemaDemo.fmp12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soliantconsulting/SimpleFM/ab453bc87f8ca61ed3fa75ee61a8ab5851fc9fe7/doc/filemaker/XmlSchemaDemo.fmp12
--------------------------------------------------------------------------------
/doc/filemaker/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "soliantconsulting/simplefm-example",
3 | "type": "project",
4 | "description": "FileMaker Server XML API Adapter",
5 | "keywords": [
6 | "FileMaker",
7 | "adapter"
8 | ],
9 | "homepage": "http://github.com/soliantconsulting/SimpleFM",
10 | "license": "MIT",
11 | "authors": [
12 | {"name": "Jeremiah Small", "email": "jsmall@soliantconsulting.com"}
13 | ],
14 | "require": {
15 | "soliantconsulting/simplefm": "dev-develop",
16 | "php-http/curl-client": "^1.6",
17 | "monolog/monolog": "^1.9"
18 | },
19 | "scripts": {
20 | "serve": "echo run it manually: php -S 0.0.0.0:8080 -t ./ ./simplefm_example.php"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/doc/filemaker/fms15_cwp_guide-XML-grammar.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soliantconsulting/SimpleFM/ab453bc87f8ca61ed3fa75ee61a8ab5851fc9fe7/doc/filemaker/fms15_cwp_guide-XML-grammar.pdf
--------------------------------------------------------------------------------
/doc/filemaker/fms15_cwp_guide.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soliantconsulting/SimpleFM/ab453bc87f8ca61ed3fa75ee61a8ab5851fc9fe7/doc/filemaker/fms15_cwp_guide.pdf
--------------------------------------------------------------------------------
/doc/filemaker/fms15_getting_started.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soliantconsulting/SimpleFM/ab453bc87f8ca61ed3fa75ee61a8ab5851fc9fe7/doc/filemaker/fms15_getting_started.pdf
--------------------------------------------------------------------------------
/doc/src/authentication.md:
--------------------------------------------------------------------------------
1 | # Authenticating a user
2 |
3 | SimpleFM supplies you with the tools to authenticate a user against the server an run requests under their permissions.
4 | Since the user's name and password are required for making requests on their behalf, it needs to be stored temporarily.
5 | When you authenticate the user against the server, it will return an identity which contains both the username and the
6 | password in an encrypted form. An identity handler is responsible for encrypting and decrypting the password, and while
7 | you can implement your own one, SimpleFM supplies a block chipher identity handler based on `Zend\Crypt`.
8 |
9 | Using the built-in identity handler, you can quickly create an authenticator like this:
10 |
11 | ```
12 | 'aes'])->setKey('some-strong-encryption-key')
19 | );
20 |
21 | $authenticator = new Authenticator(
22 | $resultSetClient,
23 | $identityHandler,
24 | 'identity-layout',
25 | 'username-field'
26 | );
27 | ```
28 |
29 | With the authenticator created you are able to authenticator the user against it. It will return a `Result` object,
30 | which will contain the identity on success:
31 |
32 | ```php
33 | authenticate($username, $password);
35 |
36 | if (!$result->isSuccess()) {
37 | // Prompt the user to enter their credentials again
38 | }
39 |
40 | $identity = $result->getIdentity();
41 | ```
42 |
43 | You should store the returned identity, either in a session or in a cookie. Now before you can make requests with that
44 | identity, you have to pass the identity handler to the connection first. To do so, you pass it as fourth parameter to
45 | the constructor:
46 |
47 | ```php
48 | execute(
65 | (new Command('sample-layout', ['-findall' => null]))->withIdentity($identity)
66 | );
67 | ```
68 |
69 | This would return all records which the user has permission to see. Since you will normally not run commands directly
70 | but via a repository, you'd want to run requests through those instead:
71 |
72 | ```php
73 | withIdentity($identity)->findAll();
75 | ```
76 |
77 | As any object within SimpleFM is immutable, the `withIdentity()` method actually returns a clone of the repository with
78 | the identity set to it. You can re-use that clone if you need to run multiple requests.
79 |
80 |
--------------------------------------------------------------------------------
/doc/src/debugging.md:
--------------------------------------------------------------------------------
1 | # Debugging requests
2 |
3 | Sometimes it can be really hard to debug a request without looking at the actual XML result directly. To make this task
4 | a little easier, you can provide a PSR-3 compliant logger to the connection. This will result in each request URL being
5 | logged, with the request parameters appended in the query.
6 |
7 | Many frameworks and libraries provide loggers implementing PSR-3, so you have a wide variety to choose from. In the
8 | following example we will use the PSR-3 adapter of `Zend\Log`:
9 |
10 | ```php
11 | [
19 | new Stream('/path/to/logfile')
20 | ],
21 | ])
22 | );
23 |
24 | $connection = new Connection(
25 | $httpClient,
26 | $uri,
27 | $databsae,
28 | null,
29 | $logger
30 | );
31 | ```
32 |
33 | !!!note
34 | Since a logger will cause I/O for every single request made against the FileMaker Server, you should **not** enable
35 | this in production.
36 |
--------------------------------------------------------------------------------
/doc/src/getting-started.md:
--------------------------------------------------------------------------------
1 | # Creating a connection
2 |
3 | The first thing you want to do is to create a connection which can be used with either the layout client or the
4 | result set client. While you can always implement your own connection class by implementing the `ConnectionInterface`,
5 | most of the time the built-in one should be sufficient.
6 |
7 | To create an instance of a connection, you need an instance of an HTTP client implementing the
8 | [`HttpClient` interface](https://github.com/php-http/httplug/blob/master/src/HttpClient.php). There are many different
9 | implementations already created, so you can choose [one from php-http](https://github.com/php-http). For the sake of
10 | simplicity, we are going with the CURL client here.
11 |
12 | The next thing you need is a PSR-7 URI object. Again, there are many different implementations for that out in the
13 | world, and we will use the one shipped with `Zend\Diactoros`. The URI object should contain the scheme, host and
14 | optionally a port and an authority part.
15 |
16 | ```php
17 | execute(new Command('sample-layout', ['-findall' => null]));
54 |
55 | foreach ($records as $record) {
56 | // Do something with the record.
57 | }
58 | ```
59 |
60 |
--------------------------------------------------------------------------------
/doc/src/index.md:
--------------------------------------------------------------------------------
1 | ../../README.md
--------------------------------------------------------------------------------
/doc/src/layout-information.md:
--------------------------------------------------------------------------------
1 | # Retrieving information about a layout
2 |
3 | Sometimes you need to programatically retrieve information about a specific layout, like the available fields, field set
4 | options and the like. For this purpose SimpleFM provides a layout client, which can be used similary to the result set
5 | client:
6 |
7 | ```php
8 | execute(new Command(
14 | 'sample-layout',
15 | ['-view' => null]
16 | ));
17 | ```
18 |
19 | The result will be a `Layout` object, which contains all information about the layout and its fields:
20 |
21 | ```php
22 | getDatabase());
24 | sprintf("Layout: %s\n", $sampleLayout->getName());
25 |
26 | print "Fields:\n";
27 |
28 | foreach ($sampleLayout->getFields() as $field) {
29 | sprintf(" Name: %s\n", $field->getName());
30 | sprintf(" Type: %s\n", $field->getType());
31 |
32 | if (!$field->hasValueList()) {
33 | continue;
34 | }
35 |
36 | $valueList = $field->getValueList();
37 | sprintf(" Value list (%s):\n", $valueList->getName());
38 |
39 | foreach ($valueList->getValues() as $value) {
40 | sprintf(" %s: %s\n", $value->getValue(), $value->getDisplay());
41 | }
42 | }
43 | ```
44 |
45 | You can also retrieve individual fields instead of looping over all fields:
46 |
47 | ```php
48 | hasField('foo')) {
50 | $field = $sampleLayout->getField('foo');
51 | }
52 | ```
53 |
54 |
--------------------------------------------------------------------------------
/doc/src/repositories/automatic-creation.md:
--------------------------------------------------------------------------------
1 | # Getting rid of manual code
2 |
3 | While it is feasible to create all your repositories manully, it can become a daunting task to do this for a lot of
4 | entities. Thus it is recommended to only define the metadata about your entity, and let SimpleFM create the
5 | repositories and their hydration and extraction strategies for you automatically.
6 |
7 | # Defining your metadata file
8 |
9 | The first thing you want to do is to create your metadata file. This is a simple XML file which describes all fields
10 | and relations of the entity in the given layout. You should choose one folder in which you place all those XML files,
11 | and each XML file must have a filename which equals the class name of the entity, with the namespace delimiters replaced
12 | by periods. So when your entity is named `My\Entity\SampleEntity`, the acccording filename would be
13 | `My.Entity.SampleEntity.xml`:
14 |
15 | ```xml
16 |
17 |
25 |
26 |
27 | ```
28 |
29 | You can read more about metadata definitions in the the [Metadata chapter](repositories/metadata.md)
30 |
31 | # Creating a repository builder
32 |
33 | Now that we have our metadata defined, we only need to instantiate a repository builder for it:
34 |
35 | ```php
36 | buildRepository(SampleEntity::class);
55 | ```
56 |
--------------------------------------------------------------------------------
/doc/src/repositories/introduction.md:
--------------------------------------------------------------------------------
1 | # Easier access to data
2 |
3 | While very simple applications can work fine just using the result set client, it is a best practice to organize data
4 | into entities with associations. To facilitate this, SimpleFM provides repositories which take care of the internals
5 | and let the application developer concentrate on the business domain.
6 |
7 | A repository instance is responsible for a single type of entity. It is where you define the infrastructure to insert,
8 | update, and delete an entity, as well as giving you multiple ways to query.
9 |
10 | The FileMaker XML API exposes data via layouts. We'll come back to this in more detail later, but it's important to know
11 | that entities, repositories, and layouts must be created and maintained in concert with each other.
12 |
--------------------------------------------------------------------------------
/doc/src/repositories/manual-creation.md:
--------------------------------------------------------------------------------
1 | # Creating a hydrator and extractor
2 |
3 | For creating a repository completely manually, you have to define a hydrator and an extractor which take care of
4 | converting an entity to record data and visa-versa. Let's say that you have a layout with a simple `name` field, these
5 | could look something like this:
6 |
7 | ```php
8 | setName($data['name']);
22 | }
23 | }
24 |
25 | final class SampleExtraction implements ExtractionInterface
26 | {
27 | public function extract($entity) : array
28 | {
29 | return [
30 | 'name' => $entity->getName(),
31 | ];
32 | }
33 | }
34 | ```
35 |
36 | # Initializing a repository
37 |
38 | With these two classes and your result set client in place, you can craft your very first repository:
39 |
40 | ```php
41 | insert(new SampleEntity('test'));
9 | ```
10 |
11 | Similary, when you want to update the record, you'd call the `update()` method:
12 |
13 | ```php
14 | update($changedSampleEntity);
16 | ```
17 |
18 | In the same way, you can also delete a record from the layout:
19 |
20 | ```php
21 | delete($oldSampleEntity);
23 | ```
24 |
25 | Please note that both `update()` and `delete()` only work with entities which you retrieved via the repository (also
26 | known as managed entities). If you try to pass an entity to those methods which is not known by the repository, you will
27 | get an exception. By default, both `update()` and `delete()` will instruct the FileMaker Server to only execute that
28 | command when the `mod-id` is the same as the one from the local entity to avoid changing records which were modified in
29 | another place. You can disable that behaviour by passing `true` to the second `$force` parameter of those methods.
30 |
31 | # Retrieving entities through a simple search
32 |
33 | Repositories offer multiple ways, to retrieve entites. The simplest one is by calling the `find()` method with a record
34 | ID of the record you want to retrieve. The result will either be an instance of that entity or null:
35 |
36 | ```php
37 | find(1);
39 | ```
40 |
41 | Similary, there is a `findBy()` and a `findOneBy()` method for either retrieving multiple records or a single one with
42 | a simple field search. Both methods take an array field search values, where the array key is the field name and the
43 | array value is the actual search. By default, the search value is automatically quoted. If you need to perform more
44 | advanced searches, you can pass `false` to the `$autoQuoteSearch` parameter and do required quoting manually by passing
45 | the value to quote to the `quoteString()` method. For details about the different search options, please refer to the
46 | FileMaker documentation.
47 |
48 | If you only want to retrieve all records from the layout without filterinig, there's also a `findAll()` method. All
49 | methods which return multiple records also accept a `$sort` parameter for sorting the results, as well as a `$limit` and
50 | `$offset` parameter.
51 |
52 | # Performing complex searches
53 |
54 | While the `findBy()` and `findOneBy()` methods will always perform an `AND` query. something you may want to make `OR`
55 | queries or mixed queries. To allow this, the repository exposes two methods, namely `findByQuery()` and
56 | `findOneByQuery()`. Both work similar to the prior two methods, but instead of a search array they take a `FindQuery`
57 | object:
58 |
59 | ```php
60 | addOrQueries(
66 | new Query('ID', '1'),
67 | new Query('ID', '2'),
68 | );
69 | $query->addAndQueries(
70 | new Query('Name', 'foo'),
71 | new Query('Status', 'closed', true)
72 | );
73 |
74 | $sampleEntities = $respository->findByQuery($query);
75 | ```
76 |
77 | This will query will look for any record which `ID` is either 1 or 2 and which `Name` equals "foo", but the `Status`
78 | field must not be "closed".
79 |
80 | !!!note "Query quoting"
81 | Values in a find query are never quoted automatically, so you'll need to quote those values manually via the
82 | `quoteString()` method of the repository.
83 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: SimpleFM
2 | repo_url: https://github.com/soliantconsulting/SimpleFM/
3 | copyright: Copyright (c) 2007-2016 Soliant Consulting, Inc.
4 | docs_dir: doc/src
5 | site_dir: doc/html
6 | theme: readthedocs
7 | pages:
8 | - index.md
9 | - 'Getting Started' : getting-started.md
10 | - 'Repositories' :
11 | [
12 | 'Introduction' : repositories/introduction.md,
13 | 'Manual creation' : repositories/manual-creation.md,
14 | 'Automatic creation' : repositories/automatic-creation.md,
15 | 'Metadata' : repositories/metadata.md,
16 | 'Working with repositories' : repositories/working-with-repositories.md,
17 | ]
18 | - 'Authentication' : authentication.md
19 | - 'Debugging' : debugging.md
20 | - 'Layout Information' : layout-information.md
21 |
22 | markdown_extensions:
23 | - admonition:
24 | - def_list:
25 | - footnotes:
26 |
27 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SimpleFM coding standard
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | src
20 | test
21 |
22 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | ./test
9 |
10 |
11 |
12 |
13 |
14 | src
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Authentication/Authenticator.php:
--------------------------------------------------------------------------------
1 | resultSetClient = $resultSetClient;
40 | $this->identityHandler = $identityHandler;
41 | $this->identityLayout = $identityLayout;
42 | $this->usernameField = $usernameField;
43 | }
44 |
45 | public function authenticate(string $username, string $password) : Result
46 | {
47 | $identity = $this->identityHandler->createIdentity($username, $password);
48 |
49 | try {
50 | $resultSet = $this->resultSetClient->execute(
51 | (new Command($this->identityLayout, [
52 | $this->usernameField => '==' . $this->resultSetClient->quoteString($username),
53 | '-find' => null,
54 | ]))->withIdentity($identity)
55 | );
56 | } catch (InvalidResponseException $e) {
57 | $errorCode = $e->getCode();
58 |
59 | if (401 === $errorCode) {
60 | return Result::fromInvalidCredentials();
61 | }
62 |
63 | throw $e;
64 | }
65 |
66 | if ($resultSet->isEmpty()) {
67 | throw InvalidResultException::fromEmptyResultSet();
68 | }
69 |
70 | return Result::fromIdentity($identity);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Authentication/BlockCipherIdentityHandler.php:
--------------------------------------------------------------------------------
1 | blockCipher = $blockCipher;
18 | }
19 |
20 | public function createIdentity(string $username, string $password) : Identity
21 | {
22 | return new Identity(
23 | $username,
24 | $this->blockCipher->encrypt($password)
25 | );
26 | }
27 |
28 | public function decryptPassword(Identity $identity) : string
29 | {
30 | return $this->blockCipher->decrypt($identity->getEncryptedPassword());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Authentication/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | username = $username;
21 | $this->encryptedPassword = $encryptedPassword;
22 | }
23 |
24 | public function getUsername() : string
25 | {
26 | return $this->username;
27 | }
28 |
29 | public function getEncryptedPassword() : string
30 | {
31 | return $this->encryptedPassword;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Authentication/IdentityHandlerInterface.php:
--------------------------------------------------------------------------------
1 | identity = $identity;
18 | }
19 |
20 | public static function fromIdentity($identity) : self
21 | {
22 | return new self($identity);
23 | }
24 |
25 | public static function fromInvalidCredentials() : self
26 | {
27 | return new self();
28 | }
29 |
30 | public function isSuccess() : bool
31 | {
32 | return null !== $this->identity;
33 | }
34 |
35 | public function getIdentity() : Identity
36 | {
37 | Assertion::notNull($this->identity);
38 | return $this->identity;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Client/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | name = $name;
28 | $this->type = $type;
29 | $this->valueList = $valueList;
30 | }
31 |
32 | public function getName() : string
33 | {
34 | return $this->name;
35 | }
36 |
37 | public function getType() : string
38 | {
39 | return $this->type;
40 | }
41 |
42 | public function hasValueList() : bool
43 | {
44 | return null !== $this->valueList;
45 | }
46 |
47 | public function getValueList() : ValueList
48 | {
49 | Assertion::notNull($this->valueList);
50 | return $this->valueList;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Client/Layout/Layout.php:
--------------------------------------------------------------------------------
1 | database = $database;
28 | $this->name = $name;
29 | $this->fields = $fields;
30 | }
31 |
32 | public function getDatabase() : string
33 | {
34 | return $this->database;
35 | }
36 |
37 | public function getName() : string
38 | {
39 | return $this->name;
40 | }
41 |
42 | /**
43 | * @return Field[]
44 | */
45 | public function getFields() : array
46 | {
47 | return $this->fields;
48 | }
49 |
50 | public function hasField(string $name) : bool
51 | {
52 | return (bool) array_filter($this->fields, function (Field $field) use ($name) : bool {
53 | return $field->getName() === $name;
54 | });
55 | }
56 |
57 | public function getField(string $name) : Field
58 | {
59 | $fields = array_filter($this->fields, function (Field $field) use ($name) : bool {
60 | return $field->getName() === $name;
61 | });
62 |
63 | Assertion::notEmpty($fields);
64 | return reset($fields);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Client/Layout/LayoutClient.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
23 | }
24 |
25 | public function execute(Command $command) : Layout
26 | {
27 | $xml = $this->connection->execute($command, self::GRAMMAR_PATH);
28 | $errorCode = (int) $xml->ERRORCODE;
29 |
30 | if ($errorCode > 0) {
31 | throw FileMakerException::fromErrorCode($errorCode);
32 | }
33 |
34 | return new Layout(
35 | (string) $xml->LAYOUT['DATABASE'],
36 | (string) $xml->LAYOUT['NAME'],
37 | ...$this->parseFields($xml, $this->parseValueLists($xml))
38 | );
39 | }
40 |
41 | private function parseValueLists(SimpleXMLElement $xml) : array
42 | {
43 | $valueLists = [];
44 |
45 | foreach ($xml->VALUELISTS->VALUELIST as $valueList) {
46 | $values = [];
47 |
48 | foreach ($valueList->VALUE as $value) {
49 | $values[] = new Value((string) $value['DISPLAY'], (string) $value);
50 | }
51 |
52 | $valueLists[(string) $valueList['NAME']] = new ValueList((string) $valueList['NAME'], ...$values);
53 | }
54 |
55 | return $valueLists;
56 | }
57 |
58 | private function parseFields(SimpleXMLElement $xml, array $valueLists) : array
59 | {
60 | $fields = [];
61 |
62 | foreach ($xml->LAYOUT->FIELD as $field) {
63 | $valueListName = (string) $field->STYLE['VALUELIST'];
64 |
65 | $fields[] = new Field(
66 | (string) $field['NAME'],
67 | (string) $field->STYLE['TYPE'],
68 | ('' !== $valueListName ? $valueLists[$valueListName] : null)
69 | );
70 | }
71 |
72 | return $fields;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Client/Layout/LayoutClientInterface.php:
--------------------------------------------------------------------------------
1 | display = $display;
21 | $this->value = $value;
22 | }
23 |
24 | public function getDisplay() : string
25 | {
26 | return $this->display;
27 | }
28 |
29 | public function getValue() : string
30 | {
31 | return $this->value;
32 | }
33 |
34 | public function __toString() : string
35 | {
36 | return $this->display;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Client/Layout/ValueList.php:
--------------------------------------------------------------------------------
1 | name = $name;
21 | $this->values = $values;
22 | }
23 |
24 | public function getName() : string
25 | {
26 | return $this->name;
27 | }
28 |
29 | /**
30 | * @return Value[]
31 | */
32 | public function getValues() : array
33 | {
34 | return $this->values;
35 | }
36 |
37 | public function __toString() : string
38 | {
39 | return $this->name;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Client/ResultSet/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | getMessage()
28 | ), 0, $previousException);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Client/ResultSet/Exception/UnknownFieldException.php:
--------------------------------------------------------------------------------
1 | getMessage()
31 | ), 0, $previousException);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Client/ResultSet/ResultSetClientInterface.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
18 | }
19 |
20 | public function __invoke(string $value)
21 | {
22 | if ('' === $value) {
23 | return null;
24 | }
25 |
26 | return new StreamProxy($this->connection, $value);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Client/ResultSet/Transformer/DateTimeTransformer.php:
--------------------------------------------------------------------------------
1 | timeZone = $timeZone;
20 | }
21 |
22 | public function __invoke(string $value)
23 | {
24 | if ('' === $value) {
25 | return null;
26 | }
27 |
28 | $dateTime = DateTimeImmutable::createFromFormat('!m/d/Y H:i:s', $value, $this->timeZone);
29 |
30 | if (false === $dateTime) {
31 | throw DateTimeException::fromDateTimeError($value, DateTimeImmutable::getLastErrors());
32 | }
33 |
34 | return $dateTime;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Client/ResultSet/Transformer/DateTransformer.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
29 | $this->assetUri = $assetUri;
30 | }
31 |
32 | public function __toString() : string
33 | {
34 | return $this->getWrappedStream()->__toString();
35 | }
36 |
37 | public function close()
38 | {
39 | $this->getWrappedStream()->close();
40 | }
41 |
42 | public function detach()
43 | {
44 | return $this->getWrappedStream()->detach();
45 | }
46 |
47 | public function eof() : bool
48 | {
49 | return $this->getWrappedStream()->eof();
50 | }
51 |
52 | public function getContents() : string
53 | {
54 | return $this->getWrappedStream()->getContents();
55 | }
56 |
57 | public function getMetadata($key = null)
58 | {
59 | return $this->getWrappedStream()->getMetadata($key);
60 | }
61 |
62 | public function getSize()
63 | {
64 | return $this->getWrappedStream()->getSize();
65 | }
66 |
67 | public function isReadable() : bool
68 | {
69 | return $this->getWrappedStream()->isReadable();
70 | }
71 |
72 | public function isSeekable() : bool
73 | {
74 | return $this->getWrappedStream()->isSeekable();
75 | }
76 |
77 | public function isWritable() : bool
78 | {
79 | return $this->getWrappedStream()->isWritable();
80 | }
81 |
82 | public function read($length) : string
83 | {
84 | return $this->getWrappedStream()->read($length);
85 | }
86 |
87 | public function rewind()
88 | {
89 | $this->getWrappedStream()->rewind();
90 | }
91 |
92 | public function seek($offset, $whence = SEEK_SET)
93 | {
94 | $this->getWrappedStream()->seek($offset, $whence);
95 | }
96 |
97 | public function tell() : int
98 | {
99 | return $this->getWrappedStream()->tell();
100 | }
101 |
102 | public function write($string) : int
103 | {
104 | return $this->getWrappedStream()->write($string);
105 | }
106 |
107 | private function getWrappedStream() : StreamInterface
108 | {
109 | if (null !== $this->wrappedStream) {
110 | return $this->wrappedStream;
111 | }
112 |
113 | return $this->wrappedStream = $this->connection->getAsset($this->assetUri);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Client/ResultSet/Transformer/TextTransformer.php:
--------------------------------------------------------------------------------
1 | items = new ArrayIterator($items);
25 | $this->totalCount = $totalCount;
26 | }
27 |
28 | public function count() : int
29 | {
30 | return count($this->items);
31 | }
32 |
33 | public function getTotalCount() : int
34 | {
35 | return $this->totalCount;
36 | }
37 |
38 | public function isEmpty() : bool
39 | {
40 | return 0 === count($this->items);
41 | }
42 |
43 | public function first()
44 | {
45 | if ($this->isEmpty()) {
46 | return null;
47 | }
48 |
49 | $this->items->rewind();
50 | return $this->items->current();
51 | }
52 |
53 | public function getIterator() : Traversable
54 | {
55 | return $this->items;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Connection/Command.php:
--------------------------------------------------------------------------------
1 | parameters = ['-lay' => $layout] + $parameters;
45 | }
46 |
47 | public function withIdentity(Identity $identity) : self
48 | {
49 | $command = clone $this;
50 | $command->identity = $identity;
51 |
52 | return $command;
53 | }
54 |
55 | public function hasIdentity()
56 | {
57 | return null !== $this->identity;
58 | }
59 |
60 | public function getIdentity() : Identity
61 | {
62 | Assertion::notNull($this->identity);
63 | return $this->identity;
64 | }
65 |
66 | public function getLayout() : string
67 | {
68 | return $this->parameters['-lay'];
69 | }
70 |
71 | public function __toString() : string
72 | {
73 | $parts = [];
74 |
75 | foreach ($this->parameters as $name => $value) {
76 | if ($value instanceof DateTimeInterface) {
77 | $value = $value->format('m/d/Y H:i:s');
78 | }
79 |
80 | if ($value instanceof Decimal) {
81 | $value = (string) $value;
82 | }
83 |
84 | if (null === $value || '' === $value) {
85 | $parts[] = urlencode((string) $name);
86 | continue;
87 | }
88 |
89 | $parts[] = sprintf('%s=%s', urlencode((string) $name), urlencode((string) $value));
90 | }
91 |
92 | return implode('&', $parts);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Connection/ConnectionInterface.php:
--------------------------------------------------------------------------------
1 | getStatusCode(),
17 | $response->getReasonPhrase()
18 | ), (int) $response->getStatusCode());
19 | }
20 |
21 | public static function fromXmlError(LibXMLError $error) : self
22 | {
23 | return new self(sprintf('An unexpected XML error occured: %s', $error->message));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | getPropertyName(),
21 | $entityMetadata->getClassName(),
22 | $previousException->getMessage()
23 | ), 0, $previousException);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Exception/HydrationException.php:
--------------------------------------------------------------------------------
1 | getPropertyName(),
21 | $entityMetadata->getClassName(),
22 | $previousException->getMessage()
23 | ), 0, $previousException);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Metadata/Embeddable.php:
--------------------------------------------------------------------------------
1 | propertyName = $propertyName;
29 | $this->fieldNamePrefix = $fieldNamePrefix;
30 | $this->metadata = $metadata;
31 | }
32 |
33 | public function getPropertyName() : string
34 | {
35 | return $this->propertyName;
36 | }
37 |
38 | public function getFieldNamePrefix() : string
39 | {
40 | return $this->fieldNamePrefix;
41 | }
42 |
43 | public function getMetadata() : Entity
44 | {
45 | return $this->metadata;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Metadata/Entity.php:
--------------------------------------------------------------------------------
1 | validateArray($fields, Field::class);
67 | $this->validateArray($embeddables, Embeddable::class);
68 | $this->validateArray($oneToMany, OneToMany::class);
69 | $this->validateArray($manyToOne, ManyToOne::class);
70 | $this->validateArray($oneToOne, OneToOne::class);
71 |
72 | $this->layout = $layout;
73 | $this->className = $className;
74 | $this->fields = $fields;
75 | $this->embeddables = $embeddables;
76 | $this->oneToMany = $oneToMany;
77 | $this->manyToOne = $manyToOne;
78 | $this->oneToOne = $oneToOne;
79 | $this->recordId = $recordId;
80 | $this->interfaceName = $interfaceName;
81 | }
82 |
83 | public function getLayout() : string
84 | {
85 | return $this->layout;
86 | }
87 |
88 | public function getClassName() : string
89 | {
90 | return $this->className;
91 | }
92 |
93 | public function hasInterfaceName() : bool
94 | {
95 | return null !== $this->interfaceName;
96 | }
97 |
98 | public function getInterfaceName() : string
99 | {
100 | Assertion::notNull($this->interfaceName);
101 | return $this->interfaceName;
102 | }
103 |
104 | /**
105 | * @return Field[]
106 | */
107 | public function getFields() : array
108 | {
109 | return $this->fields;
110 | }
111 |
112 | /**
113 | * @return Embeddable[]
114 | */
115 | public function getEmbeddables() : array
116 | {
117 | return $this->embeddables;
118 | }
119 |
120 | /**
121 | * @return OneToMany[]
122 | */
123 | public function getOneToMany() : array
124 | {
125 | return $this->oneToMany;
126 | }
127 |
128 | /**
129 | * @return ManyToOne[]
130 | */
131 | public function getManyToOne() : array
132 | {
133 | return $this->manyToOne;
134 | }
135 |
136 | /**
137 | * @return OneToOne[]
138 | */
139 | public function getOneToOne() : array
140 | {
141 | return $this->oneToOne;
142 | }
143 |
144 | public function hasRecordId() : bool
145 | {
146 | return null !== $this->recordId;
147 | }
148 |
149 | public function getRecordId() : RecordId
150 | {
151 | Assertion::notNull($this->recordId);
152 | return $this->recordId;
153 | }
154 |
155 | private function validateArray(array $array, string $expectedClassName)
156 | {
157 | Assertion::count(array_filter($array, function ($metadata) use ($expectedClassName) : bool {
158 | return !$metadata instanceof $expectedClassName;
159 | }), 0, sprintf('At least one element in array is not an instance of %s', $expectedClassName));
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Metadata/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | fieldName = $fieldName;
43 | $this->propertyName = $propertyName;
44 | $this->type = $type;
45 | $this->repeatable = $repeatable;
46 | $this->readOnly = $readOnly;
47 | }
48 |
49 | public function getFieldName() : string
50 | {
51 | return $this->fieldName;
52 | }
53 |
54 | public function getPropertyName() : string
55 | {
56 | return $this->propertyName;
57 | }
58 |
59 | public function getType() : TypeInterface
60 | {
61 | return $this->type;
62 | }
63 |
64 | public function isRepeatable() : bool
65 | {
66 | return $this->repeatable;
67 | }
68 |
69 | public function isReadOnly() : bool
70 | {
71 | return $this->readOnly;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Metadata/ManyToOne.php:
--------------------------------------------------------------------------------
1 | fieldName = $fieldName;
67 | $this->propertyName = $propertyName;
68 | $this->targetTable = $targetTable;
69 | $this->targetEntity = $targetEntity;
70 | $this->targetPropertyName = $targetPropertyName;
71 | $this->targetFieldName = $targetFieldName;
72 | $this->targetInterfaceName = $targetInterfaceName;
73 | $this->readOnly = $readOnly;
74 | $this->eagerHydration = $eagerHydration;
75 | }
76 |
77 | public function getFieldName() : string
78 | {
79 | return $this->fieldName;
80 | }
81 |
82 | public function getPropertyName() : string
83 | {
84 | return $this->propertyName;
85 | }
86 |
87 | public function getTargetTable() : string
88 | {
89 | return $this->targetTable;
90 | }
91 |
92 | public function getTargetEntity() : string
93 | {
94 | return $this->targetEntity;
95 | }
96 |
97 | public function getTargetPropertyName() : string
98 | {
99 | return $this->targetPropertyName;
100 | }
101 |
102 | public function getTargetFieldName() : string
103 | {
104 | return $this->targetFieldName;
105 | }
106 |
107 | public function getTargetInterfaceName() : string
108 | {
109 | Assertion::notNull(
110 | $this->targetInterfaceName,
111 | sprintf('Target entity %s has no interface name defined', $this->targetEntity)
112 | );
113 | return $this->targetInterfaceName;
114 | }
115 |
116 | public function isReadOnly() : bool
117 | {
118 | return $this->readOnly;
119 | }
120 |
121 | public function hasEagerHydration() : bool
122 | {
123 | return $this->eagerHydration;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Metadata/MetadataBuilderInterface.php:
--------------------------------------------------------------------------------
1 | propertyName = $propertyName;
41 | $this->targetTable = $targetTable;
42 | $this->targetEntity = $targetEntity;
43 | $this->targetFieldName = $targetFieldName;
44 | $this->eagerHydration = $eagerHydration;
45 | }
46 |
47 | public function getPropertyName() : string
48 | {
49 | return $this->propertyName;
50 | }
51 |
52 | public function getTargetTable() : string
53 | {
54 | return $this->targetTable;
55 | }
56 |
57 | public function getTargetEntity() : string
58 | {
59 | return $this->targetEntity;
60 | }
61 |
62 | public function getTargetFieldName() : string
63 | {
64 | return $this->targetFieldName;
65 | }
66 |
67 | public function hasEagerHydration() : bool
68 | {
69 | return $this->eagerHydration;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Metadata/OneToOne.php:
--------------------------------------------------------------------------------
1 | propertyName = $propertyName;
78 | $this->targetTable = $targetTable;
79 | $this->targetEntity = $targetEntity;
80 | $this->targetFieldName = $targetFieldName;
81 | $this->targetInterfaceName = $targetInterfaceName;
82 | $this->owningSide = $owningSide;
83 | $this->readOnly = $owningSide ? $readOnly : true;
84 | $this->fieldName = $owningSide ? $fieldName : null;
85 | $this->targetPropertyName = $owningSide ? $targetPropertyName : null;
86 | $this->eagerHydration = $eagerHydration;
87 | }
88 |
89 | public function getPropertyName() : string
90 | {
91 | return $this->propertyName;
92 | }
93 |
94 | public function getTargetTable() : string
95 | {
96 | return $this->targetTable;
97 | }
98 |
99 | public function getTargetEntity() : string
100 | {
101 | return $this->targetEntity;
102 | }
103 |
104 | public function getTargetFieldName() : string
105 | {
106 | return $this->targetFieldName;
107 | }
108 |
109 | public function getTargetInterfaceName() : string
110 | {
111 | Assertion::notNull(
112 | $this->targetInterfaceName,
113 | sprintf('Target entity %s has no interface name defined', $this->targetEntity)
114 | );
115 | return $this->targetInterfaceName;
116 | }
117 |
118 | public function isOwningSide() : bool
119 | {
120 | return $this->owningSide;
121 | }
122 |
123 | public function getFieldName() : string
124 | {
125 | Assertion::notNull($this->fieldName);
126 | return $this->fieldName;
127 | }
128 |
129 | public function getTargetPropertyName() : string
130 | {
131 | Assertion::notNull($this->targetPropertyName);
132 | return $this->targetPropertyName;
133 | }
134 |
135 | public function isReadOnly() : bool
136 | {
137 | return $this->readOnly;
138 | }
139 |
140 | public function hasEagerHydration() : bool
141 | {
142 | return $this->eagerHydration;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Metadata/RecordId.php:
--------------------------------------------------------------------------------
1 | propertyName = $propertyName;
16 | }
17 |
18 | public function getPropertyName() : string
19 | {
20 | return $this->propertyName;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Proxy/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | resultSetClient = $resultSetClient;
40 | $this->metadataBuilder = $metadataBuilder;
41 | $this->proxyBuilder = $proxyBuilder;
42 | }
43 |
44 | public function buildRepository(string $entityClassName) : RepositoryInterface
45 | {
46 | if (array_key_exists($entityClassName, $this->repositories)) {
47 | return $this->repositories[$entityClassName];
48 | }
49 |
50 | $metadata = $this->metadataBuilder->getMetadata($entityClassName);
51 |
52 | return ($this->repositories[$entityClassName] = new Repository(
53 | $this->resultSetClient,
54 | $metadata->getLayout(),
55 | new MetadataHydration($this, $this->proxyBuilder, $metadata),
56 | new MetadataExtraction($metadata)
57 | ));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Repository/Builder/RepositoryBuilderInterface.php:
--------------------------------------------------------------------------------
1 | comp(Decimal::fromInteger(0));
19 | }
20 |
21 | if (is_string($value)) {
22 | return $value !== '0' && $value !== '';
23 | }
24 |
25 | return true;
26 | }
27 |
28 | public function toFileMakerValue($value)
29 | {
30 | Assertion::boolean($value);
31 | return Decimal::fromInteger($value ? 1 : 0);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Type/DateTimeType.php:
--------------------------------------------------------------------------------
1 | format('m/d/Y');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Type/DecimalType.php:
--------------------------------------------------------------------------------
1 | asFloat();
19 | }
20 |
21 | public function toFileMakerValue($value)
22 | {
23 | if (null === $value) {
24 | return null;
25 | }
26 |
27 | Assertion::float($value);
28 | return Decimal::fromFloat($value);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Type/IntegerType.php:
--------------------------------------------------------------------------------
1 | asInteger();
19 | }
20 |
21 | public function toFileMakerValue($value)
22 | {
23 | if (null === $value) {
24 | return null;
25 | }
26 |
27 | Assertion::integer($value);
28 | return Decimal::fromInteger($value);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Type/NullableStringType.php:
--------------------------------------------------------------------------------
1 | format('H:i:s');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Repository/Builder/Type/TypeInterface.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
39 | $this->idFieldName = $idFieldName;
40 | $this->sparseRecords = $sparseRecords;
41 | }
42 |
43 | public function getIterator() : Traversable
44 | {
45 | if (null !== $this->iterator) {
46 | return $this->iterator;
47 | }
48 |
49 | if (empty($this->sparseRecords)) {
50 | return $this->iterator = new ArrayIterator();
51 | }
52 |
53 | $findQuery = new FindQuery();
54 | $findQuery->addOrQueries(...array_map(function (array $sparseRecord) : Query {
55 | return new Query($this->idFieldName, (string) $sparseRecord[$this->idFieldName]);
56 | }, $this->sparseRecords));
57 |
58 | return $this->iterator = new IteratorIterator($this->repository->findByQuery($findQuery));
59 | }
60 |
61 | public function count() : int
62 | {
63 | return count($this->sparseRecords);
64 | }
65 |
66 | public function getTotalCount() : int
67 | {
68 | return count($this->sparseRecords);
69 | }
70 |
71 | public function isEmpty() : bool
72 | {
73 | return 0 === count($this->sparseRecords);
74 | }
75 |
76 | public function first()
77 | {
78 | if ($this->isEmpty()) {
79 | return null;
80 | }
81 |
82 | $iterator = $this->getIterator();
83 | $iterator->rewind();
84 | return $iterator->current();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Repository/Query/Exception/EmptyQueryException.php:
--------------------------------------------------------------------------------
1 | queries += $queries;
23 | }
24 |
25 | public function addAndQueries(Query ...$queries)
26 | {
27 | if (empty($queries)) {
28 | throw InvalidArgumentException::fromEmptyQueryParameters();
29 | }
30 |
31 | $this->queries[] = $queries;
32 | }
33 |
34 | public function toParameters() : array
35 | {
36 | if (empty($this->queries)) {
37 | throw EmptyQueryException::fromEmptyQueryArray();
38 | }
39 |
40 | $parameters = [
41 | '-query' => $this->buildQueryParameter(),
42 | ];
43 |
44 | $index = 0;
45 |
46 | foreach ($this->queries as $query) {
47 | if ($query instanceof Query) {
48 | $parameters[sprintf('-q%d', ++$index)] = $query->getFieldName();
49 | $parameters[sprintf('-q%d.value', $index)] = $query->getValue();
50 | continue;
51 | }
52 |
53 | foreach ($query as $andQuery) {
54 | $parameters[sprintf('-q%d', ++$index)] = $andQuery->getFieldName();
55 | $parameters[sprintf('-q%d.value', $index)] = $andQuery->getValue();
56 | continue;
57 | }
58 | }
59 |
60 | return $parameters;
61 | }
62 |
63 | private function buildQueryParameter() : string
64 | {
65 | $index = 0;
66 | $orQueries = [];
67 |
68 | foreach ($this->queries as $query) {
69 | if ($query instanceof Query) {
70 | $orQueries[] = sprintf('%sq%d', $query->isExclude() ? '!' : '', ++$index);
71 | continue;
72 | }
73 |
74 | $andQueries = [];
75 |
76 | foreach ($query as $andQuery) {
77 | $andQueries[] = sprintf('%sq%d', $andQuery->isExclude() ? '!' : '', ++$index);
78 | }
79 |
80 | $orQueries[] = implode(',', $andQueries);
81 | }
82 |
83 | return '(' . implode(');(', $orQueries) . ')';
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Repository/Query/Query.php:
--------------------------------------------------------------------------------
1 | fieldName = $fieldName;
26 | $this->value = $value;
27 | $this->exclude = $exclude;
28 | }
29 |
30 | public function getFieldName() : string
31 | {
32 | return $this->fieldName;
33 | }
34 |
35 | public function getValue() : string
36 | {
37 | return $this->value;
38 | }
39 |
40 | public function isExclude() : bool
41 | {
42 | return $this->exclude;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Repository/RepositoryInterface.php:
--------------------------------------------------------------------------------
1 | createResultSetClientProphecy();
24 | $resultSetClient->execute(
25 | $this->createCommand('foo')->withIdentity($identity)
26 | )->willReturn(new ItemCollection([[]], 1));
27 |
28 | $authenticator = $this->createAuthenticator($resultSetClient->reveal(), $identity);
29 |
30 | $this->assertSame($identity, $authenticator->authenticate('foo', 'bar')->getIdentity());
31 | }
32 |
33 | public function testAuthenticatorGenericFail()
34 | {
35 | $identity = new Identity('foo', 'bar');
36 |
37 | $resultSetClient = $this->createResultSetClientProphecy();
38 | $resultSetClient->execute(
39 | $this->createCommand('foo')->withIdentity($identity)
40 | )->willThrow(InvalidResponseException::class);
41 |
42 | $authenticator = $this->createAuthenticator($resultSetClient->reveal(), $identity);
43 |
44 | $this->expectException(InvalidResponseException::class);
45 | $authenticator->authenticate('foo', 'bar')->getIdentity();
46 | }
47 |
48 | public function testAuthenticator401NotFound()
49 | {
50 | $identity = new Identity('foo', 'bar');
51 |
52 | $response = $this->prophesize(ResponseInterface::class);
53 | $response->getStatusCode()->willReturn(401);
54 | $response->getReasonPhrase()->willReturn('Not Found');
55 |
56 | $resultSetClient = $this->createResultSetClientProphecy();
57 | $resultSetClient->execute(
58 | $this->createCommand('foo')->withIdentity($identity)
59 | )->willThrow(InvalidResponseException::fromUnsuccessfulResponse($response->reveal()));
60 |
61 | $authenticator = $this->createAuthenticator($resultSetClient->reveal(), $identity);
62 | $this->assertFalse($authenticator->authenticate('foo', 'bar')->isSuccess());
63 | }
64 |
65 | public function testAuthenticatorEmptyResultFail()
66 | {
67 | $identity = new Identity('foo', 'bar');
68 | $resultSetClient = $this->createResultSetClientProphecy();
69 | $resultSetClient->execute(
70 | $this->createCommand('foo')->withIdentity($identity)
71 | )->willReturn(new ItemCollection([], 0));
72 |
73 | $authenticator = $this->createAuthenticator($resultSetClient->reveal(), $identity);
74 |
75 | $this->expectException(InvalidResultException::class);
76 | $authenticator->authenticate('foo', 'bar')->getIdentity();
77 | }
78 |
79 | private function createResultSetClientProphecy() : ObjectProphecy
80 | {
81 | $resultSetClient = $this->prophesize(ResultSetClientInterface::class);
82 | $resultSetClient->quoteString(\Prophecy\Argument::any())->will(function (array $parameters) : string {
83 | return $parameters[0];
84 | });
85 |
86 | return $resultSetClient;
87 | }
88 |
89 | private function createAuthenticator(ResultSetClientInterface $resultSetClient, Identity $identity) : Authenticator
90 | {
91 | $identityHandler = $this->prophesize(IdentityHandlerInterface::class);
92 | $identityHandler->createIdentity('foo', 'bar')->willReturn($identity);
93 |
94 | return new Authenticator(
95 | $resultSetClient,
96 | $identityHandler->reveal(),
97 | 'layout',
98 | 'account'
99 | );
100 | }
101 |
102 | private function createCommand(string $username) : Command
103 | {
104 | return new Command('layout', ['account' => '==' . $username, '-find' => null]);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/test/Authentication/BlockCipherIdentityHandlerTest.php:
--------------------------------------------------------------------------------
1 | prophesize(BlockCipher::class);
16 | $blockCipher->encrypt('bar')->willReturn('baz');
17 |
18 | $identityHandler = new BlockCipherIdentityHandler($blockCipher->reveal());
19 | $identity = $identityHandler->createIdentity('foo', 'bar');
20 |
21 | $this->assertSame('foo', $identity->getUsername());
22 | $this->assertSame('baz', $identity->getEncryptedPassword());
23 | }
24 |
25 | public function testDecryptPassword()
26 | {
27 | $blockCipher = $this->prophesize(BlockCipher::class);
28 | $blockCipher->decrypt('baz')->willReturn('bar');
29 |
30 | $identityHandler = new BlockCipherIdentityHandler($blockCipher->reveal());
31 | $this->assertSame('bar', $identityHandler->decryptPassword(new Identity('foo', 'baz')));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/Authentication/IdentityTest.php:
--------------------------------------------------------------------------------
1 | assertSame('foo', $identity->getUsername());
15 | $this->assertSame('bar', $identity->getEncryptedPassword());
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/test/Authentication/ResultTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($result->isSuccess());
19 | $this->assertSame($identity, $result->getIdentity());
20 | }
21 |
22 | public function testUnsuccessfulResult()
23 | {
24 | $result = Result::fromInvalidCredentials();
25 |
26 | $this->assertFalse($result->isSuccess());
27 | $this->expectException(InvalidArgumentException::class);
28 | $result->getIdentity();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/Client/Layout/LayoutClientTest.php:
--------------------------------------------------------------------------------
1 | prophesize(ConnectionInterface::class);
20 | $connection->execute($command, '/fmi/xml/FMPXMLLAYOUT.xml')->willReturn($xml);
21 | $client = new LayoutClient($connection->reveal());
22 |
23 | $layout = $client->execute($command);
24 |
25 | $this->assertSame('FMServer_Sample', $layout->getDatabase());
26 | $this->assertSame('Projects | Web', $layout->getName());
27 | $this->assertCount(9, $layout->getFields());
28 |
29 | $this->assertTrue($layout->hasField('Projects::Project Name'));
30 | $nameField = $layout->getField('Projects::Project Name');
31 | $this->assertFalse($nameField->hasValueList());
32 | $this->assertSame('EDITTEXT', $nameField->getType());
33 |
34 | $this->assertTrue($layout->hasField('Projects::Status on Screen'));
35 | $statusField = $layout->getField('Projects::Status on Screen');
36 | $this->assertTrue($statusField->hasValueList());
37 | $this->assertSame('Status', (string) $statusField->getValueList());
38 | $this->assertSame('Status', $statusField->getValueList()->getName());
39 | $this->assertSame('Completed', (string) $statusField->getValueList()->getValues()[0]);
40 | $this->assertSame('Completed', $statusField->getValueList()->getValues()[0]->getDisplay());
41 | $this->assertSame('Completed', $statusField->getValueList()->getValues()[0]->getValue());
42 |
43 | $this->expectException(InvalidArgumentException::class);
44 | $layout->getField('non-existent');
45 | }
46 |
47 | public function testErrorXml()
48 | {
49 | $command = new Command('foo', []);
50 | $xml = simplexml_load_file(__DIR__ . '/TestAssets/error_fmpxmllayout.xml');
51 | $connection = $this->prophesize(ConnectionInterface::class);
52 | $connection->execute($command, '/fmi/xml/FMPXMLLAYOUT.xml')->willReturn($xml);
53 | $client = new LayoutClient($connection->reveal());
54 |
55 | $this->expectException(FileMakerException::class);
56 | $this->expectExceptionMessage('User canceled action');
57 | $this->expectExceptionCode(1);
58 | $client->execute($command);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/test/Client/Layout/TestAssets/error_fmpxmllayout.xml:
--------------------------------------------------------------------------------
1 | 1
--------------------------------------------------------------------------------
/test/Client/Layout/TestAssets/sample_fmpxmllayout.xml:
--------------------------------------------------------------------------------
1 | 0CompletedDueIn ProgressOverduePendingfinancemanufacturingTim Thomson
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/ParentChildAssociations/Base_sparse-collection.xml:
--------------------------------------------------------------------------------
1 | 2210000000000000000000000000000000000Some other text with line breaks
2 | Line two
3 | Line three3258.72771254957897713067Some text with line breaks
4 | Line two
5 | Line three
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/deleted-field.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/duplicateportals.xml:
--------------------------------------------------------------------------------
1 | jmedemajmedema05/01/2008 06:27:2305/01/2008 06:27:23ApplicationVersion: Web Publishing Engine 8.0v4
2 | HostApplicationVersion: ?
3 | PrivilegeSetName: [Full Access]
4 | SystemPlatform: -2
5 | SystemVersion: 5.2240BFAB59CB-61F9-4803-9A91-000D608435B8BFAB59CB-61F9-4803-9A91-000D608435B8BFAB59CB-61F9-4803-9A91-000D608435B8BFAB59CB-61F9-4803-9A91-000D608435B8240
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/invalid.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PHP: Behind the Parser
5 |
6 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/invalidFieldTypeDate.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
8 |
9 |
12 |
15 |
18 |
19 |
20 |
21 |
22 | 09/14/2016
23 |
24 |
25 |
26 |
27 |
28 | invalid date value
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/invalidFieldTypeFake.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
8 |
9 |
11 |
12 |
13 |
14 |
15 | 94.82918507895723447869
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/invalidFieldTypeRepeatingNumber.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
8 |
9 |
12 |
13 |
14 |
15 |
16 | 1
17 | 1.1
18 | 0
19 | -1
20 |
21 |
22 |
23 |
24 |
25 | non-number string
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/invalidFieldTypeTime.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
8 |
9 |
12 |
15 |
18 |
19 |
20 |
21 |
22 | 14:22:23
23 |
24 |
25 |
26 |
27 |
28 | invalid time value
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/invalidFieldTypeTimestamp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
8 |
9 |
12 |
15 |
18 |
19 |
20 |
21 |
22 | 09/14/2016 14:22:23
23 |
24 |
25 |
26 |
27 |
28 | invalid timestamp value
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/invalidFieldTypes.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
8 |
9 |
11 |
14 |
17 |
18 |
19 |
20 |
21 | 94.82918507895723447869
22 |
23 |
24 | invalid data 09/14/2016 14:22:23
25 |
26 |
27 | wrong
28 | scalar
29 | type
30 |
31 | gaps
32 |
33 |
34 |
35 |
36 | last one
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/reservedfieldname.xml:
--------------------------------------------------------------------------------
1 | rjacquesrjacques12/04/2008 19:10:3712/04/2008 19:10:37ApplicationVersion: Web Publishing Engine 8.0v4
2 | HostApplicationVersion: ?
3 | PrivilegeSetName: web
4 | SystemPlatform: -2
5 | SystemVersion: 5.2
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/sample_fmresultset_empty.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/TestAssets/sample_fmresultset_fmerror4.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/Type/NumberTransformerTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($numberTransformer($fileMakerValue), Decimal::fromString($expectedValue));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/Client/ResultSet/Type/StreamProxyTest.php:
--------------------------------------------------------------------------------
1 | prophesize(StreamInterface::class);
40 | $stream->{$methodName}(...$arguments)->willReturn($returnValue)->shouldBeCalled();
41 |
42 | $connection = $this->prophesize(ConnectionInterface::class);
43 | $connection->getAsset('/foo')->willReturn($stream->reveal());
44 |
45 | $proxy = new StreamProxy($connection->reveal(), '/foo');
46 | $this->assertSame($returnValue, $proxy->{$methodName}(...$arguments));
47 | }
48 |
49 | public function testWrappedStreamIsOnlyRetrievedOnce()
50 | {
51 | $connection = $this->prophesize(ConnectionInterface::class);
52 | $connection
53 | ->getAsset('/foo')
54 | ->shouldBeCalledTimes(1)
55 | ->willReturn($this->prophesize(StreamInterface::class)->reveal());
56 |
57 | $proxy = new StreamProxy($connection->reveal(), '/foo');
58 | $proxy->rewind();
59 | $proxy->rewind();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/test/Collection/ItemCollectionTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($collection->isEmpty());
15 | $this->assertSame(['foo', 'bar'], iterator_to_array($collection->getIterator()));
16 | }
17 |
18 | public function testEmptyCollection()
19 | {
20 | $collection = new ItemCollection([], 0);
21 | $this->assertTrue($collection->isEmpty());
22 | $this->assertNull($collection->first());
23 | }
24 |
25 | public function testFirst()
26 | {
27 | $collection = new ItemCollection(['foo', 'bar'], 2);
28 | $this->assertSame('foo', $collection->first());
29 | }
30 |
31 | public function testCount()
32 | {
33 | $collection = new ItemCollection(['foo', 'bar'], 4);
34 | $this->assertSame(2, count($collection));
35 | }
36 |
37 | public function testGetTotalCount()
38 | {
39 | $collection = new ItemCollection(['foo', 'bar'], 4);
40 | $this->assertSame(4, $collection->getTotalCount());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/Connection/CommandTest.php:
--------------------------------------------------------------------------------
1 | expectException(DomainException::class);
19 | $this->expectExceptionMessage('parameter "-db" is not allowed');
20 | new Command('foo', ['-db' => 'foo']);
21 | }
22 |
23 | public function testUseDisallowedLayoutParameter()
24 | {
25 | $this->expectException(DomainException::class);
26 | $this->expectExceptionMessage('parameter "-lay" is not allowed');
27 | new Command('foo', ['-lay' => 'foo']);
28 | }
29 |
30 | public function testUseDisallowedValueType()
31 | {
32 | $this->expectException(DomainException::class);
33 | $this->expectExceptionMessage('must either be scalar, null, Decimal or implement DateTimeInterface');
34 | new Command('foo', ['bar' => []]);
35 | }
36 |
37 | public function testGetLayout()
38 | {
39 | $command = new Command('foo', []);
40 | $this->assertSame('foo', $command->getLayout());
41 | }
42 |
43 | public function testCloneWithIdentity()
44 | {
45 | $command = new Command('foo', []);
46 | $identity = new Identity('foo', 'bar');
47 | $newCommand = $command->withIdentity($identity);
48 |
49 | $this->assertNotSame($newCommand, $command);
50 | $this->assertFalse($command->hasIdentity());
51 | $this->assertTrue($newCommand->hasIdentity());
52 | $this->assertSame($identity, $newCommand->getIdentity());
53 | }
54 |
55 | public function testGetIdentityWithoutIdentity()
56 | {
57 | $this->expectException(InvalidArgumentException::class);
58 | $command = new Command('foo', []);
59 | $command->getIdentity();
60 | }
61 |
62 | public function parameterProvider() : array
63 | {
64 | return [
65 | 'no-parameters' => [
66 | [],
67 | '-lay=foo',
68 | ],
69 | 'empty-parameter' => [
70 | ['foo' => ''],
71 | '-lay=foo&foo',
72 | ],
73 | 'null-parameter' => [
74 | ['foo' => null],
75 | '-lay=foo&foo',
76 | ],
77 | 'string-parameter' => [
78 | ['foo' => 'bar'],
79 | '-lay=foo&foo=bar',
80 | ],
81 | 'integer-parameter' => [
82 | ['foo' => 3],
83 | '-lay=foo&foo=3',
84 | ],
85 | 'float-parameter' => [
86 | ['foo' => 3.3],
87 | '-lay=foo&foo=3.3',
88 | ],
89 | 'boolean-parameter' => [
90 | ['foo' => true],
91 | '-lay=foo&foo=1',
92 | ],
93 | 'datetime-parameter' => [
94 | ['foo' => new DateTimeImmutable('2016-01-01 00:00:00 UTC')],
95 | '-lay=foo&foo=01%2F01%2F2016+00%3A00%3A00',
96 | ],
97 | 'decimal-parameter' => [
98 | ['foo' => Decimal::fromString('12.499734362638823')],
99 | '-lay=foo&foo=12.499734362638823',
100 | ],
101 | ];
102 | }
103 |
104 | /**
105 | * @dataProvider parameterProvider
106 | */
107 | public function testToString(array $parameters, string $expectedString)
108 | {
109 | $command = new Command('foo', $parameters);
110 | $this->assertSame($expectedString, (string) $command);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/EmbeddableTest.php:
--------------------------------------------------------------------------------
1 | assertSame('propertyName', $metadata->getPropertyName());
18 | $this->assertSame('fieldNamePrefix', $metadata->getFieldNamePrefix());
19 | $this->assertSame($entityMetadata, $metadata->getMetadata());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/FieldTest.php:
--------------------------------------------------------------------------------
1 | prophesize(TypeInterface::class)->reveal();
15 |
16 | $metadata = new Field('fieldName', 'propertyName', $type, true, true);
17 | $this->assertSame('fieldName', $metadata->getFieldName());
18 | $this->assertSame('propertyName', $metadata->getPropertyName());
19 | $this->assertSame($type, $metadata->getType());
20 | $this->assertTrue($metadata->isRepeatable());
21 | $this->assertTrue($metadata->isReadOnly());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/ManyToOneTest.php:
--------------------------------------------------------------------------------
1 | assertSame('fieldName', $metadata->getFieldName());
24 | $this->assertSame('propertyName', $metadata->getPropertyName());
25 | $this->assertSame('targetTable', $metadata->getTargetTable());
26 | $this->assertSame('targetEntity', $metadata->getTargetEntity());
27 | $this->assertSame('targetPropertyName', $metadata->getTargetPropertyName());
28 | $this->assertSame('targetFieldName', $metadata->getTargetFieldName());
29 | $this->assertSame('targetInterfaceName', $metadata->getTargetInterfaceName());
30 | $this->assertTrue($metadata->isReadOnly());
31 | $this->assertFalse($metadata->hasEagerHydration());
32 | }
33 |
34 | public function testSettingEagerHydration()
35 | {
36 | $metadata = new ManyToOne('', '', '', '', '', '', '', false, true);
37 | $this->assertTrue($metadata->hasEagerHydration());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/OneToManyTest.php:
--------------------------------------------------------------------------------
1 | assertSame('propertyName', $metadata->getPropertyName());
15 | $this->assertSame('targetTable', $metadata->getTargetTable());
16 | $this->assertSame('targetEntity', $metadata->getTargetEntity());
17 | $this->assertSame('targetFieldName', $metadata->getTargetFieldName());
18 | $this->assertFalse($metadata->hasEagerHydration());
19 | }
20 |
21 | public function testSettingEagerHydration()
22 | {
23 | $metadata = new OneToMany('', '', '', '', true);
24 | $this->assertTrue($metadata->hasEagerHydration());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/OneToOneTest.php:
--------------------------------------------------------------------------------
1 | assertSame('propertyName', $metadata->getPropertyName());
26 | $this->assertSame('targetTable', $metadata->getTargetTable());
27 | $this->assertSame('targetEntity', $metadata->getTargetEntity());
28 | $this->assertSame('targetInterfaceName', $metadata->getTargetInterfaceName());
29 | $this->assertTrue($metadata->isOwningSide());
30 | $this->assertTrue($metadata->isReadOnly());
31 | $this->assertSame('fieldName', $metadata->getFieldName());
32 | $this->assertSame('targetPropertyName', $metadata->getTargetPropertyName());
33 | $this->assertSame('targetFieldName', $metadata->getTargetFieldName());
34 | $this->assertFalse($metadata->hasEagerHydration());
35 | }
36 |
37 | public function testSettingEagerHydration()
38 | {
39 | $metadata = new OneToOne('', '', '', '', '', true, true, '', '', true);
40 | $this->assertTrue($metadata->hasEagerHydration());
41 | }
42 |
43 | public function testExceptionOnMissingProperties()
44 | {
45 | $this->expectException(InvalidArgumentException::class);
46 | new OneToOne(
47 | 'propertyName',
48 | 'targetTable',
49 | 'targetEntity',
50 | 'targetFieldName',
51 | 'targetInterfaceName',
52 | true,
53 | false
54 | );
55 | }
56 |
57 | public function testOptionalPropertiesAreSetToNullOnInverseSide()
58 | {
59 | $metadata = new OneToOne(
60 | 'propertyName',
61 | 'targetTable',
62 | 'targetEntity',
63 | 'targetFieldName',
64 | 'targetInterfaceName',
65 | false,
66 | false,
67 | 'foo',
68 | 'bar'
69 | );
70 |
71 | $this->assertTrue($metadata->isReadOnly());
72 |
73 | $this->expectException(InvalidArgumentException::class);
74 | $metadata->getTargetPropertyName();
75 |
76 | $this->expectException(InvalidArgumentException::class);
77 | $metadata->getPropertyName();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/RecordIdTest.php:
--------------------------------------------------------------------------------
1 | assertSame('propertyName', $metadata->getPropertyName());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/BuiltInTypes.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/CustomType.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/EagerHydration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
16 |
23 |
30 |
31 |
39 |
48 |
57 |
58 |
66 |
75 |
84 |
85 |
91 |
98 |
105 |
106 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/Embeddable.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/EmbeddedEntity.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/Empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/InterfaceName.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/Invalid.Xml.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/ManyToOne.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/ManyToOneWithoutInterface.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/Non.Xml.xml:
--------------------------------------------------------------------------------
1 | non-xml
2 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/OneToMany.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/OneToOneInverse.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/OneToOneOwning.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/ReadOnly.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
22 |
31 |
40 |
41 |
49 |
58 |
67 |
68 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/RecordId.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/RelationTarget.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/RelationTargetWithoutInterface.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Metadata/TestAssets/Repeatable.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Proxy/TestAssets/ComplexSetterInterface.php:
--------------------------------------------------------------------------------
1 | prophesize(MetadataBuilderInterface::class);
20 | $metadataBuilder->getMetadata('foo')->willReturn(new Entity('bar', 'foo', [], [], [], [], []));
21 |
22 | $builder = new RepositoryBuilder(
23 | $this->prophesize(ResultSetClientInterface::class)->reveal(),
24 | $metadataBuilder->reveal(),
25 | $this->prophesize(ProxyBuilderInterface::class)->reveal()
26 | );
27 |
28 | $this->assertSame($builder->buildRepository('foo'), $builder->buildRepository('foo'));
29 | }
30 |
31 | public function testMetadataInjection()
32 | {
33 | $metadata = new Entity('bar', 'foo', [], [], [], [], []);
34 |
35 | $metadataBuilder = $this->prophesize(MetadataBuilderInterface::class);
36 | $metadataBuilder->getMetadata('foo')->willReturn($metadata);
37 |
38 | $builder = new RepositoryBuilder(
39 | $this->prophesize(ResultSetClientInterface::class)->reveal(),
40 | $metadataBuilder->reveal(),
41 | $this->prophesize(ProxyBuilderInterface::class)->reveal()
42 | );
43 |
44 | $repository = $builder->buildRepository('foo');
45 | $this->assertAttributeSame('bar', 'layout', $repository);
46 |
47 | $hydration = self::getObjectAttribute($repository, 'hydration');
48 | $this->assertInstanceOf(MetadataHydration::class, $hydration);
49 | $this->assertAttributeSame($builder, 'repositoryBuilder', $hydration);
50 | $this->assertAttributeSame($metadata, 'entityMetadata', $hydration);
51 |
52 | $extraction = self::getObjectAttribute($repository, 'extraction');
53 | $this->assertInstanceOf(MetadataExtraction::class, $extraction);
54 | $this->assertAttributeSame($metadata, 'entityMetadata', $extraction);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/test/Repository/Builder/TestAssets/EmptyEntityInterface.php:
--------------------------------------------------------------------------------
1 | assertSame($expectedResult, $type->fromFileMakerValue($fileMakerValue));
36 | }
37 |
38 | public function testUnsuccessfulConversionFromFileMaker()
39 | {
40 | $type = new BooleanType();
41 | $this->assertTrue($type->fromFileMakerValue(new stdClass()));
42 | }
43 |
44 | public function testSuccessfulConversionToFileMaker()
45 | {
46 | $type = new BooleanType();
47 | $this->assertSame(0, $type->toFileMakerValue(false)->asInteger());
48 | $this->assertSame(1, $type->toFileMakerValue(true)->asInteger());
49 | }
50 |
51 | public function testUnsuccessfulConversionToFileMaker()
52 | {
53 | $type = new BooleanType();
54 | $this->expectException(InvalidArgumentException::class);
55 | $type->toFileMakerValue('foo');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/DateTimeTypeTest.php:
--------------------------------------------------------------------------------
1 | assertSame($value, $type->fromFileMakerValue($value));
18 | }
19 |
20 | public function testNullConversionFromFileMaker()
21 | {
22 | $this->assertNull((new DateTimeType())->fromFileMakerValue(null));
23 | }
24 |
25 | public function testUnsuccessfulConversionFromFileMaker()
26 | {
27 | $type = new DateTimeType();
28 | $this->expectException(InvalidArgumentException::class);
29 | $type->fromFileMakerValue('foo');
30 | }
31 |
32 | public function testSuccessfulConversionToFileMaker()
33 | {
34 | $type = new DateTimeType();
35 | $value = new DateTimeImmutable();
36 | $this->assertSame($value, $type->toFileMakerValue($value));
37 | }
38 |
39 | public function testNullConversionToFileMaker()
40 | {
41 | $this->assertNull((new DateTimeType())->toFileMakerValue(null));
42 | }
43 |
44 | public function testUnsuccessfulConversionToFileMaker()
45 | {
46 | $type = new DateTimeType();
47 | $this->expectException(InvalidArgumentException::class);
48 | $type->toFileMakerValue('foo');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/DateTypeTest.php:
--------------------------------------------------------------------------------
1 | assertSame($value, $type->fromFileMakerValue($value));
18 | }
19 |
20 | public function testNullConversionFromFileMaker()
21 | {
22 | $this->assertNull((new DateType())->fromFileMakerValue(null));
23 | }
24 |
25 | public function testUnsuccessfulConversionFromFileMaker()
26 | {
27 | $type = new DateType();
28 | $this->expectException(InvalidArgumentException::class);
29 | $type->fromFileMakerValue('foo');
30 | }
31 |
32 | public function testSuccessfulConversionToFileMaker()
33 | {
34 | $this->assertSame(
35 | '01/23/4567',
36 | (new DateType())->toFileMakerValue(DateTimeImmutable::createFromFormat('!m/d/Y', '01/23/4567'))
37 | );
38 | }
39 |
40 | public function testNullConversionToFileMaker()
41 | {
42 | $this->assertNull((new DateType())->toFileMakerValue(null));
43 | }
44 |
45 | public function testUnsuccessfulConversionToFileMaker()
46 | {
47 | $type = new DateType();
48 | $this->expectException(InvalidArgumentException::class);
49 | $type->toFileMakerValue('foo');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/DecimalTypeTest.php:
--------------------------------------------------------------------------------
1 | assertSame($value, $type->fromFileMakerValue($value));
18 | }
19 |
20 | public function testNullConversionFromFileMaker()
21 | {
22 | $this->assertNull((new DecimalType())->fromFileMakerValue(null));
23 | }
24 |
25 | public function testUnsuccessfulConversionFromFileMaker()
26 | {
27 | $type = new DecimalType();
28 | $this->expectException(InvalidArgumentException::class);
29 | $type->fromFileMakerValue('foo');
30 | }
31 |
32 | public function testSuccessfulConversionToFileMaker()
33 | {
34 | $type = new DecimalType();
35 | $value = Decimal::fromInteger(1);
36 | $this->assertSame($value, $type->toFileMakerValue($value));
37 | }
38 |
39 | public function testNullConversionToFileMaker()
40 | {
41 | $this->assertNull((new DecimalType())->toFileMakerValue(null));
42 | }
43 |
44 | public function testUnsuccessfulConversionToFileMaker()
45 | {
46 | $type = new DecimalType();
47 | $this->expectException(InvalidArgumentException::class);
48 | $type->toFileMakerValue('foo');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/FloatTypeTest.php:
--------------------------------------------------------------------------------
1 | assertSame(1.1, $type->fromFileMakerValue($value));
18 | }
19 |
20 | public function testNullConversionFromFileMaker()
21 | {
22 | $this->assertNull((new FloatType())->fromFileMakerValue(null));
23 | }
24 |
25 | public function testUnsuccessfulConversionFromFileMaker()
26 | {
27 | $type = new FloatType();
28 | $this->expectException(InvalidArgumentException::class);
29 | $type->fromFileMakerValue('foo');
30 | }
31 |
32 | public function testSuccessfulConversionToFileMaker()
33 | {
34 | $type = new FloatType();
35 | $this->assertSame(1.1, $type->toFileMakerValue(1.1)->asFloat());
36 | }
37 |
38 | public function testNullConversionToFileMaker()
39 | {
40 | $this->assertNull((new FloatType())->toFileMakerValue(null));
41 | }
42 |
43 | public function testUnsuccessfulConversionToFileMaker()
44 | {
45 | $type = new FloatType();
46 | $this->expectException(InvalidArgumentException::class);
47 | $type->toFileMakerValue('foo');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/IntegerTypeTest.php:
--------------------------------------------------------------------------------
1 | assertSame(1, $type->fromFileMakerValue($value));
18 | }
19 |
20 | public function testNullConversionFromFileMaker()
21 | {
22 | $this->assertNull((new IntegerType())->fromFileMakerValue(null));
23 | }
24 |
25 | public function testUnsuccessfulConversionFromFileMaker()
26 | {
27 | $type = new IntegerType();
28 | $this->expectException(InvalidArgumentException::class);
29 | $type->fromFileMakerValue('foo');
30 | }
31 |
32 | public function testSuccessfulConversionToFileMaker()
33 | {
34 | $type = new IntegerType();
35 | $this->assertSame(1, $type->toFileMakerValue(1)->asInteger());
36 | }
37 |
38 | public function testNullConversionToFileMaker()
39 | {
40 | $this->assertNull((new IntegerType())->toFileMakerValue(null));
41 | }
42 |
43 | public function testUnsuccessfulConversionToFileMaker()
44 | {
45 | $type = new IntegerType();
46 | $this->expectException(InvalidArgumentException::class);
47 | $type->toFileMakerValue('foo');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/NullableStringTypeTest.php:
--------------------------------------------------------------------------------
1 | assertSame('foo', $type->fromFileMakerValue('foo'));
16 | }
17 |
18 | public function testNullConversionFromFileMaker()
19 | {
20 | $type = new NullableStringType();
21 | $this->assertNull($type->fromFileMakerValue(''));
22 | }
23 |
24 | public function testUnsuccessfulConversionFromFileMaker()
25 | {
26 | $type = new NullableStringType();
27 | $this->expectException(InvalidArgumentException::class);
28 | $type->fromFileMakerValue(1);
29 | }
30 |
31 | public function testSuccessfulConversionToFileMaker()
32 | {
33 | $type = new NullableStringType();
34 | $this->assertSame('foo', $type->toFileMakerValue('foo'));
35 | }
36 |
37 | public function testNullConversionToFileMaker()
38 | {
39 | $type = new NullableStringType();
40 | $this->assertSame('', $type->toFileMakerValue(null));
41 | }
42 |
43 | public function testUnsuccessfulConversionToFileMaker()
44 | {
45 | $type = new NullableStringType();
46 | $this->expectException(InvalidArgumentException::class);
47 | $type->toFileMakerValue(1);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/StreamTypeTest.php:
--------------------------------------------------------------------------------
1 | prophesize(StreamInterface::class)->reveal();
18 | $this->assertSame($value, $type->fromFileMakerValue($value));
19 | }
20 |
21 | public function testNullConversionFromFileMaker()
22 | {
23 | $this->assertNull((new StreamType())->fromFileMakerValue(null));
24 | }
25 |
26 | public function testUnsuccessfulConversionFromFileMaker()
27 | {
28 | $type = new StreamType();
29 | $this->expectException(InvalidArgumentException::class);
30 | $type->fromFileMakerValue('foo');
31 | }
32 |
33 | public function testExceptionOnConversionToFileMaker()
34 | {
35 | $this->expectException(DomainException::class);
36 | $this->expectExceptionMessage('Attempted conversion to FileMaker value');
37 | (new StreamType())->toFileMakerValue($this->prophesize(StreamInterface::class)->reveal());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/StringTypeTest.php:
--------------------------------------------------------------------------------
1 | assertSame('foo', $type->fromFileMakerValue('foo'));
16 | }
17 |
18 | public function testEmptyStringConversionFromFileMaker()
19 | {
20 | $type = new StringType();
21 | $this->assertSame('', $type->fromFileMakerValue(''));
22 | }
23 |
24 | public function testUnsuccessfulConversionFromFileMaker()
25 | {
26 | $type = new StringType();
27 | $this->expectException(InvalidArgumentException::class);
28 | $type->fromFileMakerValue(1);
29 | }
30 |
31 | public function testSuccessfulConversionToFileMaker()
32 | {
33 | $type = new StringType();
34 | $this->assertSame('foo', $type->toFileMakerValue('foo'));
35 | }
36 |
37 | public function testUnsuccessfulConversionToFileMaker()
38 | {
39 | $type = new StringType();
40 | $this->expectException(InvalidArgumentException::class);
41 | $type->toFileMakerValue(1);
42 | }
43 |
44 | public function testUnsuccessfulNullConversionToFileMaker()
45 | {
46 | $type = new StringType();
47 | $this->expectException(InvalidArgumentException::class);
48 | $type->toFileMakerValue(null);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test/Repository/Builder/Type/TimeTypeTest.php:
--------------------------------------------------------------------------------
1 | assertSame($value, $type->fromFileMakerValue($value));
18 | }
19 |
20 | public function testNullConversionFromFileMaker()
21 | {
22 | $this->assertNull((new TimeType())->fromFileMakerValue(null));
23 | }
24 |
25 | public function testUnsuccessfulConversionFromFileMaker()
26 | {
27 | $type = new TimeType();
28 | $this->expectException(InvalidArgumentException::class);
29 | $type->fromFileMakerValue('foo');
30 | }
31 |
32 | public function testSuccessfulConversionToFileMaker()
33 | {
34 | $this->assertSame(
35 | '01:23:45',
36 | (new TimeType())->toFileMakerValue(DateTimeImmutable::createFromFormat('!H:i:s', '01:23:45'))
37 | );
38 | }
39 |
40 | public function testNullConversionToFileMaker()
41 | {
42 | $this->assertNull((new TimeType())->toFileMakerValue(null));
43 | }
44 |
45 | public function testUnsuccessfulConversionToFileMaker()
46 | {
47 | $type = new TimeType();
48 | $this->expectException(InvalidArgumentException::class);
49 | $type->toFileMakerValue('foo');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/test/Repository/LazyLoadedCollectionTest.php:
--------------------------------------------------------------------------------
1 | prophesize(RepositoryInterface::class);
23 | $repository->findByQuery(Argument::any())->will(function (array $parameters) use (
24 | $testCase,
25 | $first,
26 | $second,
27 | $third
28 | ) {
29 | $testCase->assertSame([
30 | '-query' => '(q1);(q2);(q3)',
31 | '-q1' => 'foo',
32 | '-q1.value' => '1',
33 | '-q2' => 'foo',
34 | '-q2.value' => '2',
35 | '-q3' => 'foo',
36 | '-q3.value' => '3',
37 | ], $parameters[0]->toParameters());
38 |
39 | return new ItemCollection([
40 | $first,
41 | $second,
42 | $third,
43 | ], 3);
44 | });
45 |
46 | $collection = new LazyLoadedCollection($repository->reveal(), 'foo', [
47 | ['foo' => 1],
48 | ['foo' => 2],
49 | ['foo' => 3],
50 | ]);
51 | $entities = [];
52 |
53 | $this->assertFalse($collection->isEmpty());
54 |
55 | foreach ($collection as $entity) {
56 | $entities[] = $entity;
57 | }
58 |
59 | $this->assertSame($first, $entities[0]);
60 | $this->assertSame($second, $entities[1]);
61 | $this->assertSame($third, $entities[2]);
62 | }
63 |
64 | public function testEmptyCollection()
65 | {
66 | $collection = new LazyLoadedCollection($this->prophesize(RepositoryInterface::class)->reveal(), 'foo', []);
67 | $this->assertTrue($collection->isEmpty());
68 | $this->assertNull($collection->first());
69 | }
70 |
71 | public function testIteratorCaching()
72 | {
73 | $collection = new LazyLoadedCollection($this->prophesize(RepositoryInterface::class)->reveal(), 'foo', []);
74 | $this->assertSame($collection->getIterator(), $collection->getIterator());
75 | }
76 |
77 | public function testFirst()
78 | {
79 | $first = new stdClass();
80 | $repository = $this->prophesize(RepositoryInterface::class);
81 | $repository->findByQuery(Argument::any())->will(function () use ($first) {
82 | return new ItemCollection([
83 | $first,
84 | new stdClass(),
85 | new stdClass(),
86 | ], 3);
87 | });
88 |
89 | $collection = new LazyLoadedCollection($repository->reveal(), 'foo', [
90 | ['foo' => 1],
91 | ['foo' => 2],
92 | ['foo' => 3],
93 | ]);
94 | $this->assertSame($first, $collection->first());
95 | }
96 |
97 | public function testCount()
98 | {
99 | $collection = new LazyLoadedCollection(
100 | $this->prophesize(RepositoryInterface::class)->reveal(),
101 | 'foo',
102 | [
103 | ['foo' => 1],
104 | ['foo' => 2],
105 | ['foo' => 3],
106 | ]
107 | );
108 | $this->assertSame(3, count($collection));
109 | }
110 |
111 | public function testGetTotalCount()
112 | {
113 | $collection = new LazyLoadedCollection(
114 | $this->prophesize(RepositoryInterface::class)->reveal(),
115 | 'foo',
116 | []
117 | );
118 | $this->assertSame(0, $collection->getTotalCount());
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/test/Repository/Query/FindQueryTest.php:
--------------------------------------------------------------------------------
1 | expectException(EmptyQueryException::class);
19 | $findQuery->toParameters();
20 | }
21 |
22 | public function testAddNoOrQueries()
23 | {
24 | $findQuery = new FindQuery();
25 |
26 | $this->expectException(InvalidArgumentException::class);
27 | $this->expectExceptionMessage('cannot be empty');
28 | $findQuery->addOrQueries();
29 | }
30 |
31 | public function testAddNoAndQueries()
32 | {
33 | $findQuery = new FindQuery();
34 |
35 | $this->expectException(InvalidArgumentException::class);
36 | $this->expectExceptionMessage('cannot be empty');
37 | $findQuery->addAndQueries();
38 | }
39 |
40 | public function testAddMultipleAndQueries()
41 | {
42 | $findQuery = new FindQuery();
43 | $findQuery->addAndQueries(new Query('foo', 'bar'), new Query('baz', 'bat', true));
44 | $this->assertSame([
45 | '-query' => '(q1,!q2)',
46 | '-q1' => 'foo',
47 | '-q1.value' => 'bar',
48 | '-q2' => 'baz',
49 | '-q2.value' => 'bat',
50 | ], $findQuery->toParameters());
51 | }
52 |
53 | public function testAddMultipleOrQueries()
54 | {
55 | $findQuery = new FindQuery();
56 | $findQuery->addOrQueries(new Query('foo', 'bar'), new Query('baz', 'bat', true));
57 | $this->assertSame([
58 | '-query' => '(q1);(!q2)',
59 | '-q1' => 'foo',
60 | '-q1.value' => 'bar',
61 | '-q2' => 'baz',
62 | '-q2.value' => 'bat',
63 | ], $findQuery->toParameters());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/test/Repository/Query/QueryTest.php:
--------------------------------------------------------------------------------
1 | assertSame('foo', $query->getFieldName());
15 | $this->assertSame('bar', $query->getValue());
16 | $this->assertFalse($query->isExclude());
17 | }
18 |
19 | public function testWithExcludeParameter()
20 | {
21 | $query = new Query('foo', 'bar', true);
22 | $this->assertTrue($query->isExclude());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/xsd/entity-metadata-5-0.xsd:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------