├── .github
├── FUNDING.yml
└── workflows
│ ├── php-src-8.1.dockerfile
│ ├── php-src-8.2.dockerfile
│ ├── php-src-8.3.dockerfile
│ ├── php-src-8.4.dockerfile
│ ├── php-src-master.dockerfile
│ ├── php-src.yml
│ ├── php.yml
│ └── phpstan.yml
├── .gitignore
├── README.md
├── Sample_results.ipynb
├── composer.json
├── composer.lock
├── docker-compose.yml
├── samples
├── FMDataAPI_Sample.php
├── HISTORY.md
└── cat.jpg
├── src
├── FMDataAPI.php
└── Supporting
│ ├── CommunicationProvider.php
│ ├── FileMakerLayout.php
│ └── FileMakerRelation.php
└── test
├── .phpunit.result.cache
├── FMDataAPIUnitTest.php
├── HashForTestInput.php
├── TestProvider.php
├── phpstan-baseline.neon
├── phpstan.neon
└── phpunit.xml
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: msyk
4 |
--------------------------------------------------------------------------------
/.github/workflows/php-src-8.1.dockerfile:
--------------------------------------------------------------------------------
1 | FROM --platform=linux/amd64 ubuntu:22.04
2 | RUN export DEBIAN_FRONTEND=noninteractive \
3 | && apt update && apt install -y --no-install-recommends \
4 | software-properties-common \
5 | ca-certificates \
6 | wget \
7 | tar \
8 | git \
9 | pkg-config build-essential \
10 | libssl-dev \
11 | autoconf \
12 | gcc \
13 | make \
14 | curl \
15 | unzip \
16 | bison \
17 | re2c \
18 | locales \
19 | ldap-utils \
20 | openssl \
21 | slapd \
22 | language-pack-de \
23 | libgmp-dev \
24 | libicu-dev \
25 | libtidy-dev \
26 | libenchant-2-dev \
27 | libbz2-dev \
28 | libsasl2-dev \
29 | libxpm-dev \
30 | libzip-dev \
31 | libsqlite3-dev \
32 | libwebp-dev \
33 | libonig-dev \
34 | libkrb5-dev \
35 | libgssapi-krb5-2 \
36 | libcurl4-openssl-dev \
37 | libxml2-dev \
38 | libxslt1-dev \
39 | libpq-dev \
40 | libreadline-dev \
41 | libldap2-dev \
42 | libsodium-dev \
43 | libargon2-dev \
44 | libmm-dev \
45 | libsnmp-dev \
46 | postgresql \
47 | postgresql-contrib \
48 | snmpd \
49 | snmp-mibs-downloader \
50 | freetds-dev \
51 | unixodbc-dev \
52 | llvm \
53 | clang \
54 | dovecot-core \
55 | dovecot-pop3d \
56 | dovecot-imapd \
57 | sendmail \
58 | firebird-dev \
59 | liblmdb-dev \
60 | libtokyocabinet-dev \
61 | libdb-dev \
62 | libqdbm-dev \
63 | libjpeg-dev \
64 | libpng-dev \
65 | libfreetype6-dev \
66 | && apt -y clean \
67 | && rm -rf /var/lib/apt/lists/*
68 | RUN git clone --depth 1 --branch PHP-8.1 https://github.com/php/php-src.git
69 | RUN cd php-src; export CC=clang; export CXX=clang++; export CFLAGS="-DZEND_TRACK_ARENA_ALLOC"; ./buildconf --force; ./configure --enable-debug --enable-mbstring --with-openssl --with-curl; make -j$(/usr/bin/nproc); make TEST_PHP_ARGS=-j$(/usr/bin/nproc) test; make install
70 | COPY composer.json /composer.json
71 | COPY composer.lock /composer.lock
72 | COPY src /src
73 | COPY test /test
74 | RUN curl -sS https://getcomposer.org/installer | php; mv composer.phar /usr/local/bin/composer; chmod +x /usr/local/bin/composer
75 | RUN cd / && composer update
76 | #RUN composer test
77 | CMD [ "/sbin/init" ]
78 |
--------------------------------------------------------------------------------
/.github/workflows/php-src-8.2.dockerfile:
--------------------------------------------------------------------------------
1 | FROM --platform=linux/amd64 ubuntu:22.04
2 | RUN export DEBIAN_FRONTEND=noninteractive \
3 | && apt update && apt install -y --no-install-recommends \
4 | software-properties-common \
5 | ca-certificates \
6 | wget \
7 | tar \
8 | git \
9 | pkg-config build-essential \
10 | libssl-dev \
11 | autoconf \
12 | gcc \
13 | make \
14 | curl \
15 | unzip \
16 | bison \
17 | re2c \
18 | locales \
19 | ldap-utils \
20 | openssl \
21 | slapd \
22 | language-pack-de \
23 | libgmp-dev \
24 | libicu-dev \
25 | libtidy-dev \
26 | libenchant-2-dev \
27 | libbz2-dev \
28 | libsasl2-dev \
29 | libxpm-dev \
30 | libzip-dev \
31 | libsqlite3-dev \
32 | libwebp-dev \
33 | libonig-dev \
34 | libkrb5-dev \
35 | libgssapi-krb5-2 \
36 | libcurl4-openssl-dev \
37 | libxml2-dev \
38 | libxslt1-dev \
39 | libpq-dev \
40 | libreadline-dev \
41 | libldap2-dev \
42 | libsodium-dev \
43 | libargon2-dev \
44 | libmm-dev \
45 | libsnmp-dev \
46 | postgresql \
47 | postgresql-contrib \
48 | snmpd \
49 | snmp-mibs-downloader \
50 | freetds-dev \
51 | unixodbc-dev \
52 | llvm \
53 | clang \
54 | dovecot-core \
55 | dovecot-pop3d \
56 | dovecot-imapd \
57 | sendmail \
58 | firebird-dev \
59 | liblmdb-dev \
60 | libtokyocabinet-dev \
61 | libdb-dev \
62 | libqdbm-dev \
63 | libjpeg-dev \
64 | libpng-dev \
65 | libfreetype6-dev \
66 | && apt -y clean \
67 | && rm -rf /var/lib/apt/lists/*
68 | RUN git clone --depth 1 --branch PHP-8.2 https://github.com/php/php-src.git
69 | RUN cd php-src; export CC=clang; export CXX=clang++; export CFLAGS="-DZEND_TRACK_ARENA_ALLOC"; ./buildconf --force; ./configure --enable-debug --enable-mbstring --with-openssl --with-curl; make -j$(/usr/bin/nproc); make TEST_PHP_ARGS=-j$(/usr/bin/nproc) test; make install
70 | COPY composer.json /composer.json
71 | COPY composer.lock /composer.lock
72 | COPY src /src
73 | COPY test /test
74 | RUN curl -sS https://getcomposer.org/installer | php; mv composer.phar /usr/local/bin/composer; chmod +x /usr/local/bin/composer
75 | RUN cd / && composer update
76 | #RUN composer test
77 | CMD [ "/sbin/init" ]
78 |
--------------------------------------------------------------------------------
/.github/workflows/php-src-8.3.dockerfile:
--------------------------------------------------------------------------------
1 | FROM --platform=linux/amd64 ubuntu:22.04
2 | RUN export DEBIAN_FRONTEND=noninteractive \
3 | && apt update && apt install -y --no-install-recommends \
4 | software-properties-common \
5 | ca-certificates \
6 | wget \
7 | tar \
8 | git \
9 | pkg-config build-essential \
10 | libssl-dev \
11 | autoconf \
12 | gcc \
13 | make \
14 | curl \
15 | unzip \
16 | bison \
17 | re2c \
18 | locales \
19 | ldap-utils \
20 | openssl \
21 | slapd \
22 | language-pack-de \
23 | libgmp-dev \
24 | libicu-dev \
25 | libtidy-dev \
26 | libenchant-2-dev \
27 | libbz2-dev \
28 | libsasl2-dev \
29 | libxpm-dev \
30 | libzip-dev \
31 | libsqlite3-dev \
32 | libwebp-dev \
33 | libonig-dev \
34 | libkrb5-dev \
35 | libgssapi-krb5-2 \
36 | libcurl4-openssl-dev \
37 | libxml2-dev \
38 | libxslt1-dev \
39 | libpq-dev \
40 | libreadline-dev \
41 | libldap2-dev \
42 | libsodium-dev \
43 | libargon2-dev \
44 | libmm-dev \
45 | libsnmp-dev \
46 | postgresql \
47 | postgresql-contrib \
48 | snmpd \
49 | snmp-mibs-downloader \
50 | freetds-dev \
51 | unixodbc-dev \
52 | llvm \
53 | clang \
54 | dovecot-core \
55 | dovecot-pop3d \
56 | dovecot-imapd \
57 | sendmail \
58 | firebird-dev \
59 | liblmdb-dev \
60 | libtokyocabinet-dev \
61 | libdb-dev \
62 | libqdbm-dev \
63 | libjpeg-dev \
64 | libpng-dev \
65 | libfreetype6-dev \
66 | && apt -y clean \
67 | && rm -rf /var/lib/apt/lists/*
68 | RUN git clone --depth 1 --branch PHP-8.3 https://github.com/php/php-src.git
69 | RUN cd php-src; export CC=clang; export CXX=clang++; export CFLAGS="-DZEND_TRACK_ARENA_ALLOC"; ./buildconf --force; ./configure --enable-debug --enable-mbstring --with-openssl --with-curl; make -j$(/usr/bin/nproc); make TEST_PHP_ARGS=-j$(/usr/bin/nproc) test; make install
70 | COPY composer.json /composer.json
71 | COPY composer.lock /composer.lock
72 | COPY src /src
73 | COPY test /test
74 | RUN curl -sS https://getcomposer.org/installer | php; mv composer.phar /usr/local/bin/composer; chmod +x /usr/local/bin/composer
75 | RUN cd / && composer update
76 | #RUN composer test
77 | CMD [ "/sbin/init" ]
78 |
--------------------------------------------------------------------------------
/.github/workflows/php-src-8.4.dockerfile:
--------------------------------------------------------------------------------
1 | FROM --platform=linux/amd64 ubuntu:22.04
2 | RUN export DEBIAN_FRONTEND=noninteractive \
3 | && apt update && apt install -y --no-install-recommends \
4 | software-properties-common \
5 | ca-certificates \
6 | wget \
7 | tar \
8 | git \
9 | pkg-config build-essential \
10 | libssl-dev \
11 | autoconf \
12 | gcc \
13 | make \
14 | curl \
15 | unzip \
16 | bison \
17 | re2c \
18 | locales \
19 | ldap-utils \
20 | openssl \
21 | slapd \
22 | language-pack-de \
23 | libgmp-dev \
24 | libicu-dev \
25 | libtidy-dev \
26 | libenchant-2-dev \
27 | libbz2-dev \
28 | libsasl2-dev \
29 | libxpm-dev \
30 | libzip-dev \
31 | libsqlite3-dev \
32 | libsqlite3-mod-spatialite \
33 | libwebp-dev \
34 | libonig-dev \
35 | libcurl4-openssl-dev \
36 | libxml2-dev \
37 | libxslt1-dev \
38 | libpq-dev \
39 | libreadline-dev \
40 | libldap2-dev \
41 | libsodium-dev \
42 | libargon2-0-dev \
43 | libmm-dev \
44 | libsnmp-dev \
45 | postgresql \
46 | postgresql-contrib \
47 | snmpd \
48 | snmp-mibs-downloader \
49 | freetds-dev \
50 | unixodbc-dev \
51 | llvm \
52 | clang \
53 | dovecot-core \
54 | dovecot-pop3d \
55 | dovecot-imapd \
56 | sendmail \
57 | firebird-dev \
58 | liblmdb-dev \
59 | libtokyocabinet-dev \
60 | libdb-dev \
61 | libqdbm-dev \
62 | libjpeg-dev \
63 | libpng-dev \
64 | libfreetype6-dev \
65 | && apt -y clean \
66 | && rm -rf /var/lib/apt/lists/*
67 | RUN git clone --depth 1 --branch PHP-8.4 https://github.com/php/php-src.git
68 | RUN cd php-src; export CC=clang; export CXX=clang++; export CFLAGS="-DZEND_TRACK_ARENA_ALLOC"; ./buildconf --force; ./configure --enable-debug --enable-mbstring --with-openssl --with-curl; make -j$(/usr/bin/nproc); make TEST_PHP_ARGS=-j$(/usr/bin/nproc) test; make install
69 | COPY composer.json /composer.json
70 | COPY composer.lock /composer.lock
71 | COPY src /src
72 | COPY test /test
73 | RUN curl -sS https://getcomposer.org/installer | php; mv composer.phar /usr/local/bin/composer; chmod +x /usr/local/bin/composer
74 | RUN cd / && composer update
75 | #RUN composer test
76 | CMD [ "/sbin/init" ]
77 |
--------------------------------------------------------------------------------
/.github/workflows/php-src-master.dockerfile:
--------------------------------------------------------------------------------
1 | FROM --platform=linux/amd64 ubuntu:22.04
2 | RUN export DEBIAN_FRONTEND=noninteractive \
3 | && apt update && apt install -y --no-install-recommends \
4 | software-properties-common \
5 | ca-certificates \
6 | wget \
7 | tar \
8 | git \
9 | pkg-config build-essential \
10 | libssl-dev \
11 | autoconf \
12 | gcc \
13 | make \
14 | curl \
15 | unzip \
16 | bison \
17 | re2c \
18 | locales \
19 | ldap-utils \
20 | openssl \
21 | slapd \
22 | language-pack-de \
23 | libgmp-dev \
24 | libicu-dev \
25 | libtidy-dev \
26 | libenchant-2-dev \
27 | libbz2-dev \
28 | libsasl2-dev \
29 | libxpm-dev \
30 | libzip-dev \
31 | libsqlite3-dev \
32 | libsqlite3-mod-spatialite \
33 | libwebp-dev \
34 | libonig-dev \
35 | libcurl4-openssl-dev \
36 | libxml2-dev \
37 | libxslt1-dev \
38 | libpq-dev \
39 | libreadline-dev \
40 | libldap2-dev \
41 | libsodium-dev \
42 | libargon2-0-dev \
43 | libmm-dev \
44 | libsnmp-dev \
45 | postgresql \
46 | postgresql-contrib \
47 | snmpd \
48 | snmp-mibs-downloader \
49 | freetds-dev \
50 | unixodbc-dev \
51 | llvm \
52 | clang \
53 | dovecot-core \
54 | dovecot-pop3d \
55 | dovecot-imapd \
56 | sendmail \
57 | firebird-dev \
58 | liblmdb-dev \
59 | libtokyocabinet-dev \
60 | libdb-dev \
61 | libqdbm-dev \
62 | libjpeg-dev \
63 | libpng-dev \
64 | libfreetype6-dev \
65 | && apt -y clean \
66 | && rm -rf /var/lib/apt/lists/*
67 | RUN git clone --depth 1 --branch master https://github.com/php/php-src.git
68 | RUN cd php-src; export CC=clang; export CXX=clang++; export CFLAGS="-DZEND_TRACK_ARENA_ALLOC"; ./buildconf --force; ./configure --enable-debug --enable-mbstring --with-openssl --with-curl; make -j$(/usr/bin/nproc); make TEST_PHP_ARGS=-j$(/usr/bin/nproc) test; make install
69 | COPY composer.json /composer.json
70 | COPY composer.lock /composer.lock
71 | COPY src /src
72 | COPY test /test
73 | RUN curl -sS https://getcomposer.org/installer | php; mv composer.phar /usr/local/bin/composer; chmod +x /usr/local/bin/composer
74 | RUN cd / && composer update
75 | #RUN composer test
76 | CMD [ "/sbin/init" ]
77 |
--------------------------------------------------------------------------------
/.github/workflows/php-src.yml:
--------------------------------------------------------------------------------
1 | name: Test with php-src
2 |
3 | on:
4 | push:
5 | pull_request:
6 | workflow_dispatch:
7 | schedule:
8 | - cron: '0 9 7,14,21,28 * *'
9 |
10 | jobs:
11 | test:
12 | name: Test with php-src
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | php: [ '8.1', '8.2', '8.3', '8.4', 'master' ]
17 | steps:
18 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
19 | - name: Run docker compose
20 | shell: bash
21 | run: |
22 | cp .github/workflows/php-src-${{ matrix.php }}.dockerfile Dockerfile
23 | docker compose up -d
24 | sleep 30
25 |
26 | - name: Run testing
27 | shell: bash
28 | run: |
29 | sleep 30
30 | docker compose run web sh -c "cd / && composer test"
31 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - "*"
7 | pull_request:
8 | branches: [ 'master', 'main' ]
9 | workflow_dispatch:
10 | schedule:
11 | - cron: '0 9 15 * *'
12 |
13 | jobs:
14 | test:
15 | name: Test
16 | runs-on: ${{ matrix.os }}
17 | env:
18 | PHP_EXTENSIONS: mbstring, json, bcmath, zip, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite, exif, gd, ldap, fileinfo
19 | strategy:
20 | matrix:
21 | # https://github.com/shivammathur/setup-php?tab=readme-ov-file#cloud-osplatform-support
22 | os: [ 'ubuntu-22.04', 'windows-2022', 'macos-14' ]
23 | php-version: [ '8.1', '8.2', '8.3', '8.4' ]
24 | steps:
25 | - name: Checkout
26 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
27 |
28 | - name: Install PHP with extensions
29 | uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0
30 | with:
31 | php-version: ${{ matrix.php-version }}
32 | coverage: none
33 | extensions: ${{ env.PHP_EXTENSIONS }}
34 |
35 | - name: Prepare environment
36 | run: composer update
37 |
38 | - name: Run testing
39 | run: |
40 | php -v
41 | composer test
42 |
--------------------------------------------------------------------------------
/.github/workflows/phpstan.yml:
--------------------------------------------------------------------------------
1 | name: PHPStan
2 |
3 | on:
4 | push:
5 | branches:
6 | - "*"
7 | pull_request:
8 | branches: [ 'master', 'main' ]
9 | pull_request_target:
10 | types:
11 | - closed
12 |
13 | jobs:
14 | run:
15 | name: Run PHPStan
16 | runs-on: 'ubuntu-latest'
17 | strategy:
18 | matrix:
19 | level: [ 1, 2 ]
20 | include:
21 | - current-level: 1
22 | - max-level: 2
23 | steps:
24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25 |
26 | - name: Setup PHP
27 | uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0
28 | with:
29 | php-version: '8.4'
30 |
31 | - name: Remove phpDocumentor temporarily and Install PHPStan
32 | run: composer remove --dev --no-update phpdocumentor/phpdocumentor; composer update
33 |
34 | - name: Restore cached baseline for PHPStan
35 | id: cache-baseline-restore
36 | uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
37 | with:
38 | path: |
39 | test/phpstan-baseline.neon
40 | key: phpstan-baseline-${{ github.run_id }}"
41 | restore-keys: |
42 | phpstan-baseline-
43 |
44 | - name: Run PHPStan
45 | if: matrix.level == matrix.current-level
46 | continue-on-error: true
47 | run: |
48 | ./vendor/bin/phpstan analyse --memory-limit 1G -c test/phpstan.neon src test -l "${{ matrix.level }}"
49 |
50 | - name: Run PHPStan
51 | if: matrix.level > matrix.current-level
52 | continue-on-error: true
53 | run: |
54 | ./vendor/bin/phpstan analyse --memory-limit 1G -c test/phpstan.neon src test -l "${{ matrix.level }}"
55 | exit 0
56 |
57 | - name: Generate the baseline for PHPStan
58 | if: matrix.level == matrix.max-level && github.event.pull_request.merged == true
59 | continue-on-error: true
60 | run: |
61 | ./vendor/bin/phpstan analyse --memory-limit 1G -c test/phpstan.neon --generate-baseline test/phpstan-baseline.neon src test -vvv --debug -l "${{ matrix.level }}"
62 | exit 0
63 |
64 | - name: Save the baseline for PHPStan
65 | id: cache-baseline-save
66 | if: matrix.level == matrix.max-level && github.event.pull_request.merged == true
67 | uses: actions/cache/save@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
68 | with:
69 | path: |
70 | test/phpstan-baseline.neon
71 | key: phpstan-baseline-${{ github.run_id }}"
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor/
3 | .phpdoc
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FMDataAPI Ver.33 [](https://github.com/msyk/FMDataAPI/actions/workflows/php.yml)
2 |
3 | by Masayuki Nii (nii@msyk.net)
4 |
5 | FMDataAPI is a class developed in PHP to access FileMaker database
6 | with Claris FileMaker Data API.
7 |
8 | ## Contributers
9 |
10 | They created pull requests. Thanks for cooperating.
11 |
12 | - Atsushi Matsuo
13 | - darnel
14 | - Craig Smith
15 | - Bernhard Schulz
16 | - montaniasystemab
17 | - Rickard Andersson
18 | - Julien @AnnoyingTechnology
19 | - Tom Kuijer
20 | - Thijs Meijer
21 | - Patrick Janser
22 | - Roger Engström
23 | - Stathis Askaridis
24 |
25 | ## At a Glance
26 |
27 | The FileMaker database named "TestDB.fmp12" is hosted on localhost, and
28 | it sets the "fmrest" as access privilege. The account to connect with REST API is "web"
29 | and "password." This database has the layout named "person_layout," and you
30 | can use the layout name as a property of the FMDataAPI instance. The return
31 | value of the "query" method is Iterator and can repeat in the foreach statement
32 | with each record in the query result. This layout has the field named
33 | "FamilyName" and "GivenName," and can use the field name as a property.
34 |
35 | ```
36 | $fmdb = new FMDataAPI("TestDB", "web", "password");
37 | $result = $fmdb->person_layout->query();
38 | foreach ($result as $record) {
39 | echo "name: {$record->FamilyName}, {$record->GivenName}";
40 | }
41 | ```
42 |
43 | For more details, I'd like to read codes and comments in file samples/FMDataAPI_Sample.php.
44 |
45 | API Document is here:
46 | https://inter-mediator.info/FMDataAPI/packages/INTER-Mediator-FileMakerServer-RESTAPI.html
47 | ## What's This?
48 |
49 | The FileMaker Data API is the new feature of FileMaker Server 16,
50 | and it's the API with REST-based database operations.
51 | Although the Custom Web Publishing is the way to access the database
52 | for a long while, FileMaker Inc. has introduced the modern feature to operate
53 | the database. The current version of FMDataAPI works on just FileMaker 18 and 19 platform.
54 |
55 | For now, I'm focusing on developing the web application framework "INTER-Mediator"
56 | (https://inter-mediator.com/ or https://github.com/INTER-Mediator/INTER-Mediator.git)
57 | which can develop the core features of database-driven web application
58 | with declarative descriptions. INTER-Mediator has already supported the Custom
59 | Web Publishing with FX.php, and I develop codes here for support REST APIs.
60 |
61 | Bug reports and contributions are welcome.
62 |
63 | ## Installing to Your Project
64 |
65 | FMDataAPI has "composer.json," so you can add your composer.json file in your project as below.
66 |
67 | ```
68 | ...
69 | "require": {
70 | ...
71 | "inter-mediator/fmdataapi":"33"
72 | } ...
73 | ```
74 |
75 | ## About Files and Directories
76 |
77 | - src/FMDataAPI.php
78 | - The core class, and you just use this for your application.
79 | This class and supporting classes are object-oriented REST API
80 | wrappers.
81 | - src/Supporting/**.php
82 | - The supporting classes for the FMDataAPI class. Perhaps you don't need to create these classes, but you have to handle methods on them.
83 | - composer.json, composer.lock
84 | - Composer information files.
85 | - Sample_results.ipynb
86 | - Sample program and results with Jupyter Notebook style. Sorry for slight old version results.
87 | - samples/FMDataAPI_Sample.php and cat.jpg
88 | - This is the sample program of FMDataAPI class, and shows how to
89 | use FMDataAPI class. It includes rich comments,
90 | but Sample_results.ipynb is more informative.
91 | - README.md, .gitignore
92 | - These are for GitHub.
93 | - test
94 | - Some files for unit testing.
95 | - .github, docker-compose.yml
96 | - Files for GitHub Actions to run CI jobs.
97 |
98 | ## Licence
99 |
100 | MIT License
101 |
102 | ## Acknoledgement
103 |
104 | - Thanks to Atsushi Matsuo. Your script is quite helpful to implement the "localserver" feature.
105 | (https://gist.github.com/matsuo/ef5cb7c98bb494d507731886883bcbc1) Moreover, thanks for updating and fixing bugs.
106 | - Thanks to Frank Gonzalez. Your bug report is brilliant, and I could fix it quickly.
107 | - Thanks to base64bits for coding about container field.
108 | - Thanks to phpsa for bug fix.
109 | - Thanks to Flexboom for bug fix.
110 | - Thanks to schube for bug fix.
111 | - Thanks to frankeg for bug fix.
112 |
113 | ## History
114 |
115 | (Previous history is [here](samples/HISTORY.md))
116 |
117 | - 2021-02-10: [Ver.22]
118 | Setting the timeout value about cURL. Thanks to @montaniasystemab. Also thanks to @AnnoyingTechnology for correcting.
119 | - 2021-11-11: [Ver.23]
120 | File structure is updated for PSR-4. Thanks to tkuijer.
121 | - 2021-12-23: [Ver.24]
122 | Bug fix for portal limit parameter. Thanks to tkuijer.
123 | - 2022-03-24: [Ver.25]
124 | Add methods(getFirstRecord, getLastRecord, getRecords) to the FileMakerRelation class.
125 | - 2022-03-26: [Ver.26]
126 | Add methods(setFieldHTMLEncoding, getFieldHTMLEncoding) to the FMDataAPI class.
127 | These are going to use for compatibility mode of FileMaker API for PHP.
128 | - 2022-06-06: [Ver.27]
129 | Dropped the support of PHP5, the minimum version is PHP 7.1, but 7.2 or later is recommended.
130 | - 2022-08-04: [Ver.28]
131 | Added the getContainerData(URL) method to the FMDataAPI class for accessing container data from the url containing /Streaming/MainDB.
132 | [BUG FIX] The FileMakerRelation class's toArray method didn't return an array (Thanks to Talwinder Singh).
133 | - 2022-12-28: [Ver.29]
134 | Fixed the 'HTTP/2 stream 0 was not closed cleanly' problem with the new FileMaker (Thanks to @thijsmeijer).
135 | Also fixed the getPortalNames issue for single record relation (Thanks to @PGMMattias).
136 | - 2023-06-20: [Ver.30]
137 | The toArray() method bug fixed. In same cases, it returned []. (Thanks to @PGMMattias).
138 | - 2023-11-24: [Ver.31]
139 | The curlErrorMessage() method returns the error message from curl (Thanks to @P1-Roger).
140 | Corrected phpdoc issue (Thanks to @patacra).
141 | - 2024-10-10: [Ver.32]
142 | From this version, the minimum PHP version is 8.1.
143 | Fix SSL certificate check errors by using the system's certificate authorities (Thanks to @patacra).
144 | FileMakerLayout::getMetadataOld and getMetadata methods don't return the false value in the case of log-in error.
145 | It returns just null.
146 | - 2025-03-19: [Ver.33]
147 | The query method supports date format parameter (Thanks to @stathisaska).
148 | The debug property of the CommunicationProvider class initializes the bool false value (Thanks to Bernhard).
149 |
--------------------------------------------------------------------------------
/Sample_results.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# FMDataAPI Sample Results\n",
8 | "\n",
9 | "by Masayuki Nii (nii@msyk.net)\n",
10 | "\n",
11 | "FMDataAPI is a class developed by PHP to access FileMaker database with FileMaker Data API.\n",
12 | "\n",
13 | "The repository is https://github.com/msyk/FMDataAPI.\n",
14 | "\n",
15 | "API Document is http://inter-mediator.info/FMDataAPI/namespaces/INTERMediator.FileMakerServer.RESTAPI.html.\n",
16 | "\n",
17 | "The identifier of Composer is \"inter-mediator/fmdataapi\".\n",
18 | "\n",
19 | "Ths notebook aims to show the results of FMDataAPI in short hand. You don't have to prepare PHP even FileMaker Server because you can see all results with codes in this notebook."
20 | ]
21 | },
22 | {
23 | "cell_type": "markdown",
24 | "metadata": {},
25 | "source": [
26 | "## Preparing to Use FMDataAPI in PHP way\n",
27 | "\n",
28 | "First of all, the FMDataAPI.php file has to be included. All classes are defined in it. Of course, you can specify partial or full path, or composer based class path resolving."
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": 1,
34 | "metadata": {},
35 | "outputs": [
36 | {
37 | "data": {
38 | "text/plain": [
39 | "\u001b[35m1\u001b[39m"
40 | ]
41 | },
42 | "execution_count": 1,
43 | "metadata": {},
44 | "output_type": "execute_result"
45 | }
46 | ],
47 | "source": [
48 | "include_once \"FMDataAPI.php\";"
49 | ]
50 | },
51 | {
52 | "cell_type": "markdown",
53 | "metadata": {},
54 | "source": [
55 | "For your convenience, the main class name FMDataAPI is defined at the current namespace."
56 | ]
57 | },
58 | {
59 | "cell_type": "code",
60 | "execution_count": 2,
61 | "metadata": {},
62 | "outputs": [],
63 | "source": [
64 | "use INTERMediator\\FileMakerServer\\RESTAPI\\FMDataAPI as FMDataAPI;"
65 | ]
66 | },
67 | {
68 | "cell_type": "markdown",
69 | "metadata": {},
70 | "source": [
71 | "## Getting Access to FileMaker DB\n",
72 | "Instanticate the class FMDataAPI with database name, user name, password and host.Although the port number and protocol can be set in parameters of constractor, these parameters can be omitted with default values."
73 | ]
74 | },
75 | {
76 | "cell_type": "code",
77 | "execution_count": 3,
78 | "metadata": {},
79 | "outputs": [
80 | {
81 | "data": {
82 | "text/plain": [
83 | "\u001b[34;4mINTERMediator\\FileMakerServer\\RESTAPI\\FMDataAPI\u001b[39;24m {#200}"
84 | ]
85 | },
86 | "execution_count": 3,
87 | "metadata": {},
88 | "output_type": "execute_result"
89 | }
90 | ],
91 | "source": [
92 | "$fmdb = new FMDataAPI(\"TestDB\", \"web\", \"password\", \"localserver\");"
93 | ]
94 | },
95 | {
96 | "cell_type": "markdown",
97 | "metadata": {},
98 | "source": [
99 | "### Very Simple Query, Getting All Records\n",
100 | "The FMDataAPI has the property as the same name of layout. This sample database has the 'person_layout' layout, so '$fmdb->person_layout' refers FMLayout object fo the proxy of the layout. FMLayout class has the 'query' method and returns FileMakerRelation class's object. The condition spefied in parameter is same as FileMaker's Find Record API. This example means querying all record from the \"person_layout\" layout."
101 | ]
102 | },
103 | {
104 | "cell_type": "code",
105 | "execution_count": 4,
106 | "metadata": {},
107 | "outputs": [
108 | {
109 | "data": {
110 | "text/plain": [
111 | "\u001b[34;4mINTERMediator\\FileMakerServer\\RESTAPI\\Supporting\\FileMakerRelation\u001b[39;24m {#192}"
112 | ]
113 | },
114 | "execution_count": 4,
115 | "metadata": {},
116 | "output_type": "execute_result"
117 | }
118 | ],
119 | "source": [
120 | "$result = $fmdb->person_layout->query();"
121 | ]
122 | },
123 | {
124 | "cell_type": "markdown",
125 | "metadata": {},
126 | "source": [
127 | "The 'httpStatus()' method returns the HTTP status code in the latest response."
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": 5,
133 | "metadata": {},
134 | "outputs": [
135 | {
136 | "data": {
137 | "text/plain": [
138 | "HTTP Status: 200\n"
139 | ]
140 | },
141 | "execution_count": 5,
142 | "metadata": {},
143 | "output_type": "execute_result"
144 | }
145 | ],
146 | "source": [
147 | "echo \"HTTP Status: {$fmdb->httpStatus()}\";"
148 | ]
149 | },
150 | {
151 | "cell_type": "markdown",
152 | "metadata": {},
153 | "source": [
154 | "The following two methods return the error code and message of the latest API call which is submitted in query() method. You can check API calling succeed or fail if error code is or isn't 0 every after API calling methods."
155 | ]
156 | },
157 | {
158 | "cell_type": "code",
159 | "execution_count": 6,
160 | "metadata": {},
161 | "outputs": [
162 | {
163 | "data": {
164 | "text/plain": [
165 | "Error Code: 0\n"
166 | ]
167 | },
168 | "execution_count": 6,
169 | "metadata": {},
170 | "output_type": "execute_result"
171 | },
172 | {
173 | "data": {
174 | "text/plain": [
175 | "Error Message: \n"
176 | ]
177 | },
178 | "execution_count": 6,
179 | "metadata": {},
180 | "output_type": "execute_result"
181 | }
182 | ],
183 | "source": [
184 | "echo \"Error Code: {$fmdb->errorCode()}\";\n",
185 | "echo \"Error Message: {$fmdb->errorMessage()}\";"
186 | ]
187 | },
188 | {
189 | "cell_type": "markdown",
190 | "metadata": {},
191 | "source": [
192 | "### Accessing Queried Data\n",
193 | "The FileMakerRelation class implements the Iterator interface and it can repeat with 'foreach.' The \\$record also refers a FileMakerRelation object but it is for single record. This layout has fields as like 'id', 'name', 'mail' and so on, and the field name can be handle as a property name of the the record referring with \\$record. The 'person' table in database has 3 records and you can see 3 outs below. The recordId or redId is required to update the record, and it can get by the getRecordId method."
194 | ]
195 | },
196 | {
197 | "cell_type": "code",
198 | "execution_count": 7,
199 | "metadata": {},
200 | "outputs": [
201 | {
202 | "data": {
203 | "text/plain": [
204 | "[id]1, [name]Masayuki Nii, [mail]Dog\n"
205 | ]
206 | },
207 | "execution_count": 7,
208 | "metadata": {},
209 | "output_type": "execute_result"
210 | },
211 | {
212 | "data": {
213 | "text/plain": [
214 | "__[recordId = 1]\n"
215 | ]
216 | },
217 | "execution_count": 7,
218 | "metadata": {},
219 | "output_type": "execute_result"
220 | },
221 | {
222 | "data": {
223 | "text/plain": [
224 | "[id]2, [name]Someone, [mail]msyk@msyk.net\n"
225 | ]
226 | },
227 | "execution_count": 7,
228 | "metadata": {},
229 | "output_type": "execute_result"
230 | },
231 | {
232 | "data": {
233 | "text/plain": [
234 | "__[recordId = 2]\n"
235 | ]
236 | },
237 | "execution_count": 7,
238 | "metadata": {},
239 | "output_type": "execute_result"
240 | },
241 | {
242 | "data": {
243 | "text/plain": [
244 | "[id]3, [name]Anyone, [mail]Dog\n"
245 | ]
246 | },
247 | "execution_count": 7,
248 | "metadata": {},
249 | "output_type": "execute_result"
250 | },
251 | {
252 | "data": {
253 | "text/plain": [
254 | "__[recordId = 3]\n"
255 | ]
256 | },
257 | "execution_count": 7,
258 | "metadata": {},
259 | "output_type": "execute_result"
260 | }
261 | ],
262 | "source": [
263 | "if (!is_null($result)) {\n",
264 | " foreach ($result as $record) {\n",
265 | " echo \"[id]{$record->id}, [name]{$record->name}, [mail]{$record->mail}\";\n",
266 | " echo \"__[recordId = {$record->getRecordId()}]\";\n",
267 | " }\n",
268 | "}"
269 | ]
270 | },
271 | {
272 | "cell_type": "markdown",
273 | "metadata": {},
274 | "source": [
275 | "### Accessing to Portal Data\n",
276 | "A portal name property returns records of portal as FileMakerRelation object. You can repeat with foreach for the portal records.\n",
277 | "\n",
278 | "Technically portal field has to be refered as \"contact_to::id\" but it can be an indentifier in PHP. In this case you can call field method as like 'field(\"summary\", \"contact_to\").' If the field belongs to the table occurrence for the portal, you can refer the field as like '$item->id.' If the field belongs to another table occurrence, you have to call the 'field()' method.\n",
279 | "\n",
280 | "If the object name of the portal is blank, it can be referred as the table occurrence name. If the object name is specified, you have to access with the object name and it means you have to call 'field()' method to get the value."
281 | ]
282 | },
283 | {
284 | "cell_type": "code",
285 | "execution_count": 8,
286 | "metadata": {},
287 | "outputs": [
288 | {
289 | "data": {
290 | "text/plain": [
291 | "id: 1, name: Masayuki Nii, mail: Dog\n"
292 | ]
293 | },
294 | "execution_count": 8,
295 | "metadata": {},
296 | "output_type": "execute_result"
297 | },
298 | {
299 | "data": {
300 | "text/plain": [
301 | "__[PORTAL(contact_to)][id]1, [summary]Telephone\n"
302 | ]
303 | },
304 | "execution_count": 8,
305 | "metadata": {},
306 | "output_type": "execute_result"
307 | },
308 | {
309 | "data": {
310 | "text/plain": [
311 | "__[PORTAL(contact_to)][id]2, [summary]Meetings\n"
312 | ]
313 | },
314 | "execution_count": 8,
315 | "metadata": {},
316 | "output_type": "execute_result"
317 | },
318 | {
319 | "data": {
320 | "text/plain": [
321 | "__[PORTAL(contact_to)][id]3, [summary]Mail\n"
322 | ]
323 | },
324 | "execution_count": 8,
325 | "metadata": {},
326 | "output_type": "execute_result"
327 | },
328 | {
329 | "data": {
330 | "text/plain": [
331 | "id: 2, name: Someone, mail: msyk@msyk.net\n"
332 | ]
333 | },
334 | "execution_count": 8,
335 | "metadata": {},
336 | "output_type": "execute_result"
337 | },
338 | {
339 | "data": {
340 | "text/plain": [
341 | "__[PORTAL(contact_to)][id]4, [summary]Calling\n"
342 | ]
343 | },
344 | "execution_count": 8,
345 | "metadata": {},
346 | "output_type": "execute_result"
347 | },
348 | {
349 | "data": {
350 | "text/plain": [
351 | "__[PORTAL(contact_to)][id]5, [summary]Telephone\n"
352 | ]
353 | },
354 | "execution_count": 8,
355 | "metadata": {},
356 | "output_type": "execute_result"
357 | },
358 | {
359 | "data": {
360 | "text/plain": [
361 | "id: 3, name: Anyone, mail: Dog\n"
362 | ]
363 | },
364 | "execution_count": 8,
365 | "metadata": {},
366 | "output_type": "execute_result"
367 | },
368 | {
369 | "data": {
370 | "text/plain": [
371 | "__[PORTAL(contact_to)][id]6, [summary]Meeting\n"
372 | ]
373 | },
374 | "execution_count": 8,
375 | "metadata": {},
376 | "output_type": "execute_result"
377 | },
378 | {
379 | "data": {
380 | "text/plain": [
381 | "__[PORTAL(contact_to)][id]7, [summary]Mail etcsss\n"
382 | ]
383 | },
384 | "execution_count": 8,
385 | "metadata": {},
386 | "output_type": "execute_result"
387 | }
388 | ],
389 | "source": [
390 | "if (!is_null($result)) {\n",
391 | " foreach ($result as $record) {\n",
392 | " echo \"id: {$record->id}, name: {$record->name}, mail: {$record->mail}\";\n",
393 | " $contacts = $record->Contact;\n",
394 | " foreach ($contacts as $item) {\n",
395 | " $id = $item->field(\"id\", \"contact_to\");\n",
396 | " $summary = $item->field(\"summary\", \"contact_to\");\n",
397 | " echo \"__[PORTAL(contact_to)][id]{$id}, [summary]{$summary}\";\n",
398 | " }\n",
399 | " }\n",
400 | "}"
401 | ]
402 | },
403 | {
404 | "cell_type": "markdown",
405 | "metadata": {},
406 | "source": [
407 | "The FileMakerRelation object from 'query()' method can be accessed as like the 'cursor' style repeating.\n",
408 | "The 'count()' method returns the number of records in response. The variable $result referes current record and you can get the field value with the propaty having the same field name.\n",
409 | "The portal can be done with same way. The 'next()' method steps forward the pointer of current record.\n",
410 | "Before examining the cursor looping, the pointer has to move to the first record with the rewind() method."
411 | ]
412 | },
413 | {
414 | "cell_type": "code",
415 | "execution_count": 9,
416 | "metadata": {},
417 | "outputs": [
418 | {
419 | "data": {
420 | "text/plain": [
421 | "[id]1, [name]Masayuki Nii, [mail]Dog\n"
422 | ]
423 | },
424 | "execution_count": 9,
425 | "metadata": {},
426 | "output_type": "execute_result"
427 | },
428 | {
429 | "data": {
430 | "text/plain": [
431 | "__[PORTAL(contact_to)][id]1, [summary]Telephone\n"
432 | ]
433 | },
434 | "execution_count": 9,
435 | "metadata": {},
436 | "output_type": "execute_result"
437 | },
438 | {
439 | "data": {
440 | "text/plain": [
441 | "__[PORTAL(contact_to)][id]2, [summary]Meetings\n"
442 | ]
443 | },
444 | "execution_count": 9,
445 | "metadata": {},
446 | "output_type": "execute_result"
447 | },
448 | {
449 | "data": {
450 | "text/plain": [
451 | "__[PORTAL(contact_to)][id]3, [summary]Mail\n"
452 | ]
453 | },
454 | "execution_count": 9,
455 | "metadata": {},
456 | "output_type": "execute_result"
457 | },
458 | {
459 | "data": {
460 | "text/plain": [
461 | "__[PORTAL(history_to)][startdate]04/01/2001, [enddate]03/31/2003\n"
462 | ]
463 | },
464 | "execution_count": 9,
465 | "metadata": {},
466 | "output_type": "execute_result"
467 | },
468 | {
469 | "data": {
470 | "text/plain": [
471 | "__[PORTAL(history_to)][startdate]04/01/2003, [enddate]03/31/2007\n"
472 | ]
473 | },
474 | "execution_count": 9,
475 | "metadata": {},
476 | "output_type": "execute_result"
477 | },
478 | {
479 | "data": {
480 | "text/plain": [
481 | "[id]2, [name]Someone, [mail]msyk@msyk.net\n"
482 | ]
483 | },
484 | "execution_count": 9,
485 | "metadata": {},
486 | "output_type": "execute_result"
487 | },
488 | {
489 | "data": {
490 | "text/plain": [
491 | "__[PORTAL(contact_to)][id]4, [summary]Calling\n"
492 | ]
493 | },
494 | "execution_count": 9,
495 | "metadata": {},
496 | "output_type": "execute_result"
497 | },
498 | {
499 | "data": {
500 | "text/plain": [
501 | "__[PORTAL(contact_to)][id]5, [summary]Telephone\n"
502 | ]
503 | },
504 | "execution_count": 9,
505 | "metadata": {},
506 | "output_type": "execute_result"
507 | },
508 | {
509 | "data": {
510 | "text/plain": [
511 | "[id]3, [name]Anyone, [mail]Dog\n"
512 | ]
513 | },
514 | "execution_count": 9,
515 | "metadata": {},
516 | "output_type": "execute_result"
517 | },
518 | {
519 | "data": {
520 | "text/plain": [
521 | "__[PORTAL(contact_to)][id]6, [summary]Meeting\n"
522 | ]
523 | },
524 | "execution_count": 9,
525 | "metadata": {},
526 | "output_type": "execute_result"
527 | },
528 | {
529 | "data": {
530 | "text/plain": [
531 | "__[PORTAL(contact_to)][id]7, [summary]Mail etcsss\n"
532 | ]
533 | },
534 | "execution_count": 9,
535 | "metadata": {},
536 | "output_type": "execute_result"
537 | }
538 | ],
539 | "source": [
540 | "$result->rewind();\n",
541 | "for ($i = 0; $i < $result->count(); $i++) {\n",
542 | " echo \"[id]{$result->id}, [name]{$result->name}, [mail]{$result->mail}\";\n",
543 | " $contacts = $result->Contact;\n",
544 | " $contacts->rewind();\n",
545 | " for ($j = 0; $j < $contacts->count(); $j++) {\n",
546 | " $id = $contacts->field(\"id\", \"contact_to\");\n",
547 | " $summary = $contacts->field(\"summary\", \"contact_to\");\n",
548 | " echo \"__[PORTAL(contact_to)][id]{$id}, [summary]{$summary}\";\n",
549 | " $contacts->next();\n",
550 | " }\n",
551 | " $histories = $result->History;\n",
552 | " $histories->rewind();\n",
553 | " for ($j = 0; $j < $histories->count(); $j++) {\n",
554 | " $std = $histories->field(\"startdate\", \"history_to\");\n",
555 | " $endd = $histories->field(\"enddate\", \"history_to\");\n",
556 | " echo \"__[PORTAL(history_to)][startdate]{$std}, [enddate]{$endd}\";\n",
557 | " $histories->next();\n",
558 | " }\n",
559 | " $result->next();\n",
560 | "}"
561 | ]
562 | },
563 | {
564 | "cell_type": "markdown",
565 | "metadata": {},
566 | "source": [
567 | "### Sort Fields\n",
568 | "The second parameter of query method specifies fields for sorting with array of array data. The innner array has one or two elements with the field name and the dicrection as below. This menas ordering by the id field decendly."
569 | ]
570 | },
571 | {
572 | "cell_type": "code",
573 | "execution_count": 10,
574 | "metadata": {},
575 | "outputs": [
576 | {
577 | "data": {
578 | "text/plain": [
579 | "[id]3, [name]Anyone, [mail] Dog\n"
580 | ]
581 | },
582 | "execution_count": 10,
583 | "metadata": {},
584 | "output_type": "execute_result"
585 | },
586 | {
587 | "data": {
588 | "text/plain": [
589 | "[id]2, [name]Someone, [mail] msyk@msyk.net\n"
590 | ]
591 | },
592 | "execution_count": 10,
593 | "metadata": {},
594 | "output_type": "execute_result"
595 | },
596 | {
597 | "data": {
598 | "text/plain": [
599 | "[id]1, [name]Masayuki Nii, [mail] Dog\n"
600 | ]
601 | },
602 | "execution_count": 10,
603 | "metadata": {},
604 | "output_type": "execute_result"
605 | }
606 | ],
607 | "source": [
608 | "$result = $fmdb->person_layout->query(null, [[\"id\", \"descend\"]]);\n",
609 | "if (!is_null($result)) {\n",
610 | " foreach ($result as $record) {\n",
611 | " echo \"[id]{$record->id}, [name]{$record->name}, [mail] {$record->mail}\";\n",
612 | " }\n",
613 | "}"
614 | ]
615 | },
616 | {
617 | "cell_type": "markdown",
618 | "metadata": {},
619 | "source": [
620 | "### Start Record and Limit Records\n",
621 | "The third and fourth parameter of the query method specify the start record number and the limit number of record. The following query means query 5 records from 20th record with ordered by the f3 fields."
622 | ]
623 | },
624 | {
625 | "cell_type": "code",
626 | "execution_count": 11,
627 | "metadata": {},
628 | "outputs": [
629 | {
630 | "data": {
631 | "text/plain": [
632 | "[postal code]1000400, [place]東京都新島村以下に掲載がない場合\n"
633 | ]
634 | },
635 | "execution_count": 11,
636 | "metadata": {},
637 | "output_type": "execute_result"
638 | },
639 | {
640 | "data": {
641 | "text/plain": [
642 | "[postal code]1000401, [place]東京都新島村若郷\n"
643 | ]
644 | },
645 | "execution_count": 11,
646 | "metadata": {},
647 | "output_type": "execute_result"
648 | },
649 | {
650 | "data": {
651 | "text/plain": [
652 | "[postal code]1000402, [place]東京都新島村本村\n"
653 | ]
654 | },
655 | "execution_count": 11,
656 | "metadata": {},
657 | "output_type": "execute_result"
658 | },
659 | {
660 | "data": {
661 | "text/plain": [
662 | "[postal code]1000511, [place]東京都新島村式根島\n"
663 | ]
664 | },
665 | "execution_count": 11,
666 | "metadata": {},
667 | "output_type": "execute_result"
668 | },
669 | {
670 | "data": {
671 | "text/plain": [
672 | "[postal code]1000601, [place]東京都神津島村神津島村一円\n"
673 | ]
674 | },
675 | "execution_count": 11,
676 | "metadata": {},
677 | "output_type": "execute_result"
678 | }
679 | ],
680 | "source": [
681 | "$result = $fmdb->postalcode->query(null, [[\"f3\"]], 20, 5);\n",
682 | "if (!is_null($result)) {\n",
683 | " foreach ($result as $record) {\n",
684 | " echo \"[postal code]{$record->f3}, [place]{$record->f7}{$record->f8}{$record->f9}\";\n",
685 | " }\n",
686 | "}"
687 | ]
688 | },
689 | {
690 | "cell_type": "markdown",
691 | "metadata": {},
692 | "source": [
693 | "### Query with Condtion\n",
694 | "The 'query()' method can have several parameters. The first parameter specifies condtions with array of array. Simply key is a field name and value is the value. The following condition means just query the id field is \"1\"."
695 | ]
696 | },
697 | {
698 | "cell_type": "code",
699 | "execution_count": 12,
700 | "metadata": {},
701 | "outputs": [
702 | {
703 | "data": {
704 | "text/plain": [
705 | "[id]1, [name]Masayuki Nii, [mail] Dog\n"
706 | ]
707 | },
708 | "execution_count": 12,
709 | "metadata": {},
710 | "output_type": "execute_result"
711 | }
712 | ],
713 | "source": [
714 | "$result = $fmdb->person_layout->query([[\"id\" => \"1\"]]);\n",
715 | "if (!is_null($result)) {\n",
716 | " foreach ($result as $record) {\n",
717 | " echo \"[id]{$record->id}, [name]{$record->name}, [mail] {$record->mail}\";\n",
718 | " }\n",
719 | "}"
720 | ]
721 | },
722 | {
723 | "cell_type": "markdown",
724 | "metadata": {},
725 | "source": [
726 | "Next query means character 中 contains in the f9 field."
727 | ]
728 | },
729 | {
730 | "cell_type": "code",
731 | "execution_count": 13,
732 | "metadata": {},
733 | "outputs": [
734 | {
735 | "data": {
736 | "text/plain": [
737 | "[postal code]1030008, [place]東京都中央区日本橋中洲\n"
738 | ]
739 | },
740 | "execution_count": 13,
741 | "metadata": {},
742 | "output_type": "execute_result"
743 | },
744 | {
745 | "data": {
746 | "text/plain": [
747 | "[postal code]1610035, [place]東京都新宿区中井\n"
748 | ]
749 | },
750 | "execution_count": 13,
751 | "metadata": {},
752 | "output_type": "execute_result"
753 | },
754 | {
755 | "data": {
756 | "text/plain": [
757 | "[postal code]1610032, [place]東京都新宿区中落合\n"
758 | ]
759 | },
760 | "execution_count": 13,
761 | "metadata": {},
762 | "output_type": "execute_result"
763 | }
764 | ],
765 | "source": [
766 | "$result = $fmdb->postalcode->query([[\"f9\" => \"中\"]], null, 1, 3);\n",
767 | "if (!is_null($result)) {\n",
768 | " foreach ($result as $record) {\n",
769 | " echo \"[postal code]{$record->f3}, [place]{$record->f7}{$record->f8}{$record->f9}\";\n",
770 | " }\n",
771 | "}"
772 | ]
773 | },
774 | {
775 | "cell_type": "markdown",
776 | "metadata": {},
777 | "source": [
778 | "Next query means the f8 field is just 中央区 AND character 中 contains in the f9 field."
779 | ]
780 | },
781 | {
782 | "cell_type": "code",
783 | "execution_count": 14,
784 | "metadata": {},
785 | "outputs": [
786 | {
787 | "data": {
788 | "text/plain": [
789 | "[postal code]1030008, [place]東京都中央区日本橋中洲\n"
790 | ]
791 | },
792 | "execution_count": 14,
793 | "metadata": {},
794 | "output_type": "execute_result"
795 | }
796 | ],
797 | "source": [
798 | "$result = $fmdb->postalcode->query([[\"f8\" => \"=中央区\", \"f9\" => \"中\"]], null, 1, 3);\n",
799 | "if (!is_null($result)) {\n",
800 | " foreach ($result as $record) {\n",
801 | " echo \"[postal code]{$record->f3}, [place]{$record->f7}{$record->f8}{$record->f9}\";\n",
802 | " }\n",
803 | "}"
804 | ]
805 | },
806 | {
807 | "cell_type": "markdown",
808 | "metadata": {},
809 | "source": [
810 | "Next query means the character 中 contains in the f8 OR f9 field."
811 | ]
812 | },
813 | {
814 | "cell_type": "code",
815 | "execution_count": 15,
816 | "metadata": {},
817 | "outputs": [
818 | {
819 | "data": {
820 | "text/plain": [
821 | "[postal code]1030000, [place]東京都中央区以下に掲載がない場合\n"
822 | ]
823 | },
824 | "execution_count": 15,
825 | "metadata": {},
826 | "output_type": "execute_result"
827 | },
828 | {
829 | "data": {
830 | "text/plain": [
831 | "[postal code]1040044, [place]東京都中央区明石町\n"
832 | ]
833 | },
834 | "execution_count": 15,
835 | "metadata": {},
836 | "output_type": "execute_result"
837 | },
838 | {
839 | "data": {
840 | "text/plain": [
841 | "[postal code]1040042, [place]東京都中央区入船\n"
842 | ]
843 | },
844 | "execution_count": 15,
845 | "metadata": {},
846 | "output_type": "execute_result"
847 | }
848 | ],
849 | "source": [
850 | "$result = $fmdb->postalcode->query([[\"f8\" => \"中\"], [\"f9\" => \"中\"]], null, 1, 3);\n",
851 | "if (!is_null($result)) {\n",
852 | " foreach ($result as $record) {\n",
853 | " echo \"[postal code]{$record->f3}, [place]{$record->f7}{$record->f8}{$record->f9}\";\n",
854 | " }\n",
855 | "}"
856 | ]
857 | },
858 | {
859 | "cell_type": "markdown",
860 | "metadata": {},
861 | "source": [
862 | "Next query means the f3 field is within the range from 170000 to 171000."
863 | ]
864 | },
865 | {
866 | "cell_type": "code",
867 | "execution_count": 16,
868 | "metadata": {},
869 | "outputs": [
870 | {
871 | "data": {
872 | "text/plain": [
873 | "[postal code]1700000, [place]東京都豊島区以下に掲載がない場合\n"
874 | ]
875 | },
876 | "execution_count": 16,
877 | "metadata": {},
878 | "output_type": "execute_result"
879 | },
880 | {
881 | "data": {
882 | "text/plain": [
883 | "[postal code]1700014, [place]東京都豊島区池袋(1丁目)\n"
884 | ]
885 | },
886 | "execution_count": 16,
887 | "metadata": {},
888 | "output_type": "execute_result"
889 | },
890 | {
891 | "data": {
892 | "text/plain": [
893 | "[postal code]1700011, [place]東京都豊島区池袋本町\n"
894 | ]
895 | },
896 | "execution_count": 16,
897 | "metadata": {},
898 | "output_type": "execute_result"
899 | }
900 | ],
901 | "source": [
902 | "$result = $fmdb->postalcode->query([[\"f3\" => \"1700000...1710000\"]], null, 1, 3);\n",
903 | "if (!is_null($result)) {\n",
904 | " foreach ($result as $record) {\n",
905 | " echo \"[postal code]{$record->f3}, [place]{$record->f7}{$record->f8}{$record->f9}\";\n",
906 | " }\n",
907 | "}"
908 | ]
909 | },
910 | {
911 | "cell_type": "markdown",
912 | "metadata": {},
913 | "source": [
914 | "### Restricting to Specified Portals\n",
915 | "The portal specification (the 5th parameter of query or the second parameter of getRecord) has to be an array with the object name of the portal not the table occurrence name. The person_layout has two portals named \"Contact\" and \"History\", but if the 5th parameter is specified, portal data in the result is just in the parameter."
916 | ]
917 | },
918 | {
919 | "cell_type": "code",
920 | "execution_count": 17,
921 | "metadata": {},
922 | "outputs": [
923 | {
924 | "data": {
925 | "text/plain": [
926 | "[id]1, [name]Masayuki Nii, [mail]Dog\n"
927 | ]
928 | },
929 | "execution_count": 17,
930 | "metadata": {},
931 | "output_type": "execute_result"
932 | },
933 | {
934 | "data": {
935 | "text/plain": [
936 | "__[PORTAL(history_to)][startdate]04/01/2001, [enddate]03/31/2003\n"
937 | ]
938 | },
939 | "execution_count": 17,
940 | "metadata": {},
941 | "output_type": "execute_result"
942 | },
943 | {
944 | "data": {
945 | "text/plain": [
946 | "__[PORTAL(history_to)][startdate]04/01/2003, [enddate]03/31/2007\n"
947 | ]
948 | },
949 | "execution_count": 17,
950 | "metadata": {},
951 | "output_type": "execute_result"
952 | }
953 | ],
954 | "source": [
955 | "$result = $fmdb->person_layout->query([[\"id\" => \"1\"]], null, 1, -1, [\"History\"]);\n",
956 | "if (!is_null($result)) {\n",
957 | " foreach ($result as $record) {\n",
958 | " echo \"[id]{$record->id}, [name]{$record->name}, [mail]{$record->mail}\";\n",
959 | " $histories = $record->History;\n",
960 | " foreach ($histories as $item) {\n",
961 | " $startdate = $item->field(\"startdate\", \"history_to\");\n",
962 | " $enddate = $item->field(\"enddate\", \"history_to\");\n",
963 | " echo \"__[PORTAL(history_to)][startdate]{$startdate}, [enddate]{$enddate}\";\n",
964 | " }\n",
965 | " }\n",
966 | "}"
967 | ]
968 | },
969 | {
970 | "cell_type": "markdown",
971 | "metadata": {},
972 | "source": [
973 | "## Writing Operations\n",
974 | "### Create Record\n",
975 | "The 'create()' method creates a record with values in parameter.\n",
976 | "The associated array of the parameter has to be a series of field name key and its value."
977 | ]
978 | },
979 | {
980 | "cell_type": "code",
981 | "execution_count": 18,
982 | "metadata": {},
983 | "outputs": [
984 | {
985 | "data": {
986 | "text/plain": [
987 | "\"\u001b[32m4123\u001b[39m\""
988 | ]
989 | },
990 | "execution_count": 18,
991 | "metadata": {},
992 | "output_type": "execute_result"
993 | }
994 | ],
995 | "source": [
996 | "$recId = $fmdb->postalcode->create([\"f3\" => \"field 3 data\", \"f7\" => \"field 7 data\"]);"
997 | ]
998 | },
999 | {
1000 | "cell_type": "markdown",
1001 | "metadata": {},
1002 | "source": [
1003 | "The 'getRecord()' method query the record with the recordId of the parameter.\n",
1004 | "It returns the FileMakerRelation object and you can handle it with the return value from 'query()' method."
1005 | ]
1006 | },
1007 | {
1008 | "cell_type": "code",
1009 | "execution_count": 19,
1010 | "metadata": {},
1011 | "outputs": [
1012 | {
1013 | "data": {
1014 | "text/plain": [
1015 | "[f3]field 3 data, [f7]field 7 data, [f8]\n"
1016 | ]
1017 | },
1018 | "execution_count": 19,
1019 | "metadata": {},
1020 | "output_type": "execute_result"
1021 | }
1022 | ],
1023 | "source": [
1024 | "$result = $fmdb->postalcode->getRecord($recId);\n",
1025 | "if (!is_null($result)) {\n",
1026 | " foreach ($result as $record) {\n",
1027 | " echo \"[f3]{$record->f3}, [f7]{$record->f7}, [f8]{$record->f8}\";\n",
1028 | " }\n",
1029 | "}"
1030 | ]
1031 | },
1032 | {
1033 | "cell_type": "markdown",
1034 | "metadata": {},
1035 | "source": [
1036 | "### Update Record\n",
1037 | "The 'update()' method modifies fields in a record. You have to set parameters as the recordId of target record and associated array to specify the modified data."
1038 | ]
1039 | },
1040 | {
1041 | "cell_type": "code",
1042 | "execution_count": 20,
1043 | "metadata": {},
1044 | "outputs": [
1045 | {
1046 | "data": {
1047 | "text/plain": [
1048 | "[f3]field 3 modifed, [f7]field 7 data, [f8]field 8 update\n"
1049 | ]
1050 | },
1051 | "execution_count": 20,
1052 | "metadata": {},
1053 | "output_type": "execute_result"
1054 | }
1055 | ],
1056 | "source": [
1057 | "$fmdb->postalcode->update($recId, [\"f3\" => \"field 3 modifed\", \"f8\" => \"field 8 update\"]);\n",
1058 | "$result = $fmdb->postalcode->getRecord($recId);\n",
1059 | "if (!is_null($result)) {\n",
1060 | " foreach ($result as $record) {\n",
1061 | " echo \"[f3]{$record->f3}, [f7]{$record->f7}, [f8]{$record->f8}\";\n",
1062 | " }\n",
1063 | "}"
1064 | ]
1065 | },
1066 | {
1067 | "cell_type": "markdown",
1068 | "metadata": {},
1069 | "source": [
1070 | "## Delete Record\n",
1071 | "The 'delete()' method deletes the record specified by the parameter."
1072 | ]
1073 | },
1074 | {
1075 | "cell_type": "code",
1076 | "execution_count": 21,
1077 | "metadata": {},
1078 | "outputs": [
1079 | {
1080 | "data": {
1081 | "text/plain": [
1082 | "\u001b[36mnull\u001b[39m"
1083 | ]
1084 | },
1085 | "execution_count": 21,
1086 | "metadata": {},
1087 | "output_type": "execute_result"
1088 | }
1089 | ],
1090 | "source": [
1091 | "$fmdb->postalcode->delete($recId);"
1092 | ]
1093 | },
1094 | {
1095 | "cell_type": "markdown",
1096 | "metadata": {},
1097 | "source": [
1098 | "## Call Script\n",
1099 | "Some methods ex. query can execute a script with 6th paramter."
1100 | ]
1101 | },
1102 | {
1103 | "cell_type": "markdown",
1104 | "metadata": {},
1105 | "source": [
1106 | "This example execute the \"TextScript\" script with the parameter \"ok\". The script finishes no error and returns value and you can detect it from the getScriptResult method."
1107 | ]
1108 | },
1109 | {
1110 | "cell_type": "code",
1111 | "execution_count": 22,
1112 | "metadata": {},
1113 | "outputs": [
1114 | {
1115 | "data": {
1116 | "text/plain": [
1117 | "Script Error: 0\n"
1118 | ]
1119 | },
1120 | "execution_count": 22,
1121 | "metadata": {},
1122 | "output_type": "execute_result"
1123 | },
1124 | {
1125 | "data": {
1126 | "text/plain": [
1127 | "Script Result: It's over.\n"
1128 | ]
1129 | },
1130 | "execution_count": 22,
1131 | "metadata": {},
1132 | "output_type": "execute_result"
1133 | }
1134 | ],
1135 | "source": [
1136 | "$scriptSpec = [\"script\" => \"TestScript\", \"script.param\" => \"ok\"];\n",
1137 | "$result = $fmdb->person_layout->query(null, null, -1, 1, null, $scriptSpec);\n",
1138 | "if (!is_null($result)) {\n",
1139 | " echo \"Script Error: {$fmdb->person_layout->getScriptError()}\";\n",
1140 | " echo \"Script Result: {$fmdb->person_layout->getScriptResult()}\";\n",
1141 | "}"
1142 | ]
1143 | },
1144 | {
1145 | "cell_type": "markdown",
1146 | "metadata": {},
1147 | "source": [
1148 | "The \"script\" key's script executes just after querying. Otherwise the \"script.prerequest\" key's one does before querying. Errors and/or Result can be detect with methods described below."
1149 | ]
1150 | },
1151 | {
1152 | "cell_type": "code",
1153 | "execution_count": 23,
1154 | "metadata": {},
1155 | "outputs": [
1156 | {
1157 | "data": {
1158 | "text/plain": [
1159 | "Script Error: 0\n"
1160 | ]
1161 | },
1162 | "execution_count": 23,
1163 | "metadata": {},
1164 | "output_type": "execute_result"
1165 | },
1166 | {
1167 | "data": {
1168 | "text/plain": [
1169 | "Script Result: It's over.\n"
1170 | ]
1171 | },
1172 | "execution_count": 23,
1173 | "metadata": {},
1174 | "output_type": "execute_result"
1175 | }
1176 | ],
1177 | "source": [
1178 | "$scriptSpec = [\"script.prerequest\" => \"TestScript\", \"script.prerequest.param\" => \"ok\"];\n",
1179 | "$result = $fmdb->person_layout->query(null, null, -1, 1, null, $scriptSpec);\n",
1180 | "if (!is_null($result)) {\n",
1181 | " echo \"Script Error: {$fmdb->person_layout->getScriptErrorPrerequest()}\";\n",
1182 | " echo \"Script Result: {$fmdb->person_layout->getScriptResultPrerequest()}\";\n",
1183 | "\n",
1184 | "}"
1185 | ]
1186 | },
1187 | {
1188 | "cell_type": "markdown",
1189 | "metadata": {},
1190 | "source": [
1191 | "If any errors happens in the script, the error is not 0 as shown below."
1192 | ]
1193 | },
1194 | {
1195 | "cell_type": "code",
1196 | "execution_count": 24,
1197 | "metadata": {},
1198 | "outputs": [
1199 | {
1200 | "data": {
1201 | "text/plain": [
1202 | "Script Error: 102\n"
1203 | ]
1204 | },
1205 | "execution_count": 24,
1206 | "metadata": {},
1207 | "output_type": "execute_result"
1208 | },
1209 | {
1210 | "data": {
1211 | "text/plain": [
1212 | "Script Result: It's error.\n"
1213 | ]
1214 | },
1215 | "execution_count": 24,
1216 | "metadata": {},
1217 | "output_type": "execute_result"
1218 | }
1219 | ],
1220 | "source": [
1221 | "$scriptSpec = [\"script\" => \"TestScript\", \"script.param\" => \"not\"];\n",
1222 | "$result = $fmdb->person_layout->query(null, null, -1, 1, null, $scriptSpec);\n",
1223 | "if (!is_null($result)) {\n",
1224 | " echo \"Script Error: {$fmdb->person_layout->getScriptError()}\";\n",
1225 | " echo \"Script Result: {$fmdb->person_layout->getScriptResult()}\";\n",
1226 | "}"
1227 | ]
1228 | },
1229 | {
1230 | "cell_type": "markdown",
1231 | "metadata": {},
1232 | "source": [
1233 | "If you don't specify any script information, error and result are both \"blank\" data."
1234 | ]
1235 | },
1236 | {
1237 | "cell_type": "code",
1238 | "execution_count": 25,
1239 | "metadata": {},
1240 | "outputs": [
1241 | {
1242 | "data": {
1243 | "text/plain": [
1244 | "Script Error: \n"
1245 | ]
1246 | },
1247 | "execution_count": 25,
1248 | "metadata": {},
1249 | "output_type": "execute_result"
1250 | },
1251 | {
1252 | "data": {
1253 | "text/plain": [
1254 | "Script Result: \n"
1255 | ]
1256 | },
1257 | "execution_count": 25,
1258 | "metadata": {},
1259 | "output_type": "execute_result"
1260 | }
1261 | ],
1262 | "source": [
1263 | "$result = $fmdb->person_layout->query(null, null, -1, 1);\n",
1264 | "if (!is_null($result)) {\n",
1265 | " echo \"Script Error: {$fmdb->person_layout->getScriptError()}\";\n",
1266 | " echo \"Script Result: {$fmdb->person_layout->getScriptResult()}\";\n",
1267 | "}"
1268 | ]
1269 | },
1270 | {
1271 | "cell_type": "markdown",
1272 | "metadata": {},
1273 | "source": [
1274 | "## File Uploading\n",
1275 | "A new record is created in \"testtable\" table on the first statement of below.\n",
1276 | "Then the \"testtable\" table has a container filed \"vc1\". One image file is going to be uploaded to it.\n",
1277 | "The file path, record id and field name are required."
1278 | ]
1279 | },
1280 | {
1281 | "cell_type": "code",
1282 | "execution_count": 26,
1283 | "metadata": {},
1284 | "outputs": [
1285 | {
1286 | "data": {
1287 | "text/plain": [
1288 | "\u001b[36mnull\u001b[39m"
1289 | ]
1290 | },
1291 | "execution_count": 26,
1292 | "metadata": {},
1293 | "output_type": "execute_result"
1294 | }
1295 | ],
1296 | "source": [
1297 | "$recId = $fmdb->testtable->create();\n",
1298 | "$fmdb->testtable->uploadFile(\"samples/cat.jpg\", $recId, \"vc1\");"
1299 | ]
1300 | },
1301 | {
1302 | "cell_type": "markdown",
1303 | "metadata": {},
1304 | "source": [
1305 | "What kind of data does the container field which inserted an image return? For example, the returned value is going to show as the value of the vc1 field as below. It'a kind of url, and it can get the content of the container field, and it means you can download with the getContainerData method. "
1306 | ]
1307 | },
1308 | {
1309 | "cell_type": "code",
1310 | "execution_count": 27,
1311 | "metadata": {},
1312 | "outputs": [
1313 | {
1314 | "data": {
1315 | "text/plain": [
1316 | "vc1: https://localhost/Streaming_SSL/MainDB/F18E548D6339DB444ED92BF13DE220A5F773E7E68607E29E388FBD6ECE1B5AEF.jpg?RCType=EmbeddedRCFileProcessor\n"
1317 | ]
1318 | },
1319 | "execution_count": 27,
1320 | "metadata": {},
1321 | "output_type": "execute_result"
1322 | },
1323 | {
1324 | "data": {
1325 | "text/plain": [
1326 | "\u001b[31;1mException with message 'Error in creating cookie file. Failed to connect to localhost port 443: Connection refused'\u001b[39;22m"
1327 | ]
1328 | },
1329 | "execution_count": 27,
1330 | "metadata": {},
1331 | "output_type": "execute_result"
1332 | }
1333 | ],
1334 | "source": [
1335 | "$result = $fmdb->testtable->getRecord($recId);\n",
1336 | "if(!is_null($result)) {\n",
1337 | " foreach ($result as $record) {\n",
1338 | " echo \"vc1: {$record->vc1}\";\n",
1339 | " echo \"\";\n",
1340 | " }\n",
1341 | "}"
1342 | ]
1343 | },
1344 | {
1345 | "cell_type": "markdown",
1346 | "metadata": {},
1347 | "source": [
1348 | "## Batch Operations\n",
1349 | "If you call the 'startCommunication()' method, you can describe a series of database operation\n",
1350 | "calls. This means the authentication is going to be done at the 'startCommunication()' method, and the token is going to be shared with following statements. The 'endCommunication()' calls logout REST API call and invalidate the shared token."
1351 | ]
1352 | },
1353 | {
1354 | "cell_type": "code",
1355 | "execution_count": 28,
1356 | "metadata": {},
1357 | "outputs": [
1358 | {
1359 | "data": {
1360 | "text/plain": [
1361 | "array (\n",
1362 | " 0 => '4124',\n",
1363 | " 1 => '4125',\n",
1364 | " 2 => '4126',\n",
1365 | " 3 => '4127',\n",
1366 | " 4 => '4128',\n",
1367 | " 5 => '4129',\n",
1368 | " 6 => '4130',\n",
1369 | ")\n"
1370 | ]
1371 | },
1372 | "execution_count": 28,
1373 | "metadata": {},
1374 | "output_type": "execute_result"
1375 | }
1376 | ],
1377 | "source": [
1378 | "$recIds = [];\n",
1379 | "$fmdb->postalcode->startCommunication();\n",
1380 | "$recIds[] = $fmdb->postalcode->create([\"f3\" => \"field 3 data 1\", \"f7\" => \"field 7 data\"]);\n",
1381 | "$recIds[] = $fmdb->postalcode->create([\"f3\" => \"field 3 data 2\", \"f7\" => \"field 7 data\"]);\n",
1382 | "$recIds[] = $fmdb->postalcode->create([\"f3\" => \"field 3 data 3\", \"f7\" => \"field 7 data\"]);\n",
1383 | "$recIds[] = $fmdb->postalcode->create([\"f3\" => \"field 3 data 4\", \"f7\" => \"field 7 data\"]);\n",
1384 | "$recIds[] = $fmdb->postalcode->create([\"f3\" => \"field 3 data 5\", \"f7\" => \"field 7 data\"]);\n",
1385 | "$recIds[] = $fmdb->postalcode->create([\"f3\" => \"field 3 data 6\", \"f7\" => \"field 7 data\"]);\n",
1386 | "$recIds[] = $fmdb->postalcode->create([\"f3\" => \"field 3 data 7\", \"f7\" => \"field 7 data\"]);\n",
1387 | "$fmdb->postalcode->endCommunication();\n",
1388 | "echo var_export($recIds, true);"
1389 | ]
1390 | },
1391 | {
1392 | "cell_type": "markdown",
1393 | "metadata": {},
1394 | "source": [
1395 | "## Parameter for Operations"
1396 | ]
1397 | },
1398 | {
1399 | "cell_type": "markdown",
1400 | "metadata": {},
1401 | "source": [
1402 | "You can turn off to throw an exception in case of error. You have to handle errors with checking result error."
1403 | ]
1404 | },
1405 | {
1406 | "cell_type": "code",
1407 | "execution_count": 29,
1408 | "metadata": {},
1409 | "outputs": [
1410 | {
1411 | "data": {
1412 | "text/plain": [
1413 | "\u001b[36mnull\u001b[39m"
1414 | ]
1415 | },
1416 | "execution_count": 29,
1417 | "metadata": {},
1418 | "output_type": "execute_result"
1419 | }
1420 | ],
1421 | "source": [
1422 | "$fmdb->setThrowException(false);"
1423 | ]
1424 | },
1425 | {
1426 | "cell_type": "markdown",
1427 | "metadata": {},
1428 | "source": [
1429 | "If you set to throwing exceptions, you can describe the try-catch statement in your code for error handling."
1430 | ]
1431 | },
1432 | {
1433 | "cell_type": "code",
1434 | "execution_count": 30,
1435 | "metadata": {},
1436 | "outputs": [
1437 | {
1438 | "data": {
1439 | "text/plain": [
1440 | "[Exception]Field not_exist_field doesn't exist.\n"
1441 | ]
1442 | },
1443 | "execution_count": 30,
1444 | "metadata": {},
1445 | "output_type": "execute_result"
1446 | }
1447 | ],
1448 | "source": [
1449 | "$fmdb->setThrowException(true);\n",
1450 | "$result = $fmdb->testtable->getRecord($recId);\n",
1451 | "try {\n",
1452 | " foreach ($result as $record) {\n",
1453 | " echo $record->not_exist_field;\n",
1454 | " }\n",
1455 | "} catch(Exception $ex) {\n",
1456 | " echo \"[Exception]{$ex->getMessage()}\";\n",
1457 | "}"
1458 | ]
1459 | },
1460 | {
1461 | "cell_type": "markdown",
1462 | "metadata": {},
1463 | "source": [
1464 | "If you call with true, the debug mode is activated. Debug mode echos the contents of communication such as request and response."
1465 | ]
1466 | },
1467 | {
1468 | "cell_type": "code",
1469 | "execution_count": 31,
1470 | "metadata": {},
1471 | "outputs": [
1472 | {
1473 | "data": {
1474 | "text/plain": [
1475 | "\u001b[36mnull\u001b[39m"
1476 | ]
1477 | },
1478 | "execution_count": 31,
1479 | "metadata": {},
1480 | "output_type": "execute_result"
1481 | }
1482 | ],
1483 | "source": [
1484 | "$fmdb->setDebug(false);"
1485 | ]
1486 | },
1487 | {
1488 | "cell_type": "markdown",
1489 | "metadata": {},
1490 | "source": [
1491 | "If you call with true, the certificate from the server is going to verify. In case of self-signed one (usually default situation), you don't have to call this method."
1492 | ]
1493 | },
1494 | {
1495 | "cell_type": "code",
1496 | "execution_count": 32,
1497 | "metadata": {},
1498 | "outputs": [],
1499 | "source": [
1500 | "//$fmdb->setCertValidating(true);"
1501 | ]
1502 | }
1503 | ],
1504 | "metadata": {
1505 | "kernelspec": {
1506 | "display_name": "PHP",
1507 | "language": "php",
1508 | "name": "jupyter-php"
1509 | },
1510 | "language_info": {
1511 | "file_extension": ".php",
1512 | "mimetype": "text/x-php",
1513 | "name": "PHP",
1514 | "pygments_lexer": "PHP",
1515 | "version": "7.2.12"
1516 | }
1517 | },
1518 | "nbformat": 4,
1519 | "nbformat_minor": 2
1520 | }
1521 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "inter-mediator/fmdataapi",
3 | "version": "33",
4 | "time": "2024-09-16",
5 | "repositories": [
6 | {
7 | "type": "git",
8 | "url": "https://github.com/msyk/FMDataAPI.git"
9 | }
10 | ],
11 | "prefer-stable": true,
12 | "require": {
13 | "php": ">=8.1",
14 | "ext-curl": "*",
15 | "ext-json": "*"
16 | },
17 | "require-dev": {
18 | "phpunit/phpunit": "*",
19 | "phpstan/phpstan": "^2.0"
20 | },
21 | "autoload": {
22 | "psr-4": {
23 | "INTERMediator\\FileMakerServer\\RESTAPI\\": "src/"
24 | },
25 | "classmap": [
26 | "test/FMDataAPIUnitTest.php",
27 | "test/TestProvider.php"
28 | ]
29 | },
30 | "description": "FMDataAPI is the class library in PHP for accessing FileMaker database with FileMaker Data API.",
31 | "type": "library",
32 | "keywords": [
33 | "INTER-Mediator",
34 | "FileMaker",
35 | "REST",
36 | "API"
37 | ],
38 | "homepage": "https://github.com/msyk/FMDataAPI",
39 | "license": "MIT",
40 | "authors": [
41 | {
42 | "name": "Masayuki Nii (Auther)",
43 | "homepage": "http://msyk.net/"
44 | },
45 | {
46 | "name": "Atsushi Matsuo (Contributor)"
47 | },
48 | {
49 | "name": "darnel (Contributor)"
50 | },
51 | {
52 | "name": "Craig Smith (Contributor)"
53 | },
54 | {
55 | "name": "Bernhard Schulz (Contributor)"
56 | },
57 | {
58 | "name": "montaniasystemab (Contributor)"
59 | },
60 | {
61 | "name": "Rickard Andersson (Contributor)"
62 | },
63 | {
64 | "name": "Julien @AnnoyingTechnology (Contributor)"
65 | },
66 | {
67 | "name": "Tom Kuijer (Contributor)"
68 | },
69 | {
70 | "name": "Thijs Meijer (Contributor)"
71 | },
72 | {
73 | "name": "Patrick Janser (Contributor)"
74 | },
75 | {
76 | "name": "Roger Engström (Contributor)"
77 | },
78 | {
79 | "name": "Stathis Askaridis (Contributor)"
80 | }
81 | ],
82 | "support": {
83 | "community-jp": "https://www.facebook.com/groups/233378356708157/",
84 | "community-en": "https://www.facebook.com/groups/254446237922985/",
85 | "source": "https://github.com/msyk/FMDataAPI.git",
86 | "manual": "http://inter-mediator.info/FMDataAPI/namespaces/INTERMediator.FileMakerServer.RESTAPI.html"
87 | },
88 | "scripts": {
89 | "test": [
90 | "./vendor/bin/phpunit --bootstrap ./vendor/autoload.php --configuration ./test/phpunit.xml ./test/FMDataAPIUnitTest.php"
91 | ],
92 | "doc": [
93 | "./vendor/bin/phpdoc -f ./src/FMDataAPI.php -t ../INTER-Mediator_Documents/FMDataAPI"
94 | ]
95 | },
96 | "config": {
97 | "allow-plugins": {
98 | "symfony/flex": true
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | web:
3 | build: .
4 | platform: linux/amd64
5 |
--------------------------------------------------------------------------------
/samples/FMDataAPI_Sample.php:
--------------------------------------------------------------------------------
1 | setThrowException(false);
38 |
39 | // If you call with true, the debug mode is activated. Debug mode echos the contents of communication
40 | // such as request and response.
41 | $fmdb->setDebug(true);
42 |
43 | // If you call with true, the certificate from the server is going to verify.
44 | // In the case of self-signed one (usually default situation), you don't have to call this method.
45 | //$fmdb->setCertValidating(true);
46 |
47 | // Metadata API is the new feature of FMS18.
48 | $pInfo = var_export($fmdb->getProductInfo(), true);
49 | echo htmlspecialchars("Product Info: {$pInfo}", ENT_QUOTES, "UTF-8") . "
";
50 | $pInfo = var_export($fmdb->getDatabaseNames(), true);
51 | echo htmlspecialchars("Database Names: {$pInfo}", ENT_QUOTES, "UTF-8") . "";
52 | $pInfo = var_export($fmdb->getLayoutNames(), true);
53 | echo htmlspecialchars("Layout Names: {$pInfo}", ENT_QUOTES, "UTF-8") . "";
54 | $pInfo = var_export($fmdb->getScriptNames(), true);
55 | echo htmlspecialchars("Script Names: {$pInfo}", ENT_QUOTES, "UTF-8") . "";
56 | $result = $fmdb->person_layout->getMetadata();
57 | $pInfo = var_export($result, true);
58 | echo htmlspecialchars("Layout Metadata: {$pInfo}", ENT_QUOTES, "UTF-8") . "";
59 | $result = $fmdb->person_layout->getMetadataOld();
60 | $pInfo = var_export($result, true);
61 | echo htmlspecialchars("Layout Metadata (Old): {$pInfo}", ENT_QUOTES, "UTF-8") . "";
62 |
63 | // The FMDataAPI has the property as the same name of layout. This sample database has the 'person_layout' layout,
64 | // so '$fmdb->person_layout' refers FMLayout object fo the proxy of the layout. FMLayout class has the 'query' method
65 | // and returns FileMakerRelation class's object. The condition spread in parameter is the same as FileMaker's Find Record API.
66 | $result = $fmdb->person_layout->query(/*array(array("id" => ">1"))*/);
67 |
68 | // The 'httpStatus()' method returns the HTTP status code in the latest response.
69 | echo htmlspecialchars("HTTP Status: {$fmdb->httpStatus()}", ENT_QUOTES, "UTF-8") . "";
70 |
71 | // The following two methods return the error code and message of the latest API call which is submitted in query() method.
72 | // You can check API calling succeed or fail if error code is or isn't 0 every after API calling methods.
73 | echo htmlspecialchars("Error Code: {$fmdb->errorCode()}", ENT_QUOTES, "UTF-8") . "";
74 | echo htmlspecialchars("Error Message: {$fmdb->errorMessage()}", ENT_QUOTES, "UTF-8") . "";
75 |
76 | // If the query is succeeded, the following information can be detected.
77 | echo htmlspecialchars("Target Table: {$fmdb->getTargetTable()}", ENT_QUOTES, "UTF-8") . "";
78 | echo htmlspecialchars("Total Count: {$fmdb->getTotalCount()}", ENT_QUOTES, "UTF-8") . "";
79 | echo htmlspecialchars("Found Count: {$fmdb->getFoundCount()}", ENT_QUOTES, "UTF-8") . "";
80 | echo htmlspecialchars("Returned Count: {$fmdb->getReturnedCount()}", ENT_QUOTES, "UTF-8") . "";
81 |
82 | // The FileMakerRelation class implements the Iterator interface, and it can repeat with 'foreach.'
83 | // The $record also refers to a FileMakerRelation object, but it is for a single record.
84 | // This layout has fields as like 'id', 'name', 'mail' and so on, and the field name can be handled
85 | // as a property name of the record referring to $record.
86 | if (!is_null($result)) {
87 | // If the query is succeeded, the following information can be detected.
88 | echo htmlspecialchars("Target Table: {$result->getTargetTable()}", ENT_QUOTES, "UTF-8") . "";
89 | echo htmlspecialchars("Total Count: {$result->getTotalCount()}", ENT_QUOTES, "UTF-8") . "";
90 | echo htmlspecialchars("Found Count: {$result->getFoundCount()}", ENT_QUOTES, "UTF-8") . "";
91 | echo htmlspecialchars("Returned Count: {$result->getReturnedCount()}", ENT_QUOTES, "UTF-8") . "";
92 | foreach ($result as $record) {
93 | echo htmlspecialchars("id: {$record->id},", ENT_QUOTES, "UTF-8");
94 | echo htmlspecialchars("name: {$record->name},", ENT_QUOTES, "UTF-8");
95 | echo htmlspecialchars("mail: {$record->mail}", ENT_QUOTES, "UTF-8") . "";
96 | // If you named field name as not variable friendly, you can use field('field_name') method or
97 | // set the name to any variable such as $fname = 'field_name'; echo $record->$fname;.
98 |
99 | // In the case of a related field but outside portal, the field method is available as below:
100 | // echo $record->field("summary", "contact_to");
101 |
102 | // A portal name property returns records of portal as FileMakerRelation object.
103 | $contacts = $record->Contact;
104 |
105 | // If the query is succeeded, the following information can be detected.
106 | echo htmlspecialchars("Target Table: {$contacts->getTargetTable()}", ENT_QUOTES, "UTF-8") . "";
107 | echo htmlspecialchars("Total Count: {$contacts->getTotalCount()}", ENT_QUOTES, "UTF-8") . "";
108 | echo htmlspecialchars("Found Count: {$contacts->getFoundCount()}", ENT_QUOTES, "UTF-8") . "";
109 | echo htmlspecialchars("Returned Count: {$contacts->getReturnedCount()}", ENT_QUOTES, "UTF-8") . "";
110 |
111 | // You can repeat with foreach for the portal records.
112 | foreach ($contacts as $item) {
113 | // Technically portal field has to be refered as "contact_to::id" but it can be an indentifier in PHP.
114 | // In this case, you can call field method as like 'field("summary", "contact_to").'
115 | // If the field belongs to the table occurrence for the portal, you can refer the field as like '$item->id.'
116 | // If the field belongs to another table occurrence, you have to call the 'field()' method.
117 | echo htmlspecialchars("[PORTAL(contact_to)] id: {$item->field("id", "contact_to")},", ENT_QUOTES, "UTF-8");
118 | echo htmlspecialchars("summary: {$item->field("summary", "contact_to")}", ENT_QUOTES, "UTF-8") . "";
119 | // If the object name of the portal is blank, it can be referred as the table occurrence name.
120 | // If the object name is specified, you have to access with the object name, and it means you have to
121 | // call 'field()' method to get the value.
122 | }
123 | echo "";
124 | }
125 |
126 |
127 | echo "
";
140 | var_export($portalRecord->toArray());
141 | }
142 | }
143 | }
144 |
145 | // Move to the pointer to the first record.
146 | $result->rewind();
147 |
148 | // The FileMakerRelation object from 'query()' method can be accessed as like the 'cursor' style repeating.
149 | // The 'count()' method returns the number of records in response. The variable $result refers current
150 | // record, and you can get the field value with the propaty having the same field name.
151 | // The portal can be done with same way. The 'next()' method steps forward the pointer of the current record.
152 | for ($i = 0; $i < $result->count(); $i++) {
153 | echo htmlspecialchars("id: {$result->id},", ENT_QUOTES, "UTF-8");
154 | echo htmlspecialchars("name: {$result->name},", ENT_QUOTES, "UTF-8");
155 | echo htmlspecialchars("mail: {$result->mail}", ENT_QUOTES, "UTF-8") . "";
156 | $contacts = $result->Contact;
157 |
158 | for ($j = 0; $j < $contacts->count(); $j++) {
159 | echo htmlspecialchars("[PORTAL(contact_to)] id: {$contacts->field("id", "contact_to")},", ENT_QUOTES, "UTF-8");
160 | echo htmlspecialchars("summary: {$contacts->field("summary", "contact_to")}", ENT_QUOTES, "UTF-8") . "";
161 | $contacts->next();
162 | }
163 | $result->next();
164 | }
165 | }
166 | // The 'create()' method creates a record with values in parameter.
167 | // The associated array of the parameter has to be a series of field name key and its value.
168 | $recId = $fmdb->postalcode->create(array("f3" => "field 3 data", "f7" => "field 7 data"));
169 |
170 | // The 'getRecord()' method query the record with the recordId of the parameter.
171 | // It returns the FileMakerRelation object, and you can handle it with the return value from 'query()' method.
172 | $result = $fmdb->postalcode->getRecord($recId);
173 | if (!is_null($result)) {
174 | foreach ($result as $record) {
175 | echo htmlspecialchars("f3: {$record->f3},", ENT_QUOTES, "UTF-8");
176 | echo htmlspecialchars("f7: {$record->f7},", ENT_QUOTES, "UTF-8");
177 | echo htmlspecialchars("f8: {$record->f8}", ENT_QUOTES, "UTF-8") . "";
178 | echo "";
179 | }
180 | }
181 |
182 | // The 'update()' method modifies fields in a record. You have to set parameters as the recordId of target
183 | // record and associated array to specify the modified data.
184 | $fmdb->postalcode->update($recId, array("f3" => "field 3 modifed", "f8" => "field 8 update"));
185 | $result = $fmdb->postalcode->getRecord($recId);
186 | if (!is_null($result)) {
187 | foreach ($result as $record) {
188 | echo htmlspecialchars("f3: {$record->f3},", ENT_QUOTES, "UTF-8");
189 | echo htmlspecialchars("f7: {$record->f7},", ENT_QUOTES, "UTF-8");
190 | echo htmlspecialchars("f8: {$record->f8}", ENT_QUOTES, "UTF-8") . "";
191 | echo "";
192 | }
193 | }
194 | // The 'delete()' method deletes the record specified by the parameter.
195 | $fmdb->postalcode->delete($recId);
196 |
197 | // Call script
198 | $result = $fmdb->person_layout->query(null, null, -1, 1, null, ["script" => "TestScript", "script.param" => "ok"]);
199 | if (!is_null($result)) {
200 | echo htmlspecialchars("Script Error: {$fmdb->person_layout->getScriptError()}", ENT_QUOTES, "UTF-8") . "";
201 | echo htmlspecialchars("Script Result: {$fmdb->person_layout->getScriptResult()}", ENT_QUOTES, "UTF-8") . "";
202 | }
203 | $result = $fmdb->person_layout->query(null, null, -1, 1, null, ["script.prerequest" => "TestScript", "script.prerequest.param" => "ok"]);
204 | if (!is_null($result)) {
205 | echo htmlspecialchars("Script Error: {$fmdb->person_layout->getScriptErrorPrerequest()}", ENT_QUOTES, "UTF-8") . "";
206 | echo htmlspecialchars("Script Result: {$fmdb->person_layout->getScriptResultPrerequest()}", ENT_QUOTES, "UTF-8") . "";
207 | }
208 | $result = $fmdb->person_layout->query(null, null, -1, 1, null, ["script" => "TestScript", "script.param" => "not"]);
209 | if (!is_null($result)) {
210 | echo htmlspecialchars("Script Error: {$fmdb->person_layout->getScriptError()}", ENT_QUOTES, "UTF-8") . "";
211 | echo htmlspecialchars("Script Result: {$fmdb->person_layout->getScriptResult()}", ENT_QUOTES, "UTF-8") . "";
212 | }
213 | $result = $fmdb->person_layout->query(null, null, -1, 1);
214 | if (!is_null($result)) {
215 | echo htmlspecialchars("Script Error: {$fmdb->person_layout->getScriptError()}", ENT_QUOTES, "UTF-8") . "";
216 | echo htmlspecialchars("Script Result: {$fmdb->person_layout->getScriptResult()}", ENT_QUOTES, "UTF-8") . "";
217 | }
218 |
219 | // A new record is created in "testtable" table.
220 | $recId = $fmdb->testtable->create();
221 | echo "RecId = {$recId}";
222 | // The "testtable" table has a container filed "vc1". One image file is going to be uploaded to it.
223 | // The file path, record id and field name are required.
224 | $fmdb->testtable->uploadFile("cat.jpg", $recId, "vc1");
225 | // What kind of data does the container field which inserted an image return?
226 | // For example, the returned value was like this:
227 | // https://localhost/Streaming_SSL/MainDB/6A4A253F7CE33465DCDFBFF0704B34C0993D54AD85702396920E85249BD0271A.jpg?RCType=EmbeddedRCFileProcessor
228 | // This url can get the content of the container field, and it means you can download with file_put_content() function and so on.
229 | $result = $fmdb->testtable->getRecord($recId);
230 | echo htmlspecialchars("Target Table(getRecord): {$result->getTargetTable()}", ENT_QUOTES, "UTF-8") . "";
231 | echo htmlspecialchars("Total Count(getRecord): {$result->getTotalCount()}", ENT_QUOTES, "UTF-8") . "";
232 | echo htmlspecialchars("Found Count(getRecord): {$result->getFoundCount()}", ENT_QUOTES, "UTF-8") . "";
233 | echo htmlspecialchars("Returned Count(getRecord): {$result->getReturnedCount()}", ENT_QUOTES, "UTF-8") . "";
234 |
235 | if (!is_null($result)) {
236 | foreach ($result as $record) {
237 | echo htmlspecialchars("vc1: {$record->vc1}", ENT_QUOTES, "UTF-8") . "";
238 | echo "
";
239 | }
240 | }
241 |
242 | // If you call the 'startCommunication()' method, you can describe a series of database operation
243 | // calls. This means the authentication is going to be done at the 'startCommunication()' method,
244 | // and the token is going to be shared with the following statements. The 'endCommunication()' calls
245 | // logout REST API call and invalidates the shared token.
246 | $recIds = array();
247 | $fmdb->postalcode->startCommunication();
248 | $recIds[] = $fmdb->postalcode->create(array("f3" => "field 3 data 1", "f7" => "field 7 data"));
249 | $recIds[] = $fmdb->postalcode->create(array("f3" => "field 3 data 2", "f7" => "field 7 data"));
250 | $recIds[] = $fmdb->postalcode->create(array("f3" => "field 3 data 3", "f7" => "field 7 data"));
251 | $recIds[] = $fmdb->postalcode->create(array("f3" => "field 3 data 4", "f7" => "field 7 data"));
252 | $recIds[] = $fmdb->postalcode->create(array("f3" => "field 3 data 5", "f7" => "field 7 data"));
253 | $recIds[] = $fmdb->postalcode->create(array("f3" => "field 3 data 6", "f7" => "field 7 data"));
254 | $recIds[] = $fmdb->postalcode->create(array("f3" => "field 3 data 7", "f7" => "field 7 data"));
255 | $fmdb->postalcode->endCommunication();
256 | var_export($recIds);
257 | echo "";
258 |
259 | // The 'query()' method can have several parameters. The portal specification has to be an array
260 | // with the object name of the portal, not the table occurrence name.
261 | $portal = array("Contact");
262 | $result = $fmdb->person_layout->query(array(array("id" => "1")), null, 1, -1, $portal);
263 | if (!is_null($result)) {
264 | foreach ($result as $record) {
265 | $recordId = $record->getRecordId();
266 | $partialResult = $fmdb->person_layout->getRecord($recordId, $portal);
267 | var_export($partialResult);
268 | echo "";
269 | }
270 | }
271 | // The 'query()' method can have several parameters. The second parameter is for sorting.
272 | $portal = array("Contact");
273 | $result = $fmdb->person_layout->query(array(array("id" => "1...")), array(array("id", "descend")), 1, -1, $portal);
274 | if (!is_null($result)) {
275 | foreach ($result as $record) {
276 | $recordId = $record->getRecordId();
277 | $partialResult = $fmdb->person_layout->getRecord($recordId, $portal);
278 | var_export($partialResult);
279 | echo "";
280 | }
281 | }
282 | // The 'query()' method can have several parameters.
283 | // The forth parameter is limit record number to query, and the third is offset.
284 | $result = $fmdb->person_layout->query(null, null, 2, 2);
285 | if (!is_null($result)) {
286 | foreach ($result as $record) {
287 | $recordId = $record->getRecordId();
288 | $partialResult = $fmdb->person_layout->getRecord($recordId, $portal);
289 | var_export($partialResult);
290 | echo "";
291 | }
292 | }
293 |
294 | // The getFirstRecord method returns FileMakerRelation class object.
295 | $result = $fmdb->person_layout->query();
296 | $first = $result->getFirstRecord();
297 | echo "id field of the first record: {$first->field('id')} ";
298 | $portals = $first->getPortalNames();
299 | echo "getPortalNames of the first record: " . var_export($portals, true) . " ";
300 | $contacts = $first->Contact;
301 | echo "[PORTAL(contact_to)] id: {$contacts->field("id", "contact_to")} ";
302 |
303 | } catch (Exception $e) {
304 | echo '