[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-8478":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":21,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":44,"readmeContent":45,"aiSummary":46,"trendingCount":16,"starSnapshotCount":16,"syncStatus":47,"lastSyncTime":48,"discoverSource":49},8478,"php-crud-api","mevdschee\u002Fphp-crud-api","mevdschee","Single file PHP script that adds a REST API to a SQL database","",null,"PHP",3738,1033,166,93,0,1,5,31.04,"MIT License",false,"main",true,[25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"api-server","automatic-api","crud","database","geojson","geospatial","multi-database","mysql","openapi","php","php-api","postgis","postgresql","rest-api","restful","sql-database","sqlite","sqlserver","swagger","2026-06-12 02:01:54","# PHP-CRUD-API\n\nSingle file PHP script that adds a REST API to a MySQL\u002FMariaDB, PostgreSQL, SQL Server or SQLite database. \n\nHowto: Upload \"`api.php`\" to your webserver, configure it to connect to your database, have an instant full-featured REST API.\n\nNB: This is the [TreeQL](https:\u002F\u002Ftreeql.org) reference implementation in PHP.\n\n## Requirements\n\n  - PHP 7.2 or higher with PDO drivers enabled for one of these database systems:\n    - MySQL 5.7 \u002F MariaDB 10.0 or higher for spatial features in MySQL\n    - PostgreSQL 9.5 or higher with PostGIS 2.2 or higher for spatial features\n    - SQL Server 2017 or higher (2019 also has Linux support)\n    - SQLite 3.16 or higher (spatial features NOT supported)\n\n## Installation\n\nDownload the \"`api.php`\" file from the latest release:\n\nhttps:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fphp-crud-api\u002Freleases\u002Flatest or direct from:  \nhttps:\u002F\u002Fraw.githubusercontent.com\u002Fmevdschee\u002Fphp-crud-api\u002Fmain\u002Fapi.php\n\nThis is a single file application! Upload \"`api.php`\" somewhere and enjoy!\n\nFor local development you may run PHP's built-in web server:\n\n    php -S localhost:8080\n\nTest the script by opening the following URL:\n\n    http:\u002F\u002Flocalhost:8080\u002Fapi.php\u002Frecords\u002Fposts\u002F1\n\nDon't forget to modify the configuration at the bottom of the file.\n\nAlternatively you can integrate this project into the web framework of your choice, see:\n\n- [Automatic REST API for Laravel](https:\u002F\u002Ftqdev.com\u002F2019-automatic-rest-api-laravel)\n- [Automatic REST API for Symfony 4](https:\u002F\u002Ftqdev.com\u002F2019-automatic-rest-api-symfony)\n- [Automatic REST API for SlimPHP 4](https:\u002F\u002Ftqdev.com\u002F2019-automatic-api-slimphp-4)\n\nIn these integrations [Composer](https:\u002F\u002Fgetcomposer.org\u002F) is used to load this project as a dependency.\n\nFor people that don't use composer, the file \"`api.include.php`\" is provided. This file contains everything \nfrom \"`api.php`\" except the configuration from \"`src\u002Findex.php`\" and can be used by PHP's \"include\" function.\n\n## Configuration\n\nEdit the following lines in the bottom of the file \"`api.php`\":\n\n    $config = new Config([\n        'username' => 'xxx',\n        'password' => 'xxx',\n        'database' => 'xxx',\n    ]);\n\nThese are all the configuration options and their default value between brackets:\n\n- \"driver\": `mysql`, `pgsql`, `sqlsrv` or `sqlite` (`mysql`)\n- \"address\": Hostname (or filename) of the database server (`localhost`)\n- \"port\": TCP port of the database server (defaults to driver default)\n- \"username\": Username of the user connecting to the database (no default)\n- \"password\": Password of the user connecting to the database (no default)\n- \"database\": Database the connecting is made to (no default)\n- \"command\": Extra SQL to initialize the database connection (none)\n- \"tables\": Comma separated list of tables to publish (defaults to 'all')\n- \"mapping\": Comma separated list of table\u002Fcolumn mappings (no mappping)\n- \"geometrySrid\": SRID assumed when converting from WKT to geometry (`4326`)\n- \"middlewares\": List of middlewares to load (`cors`)\n- \"controllers\": List of controllers to load (`records,geojson,openapi,status`)\n- \"customControllers\": List of user custom controllers to load (no default)\n- \"openApiBase\": OpenAPI info (`{\"info\":{\"title\":\"PHP-CRUD-API\",\"version\":\"1.0.0\"}}`)\n- \"cacheType\": `TempFile`, `Redis`, `Memcache`, `Memcached` or `NoCache` (`TempFile`)\n- \"cachePath\": Path\u002Faddress of the cache (defaults to system's temp directory)\n- \"cacheTime\": Number of seconds the cache is valid (`10`)\n- \"jsonOptions\": Options used for encoding JSON (`JSON_UNESCAPED_UNICODE`)\n- \"debug\": Show errors in the \"X-Exception\" headers (`false`)\n- \"basePath\": URI base path of the API (determined using PATH_INFO by default)\n\nAll configuration options are also available as environment variables. Write the config option with capitals, a \"PHP_CRUD_API_\" prefix and underscores for word breakes, so for instance:\n\n- PHP_CRUD_API_DRIVER=mysql\n- PHP_CRUD_API_ADDRESS=localhost\n- PHP_CRUD_API_PORT=3306\n- PHP_CRUD_API_DATABASE=php-crud-api\n- PHP_CRUD_API_USERNAME=php-crud-api\n- PHP_CRUD_API_PASSWORD=php-crud-api\n- PHP_CRUD_API_DEBUG=1\n\nThe environment variables take precedence over the PHP configuration.\n\n## Limitations\n\nThese limitation and constrains apply:\n\n  - Primary keys should either be auto-increment (from 1 to 2^53) or UUID\n  - Composite primary and composite foreign keys are not supported\n  - Complex writes (transactions) are not supported\n  - Complex queries calling functions (like \"concat\" or \"sum\") are not supported\n  - Database must support and define foreign key constraints\n  - SQLite cannot have bigint typed auto incrementing primary keys\n  - SQLite does not support altering table columns (structure)\n    \n## Features\n\nThe following features are supported:\n\n  - Composer install or single PHP file, easy to deploy.\n  - Very little code, easy to adapt and maintain\n  - Supports POST variables as input (x-www-form-urlencoded)\n  - Supports a JSON object as input\n  - Supports a JSON array as input (batch insert)\n  - Sanitize and validate input using type rules and callbacks\n  - Permission system for databases, tables, columns and records\n  - Multi-tenant single and multi database layouts are supported\n  - Multi-domain CORS support for cross-domain requests\n  - Support for reading joined results from multiple tables\n  - Search support on multiple criteria\n  - Pagination, sorting, top N list and column selection\n  - Relation detection with nested results (belongsTo, hasMany and HABTM)\n  - Atomic increment support via PATCH (for counters)\n  - Binary fields supported with base64 encoding\n  - Spatial\u002FGIS fields and filters supported with WKT and GeoJSON\n  - Mapping table and column names to support legacy systems\n  - Generate API documentation using OpenAPI tools\n  - Authentication via API key, JWT token or username\u002Fpassword\n  - Database connection parameters may depend on authentication\n  - Support for reading database structure in JSON\n  - Support for modifying database structure using REST endpoint\n  - Security enhancing middleware is included\n  - Standard compliant: PSR-4, PSR-7, PSR-12, PSR-15 and PSR-17\n\n## Related projects and ports\n\nRelated projects:\n\n  - [PHP-CRUD-API Quick Start](https:\u002F\u002Fgithub.com\u002Fnik2208\u002Fphp-crud-api-quick-start): A customizable, ready to go, docker compose file featuring PHP-CRUD-API.\n  - [PHP-CRUD-API filter generator](https:\u002F\u002Fthipages.github.io\u002Fjca-filter\u002F#): A JavaScript library creating PHP-CRUD-API filters from expressions.\n  - [JS-CRUD-API](https:\u002F\u002Fgithub.com\u002Fthipages\u002Fjs-crud-api): A JavaScript client library for the API of PHP-CRUD-API\n  - [PHP-API-AUTH](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fphp-api-auth): Single file PHP script that is an authentication provider for PHP-CRUD-API\n  - [PHP-CRUD-UI](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fphp-crud-ui): Single file PHP script that adds a UI to a PHP-CRUD-API project.\n  - [PHP-CRUD-ADMIN](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fphp-crud-admin): Single file PHP script that adds a database admin interface to a PHP-CRUD-API project.\n  - [PHP-SP-API](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fphp-sp-api): Single file PHP script that adds a REST API to a SQL database.\n  - [dexie-mysql-sync](https:\u002F\u002Fgithub.com\u002FscriptPilot\u002Fdexie-mysql-sync): Synchronization between local IndexedDB and MySQL Database. \n  - [ra-data-treeql](https:\u002F\u002Fgithub.com\u002Fnkappler\u002Fra-data-treeql): NPM package that provides a [Data Provider](https:\u002F\u002Fmarmelab.com\u002Freact-admin\u002FDataProviderIntroduction.html) for [React Admin](https:\u002F\u002Fmarmelab.com\u002Freact-admin\u002F).\n  - [scriptPilot\u002Fvueuse](https:\u002F\u002Fgithub.com\u002FscriptPilot\u002Fvueuse\u002F): Vue [Composables](https:\u002F\u002Fvuejs.org\u002Fguide\u002Freusability\u002Fcomposables.html) in addition to [VueUse.org](https:\u002F\u002Fvueuse.org\u002F) (that support PHP-CRUD-API).\n  - [scriptPilot\u002Fadd-php-backend](https:\u002F\u002Fgithub.com\u002FscriptPilot\u002Fadd-php-backend): Add MySQL, phpMyAdmin and PHP-CRUD-API to your dev environment. \n  - [VUE-CRUD-UI](https:\u002F\u002Fgithub.com\u002Fnlware\u002Fvue-crud-ui): Single file Vue.js script that adds a UI to a PHP-CRUD-API project.\n  \nThere are also ports of this script in:\n\n- [Go-CRUD-API](https:\u002F\u002Fgithub.com\u002Fdranih\u002Fgo-crud-api) (work in progress)\n- [Java JDBC by Ivan Kolchagov](https:\u002F\u002Fgithub.com\u002Fkolchagov\u002Fjava-crud-api) (v1)\n- [Java Spring Boot + jOOQ](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fjava-crud-api\u002Ftree\u002Fmaster\u002Ffull) (v2: work in progress)\n\nThere are also proof-of-concept ports of this script that only support basic REST CRUD functionality in:\n[PHP](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fphp-crud-api\u002Fblob\u002Fmaster\u002Fextras\u002Fcore.php),\n[Java](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fjava-crud-api\u002Fblob\u002Fmaster\u002Fcore\u002Fsrc\u002Fmain\u002Fjava\u002Fcom\u002Ftqdev\u002FCrudApiHandler.java),\n[Go](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fgo-crud-api\u002Fblob\u002Fmaster\u002Fapi.go),\n[C# .net core](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fcore-data-api\u002Fblob\u002Fmaster\u002FProgram.cs),\n[Node.js](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fjs-crud-api\u002Fblob\u002Fmaster\u002Fapp.js) and\n[Python](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fpy-crud-api\u002Fblob\u002Fmaster\u002Fapi.py).\n\n## Compilation\n\nYou can install all dependencies of this project using the following command:\n\n    php install.php\n\nYou can compile all files into a single \"`api.php`\" file using:\n\n    php build.php\n\nNote that you don't use compilation when you integrate this project into another project or framework (use Composer instead).\n\n### Development\n\nYou can access the non-compiled code at the URL:\n\n    http:\u002F\u002Flocalhost:8080\u002Fsrc\u002Frecords\u002Fposts\u002F1\n\nThe non-compiled code resides in the \"`src`\" and \"`vendor`\" directories. The \"`vendor`\" directory contains the dependencies.\n\n### Updating dependencies\n\nYou can update all dependencies of this project using the following command:\n\n    php update.php\n\nThis script will install and run [Composer](https:\u002F\u002Fgetcomposer.org\u002F) to update the dependencies.\n\nNB: The update script will patch the dependencies in the vendor directory for PHP 7.0 compatibility.\n\n## TreeQL, a pragmatic GraphQL\n\n[TreeQL](https:\u002F\u002Ftreeql.org) allows you to create a \"tree\" of JSON objects based on your SQL database structure (relations) and your query.\n\nIt is loosely based on the REST standard and also inspired by json:api.\n\n### CRUD + List\n\nThe example posts table has only a a few fields:\n\n    posts  \n    =======\n    id     \n    title  \n    content\n    created\n\nThe CRUD + List operations below act on this table.\n\n#### Create\n\nIf you want to create a record the request can be written in URL format as: \n\n    POST \u002Frecords\u002Fposts\n\nYou have to send a body containing:\n\n    {\n        \"title\": \"Black is the new red\",\n        \"content\": \"This is the second post.\",\n        \"created\": \"2018-03-06T21:34:01Z\"\n    }\n\nAnd it will return the value of the primary key of the newly created record:\n\n    2\n\n#### Read\n\nTo read a record from this table the request can be written in URL format as:\n\n    GET \u002Frecords\u002Fposts\u002F1\n\nWhere \"1\" is the value of the primary key of the record that you want to read. It will return:\n\n    {\n        \"id\": 1\n        \"title\": \"Hello world!\",\n        \"content\": \"Welcome to the first post.\",\n        \"created\": \"2018-03-05T20:12:56Z\"\n    }\n\nOn read operations you may apply joins.\n\n#### Update\n\nTo update a record in this table the request can be written in URL format as:\n\n    PUT \u002Frecords\u002Fposts\u002F1\n\nWhere \"1\" is the value of the primary key of the record that you want to update. Send as a body:\n\n    {\n        \"title\": \"Adjusted title!\"\n    }\n\nThis adjusts the title of the post. And the return value is the number of rows that are set:\n\n    1\n\n#### Delete\n\nIf you want to delete a record from this table the request can be written in URL format as:\n\n    DELETE \u002Frecords\u002Fposts\u002F1\n\nAnd it will return the number of deleted rows:\n\n    1\n\n#### List\n\nTo list records from this table the request can be written in URL format as:\n\n    GET \u002Frecords\u002Fposts\n\nIt will return:\n\n    {\n        \"records\":[\n            {\n                \"id\": 1,\n                \"title\": \"Hello world!\",\n                \"content\": \"Welcome to the first post.\",\n                \"created\": \"2018-03-05T20:12:56Z\"\n            }\n        ]\n    }\n\nOn list operations you may apply filters and joins.\n\n### Filters\n\nFilters provide search functionality, on list calls, using the \"filter\" parameter. You need to specify the column\nname, a comma, the match type, another comma and the value you want to filter on. These are supported match types:\n\n  - \"cs\": contain string (string contains value)\n  - \"sw\": start with (string starts with value)\n  - \"ew\": end with (string end with value)\n  - \"eq\": equal (string or number matches exactly)\n  - \"lt\": lower than (number is lower than value)\n  - \"le\": lower or equal (number is lower than or equal to value)\n  - \"ge\": greater or equal (number is higher than or equal to value)\n  - \"gt\": greater than (number is higher than value)\n  - \"bt\": between (number is between two comma separated values)\n  - \"in\": in (number or string is in comma separated list of values)\n  - \"is\": is null (field contains \"NULL\" value)\n\nYou can negate all filters by prepending a \"n\" character, so that \"eq\" becomes \"neq\". \nExamples of filter usage are:\n\n    GET \u002Frecords\u002Fcategories?filter=name,eq,Internet\n    GET \u002Frecords\u002Fcategories?filter=name,sw,Inter\n    GET \u002Frecords\u002Fcategories?filter=id,le,1\n    GET \u002Frecords\u002Fcategories?filter=id,ngt,1\n    GET \u002Frecords\u002Fcategories?filter=id,bt,0,1\n    GET \u002Frecords\u002Fcategories?filter=id,in,0,1\n\nOutput:\n\n    {\n        \"records\":[\n            {\n                \"id\": 1\n                \"name\": \"Internet\"\n            }\n        ]\n    }\n\nIn the next section we dive deeper into how you can apply multiple filters on a single list call.\n\n### Multiple filters\n\nFilters can be a by applied by repeating the \"filter\" parameter in the URL. For example the following URL: \n\n    GET \u002Frecords\u002Fcategories?filter=id,gt,1&filter=id,lt,3\n\nwill request all categories \"where id > 1 and id \u003C 3\". If you wanted \"where id = 2 or id = 4\" you should write:\n\n    GET \u002Frecords\u002Fcategories?filter1=id,eq,2&filter2=id,eq,4\n    \nAs you see we added a number to the \"filter\" parameter to indicate that \"OR\" instead of \"AND\" should be applied.\nNote that you can also repeat \"filter1\" and create an \"AND\" within an \"OR\". Since you can also go one level deeper\nby adding a letter (a-f) you can create almost any reasonably complex condition tree.\n\nNB: You can only filter on the requested table (not on it's included tables) and filters are only applied on list calls.\n\n### Column selection\n\nBy default all columns are selected. With the \"include\" parameter you can select specific columns. \nYou may use a dot to separate the table name from the column name. Multiple columns should be comma separated. \nAn asterisk (\"*\") may be used as a wildcard to indicate \"all columns\". Similar to \"include\" you may use the \"exclude\" parameter to remove certain columns:\n\n```\nGET \u002Frecords\u002Fcategories\u002F1?include=name\nGET \u002Frecords\u002Fcategories\u002F1?include=categories.name\nGET \u002Frecords\u002Fcategories\u002F1?exclude=categories.id\n```\n\nOutput:\n\n```\n    {\n        \"name\": \"Internet\"\n    }\n```\n\nNB: Columns that are used to include related entities are automatically added and cannot be left out of the output.\n\n### Ordering\n\nWith the \"order\" parameter you can sort. By default the sort is in ascending order, but by specifying \"desc\" this can be reversed:\n\n```\nGET \u002Frecords\u002Fcategories?order=name,desc\nGET \u002Frecords\u002Fcategories?order=id,desc&order=name\n```\n\nOutput:\n\n```\n    {\n        \"records\":[\n            {\n                \"id\": 3\n                \"name\": \"Web development\"\n            },\n            {\n                \"id\": 1\n                \"name\": \"Internet\"\n            }\n        ]\n    }\n```\n\nNB: You may sort on multiple fields by using multiple \"order\" parameters. You can not order on \"joined\" columns.\n\n### Limit size\n\nThe \"size\" parameter limits the number of returned records. This can be used for top N lists together with the \"order\" parameter (use descending order).\n\n```\nGET \u002Frecords\u002Fcategories?order=id,desc&size=1\n```\n\nOutput:\n\n```\n    {\n        \"records\":[\n            {\n                \"id\": 3\n                \"name\": \"Web development\"\n            }\n        ]\n    }\n```\n\nNB: If you also want to know to the total number of records you may want to use the \"page\" parameter.\n\n### Pagination\n\nThe \"page\" parameter holds the requested page. The default page size is 20, but can be adjusted (e.g. to 50).\n\n```\nGET \u002Frecords\u002Fcategories?order=id&page=1\nGET \u002Frecords\u002Fcategories?order=id&page=1,50\n```\n\nOutput:\n\n```\n    {\n        \"records\":[\n            {\n                \"id\": 1\n                \"name\": \"Internet\"\n            },\n            {\n                \"id\": 3\n                \"name\": \"Web development\"\n            }\n        ],\n        \"results\": 2\n    }\n```\n\nThe element \"results\" holds to total number of records in the table, which would be returned if no pagination would be used.\n\nNB: Since pages that are not ordered cannot be paginated, pages will be ordered by primary key.\n\n### Joins\n\nLet's say that you have a posts table that has comments (made by users) and the posts can have tags.\n\n    posts    comments  users     post_tags  tags\n    =======  ========  =======   =========  ======= \n    id       id        id        id         id\n    title    post_id   username  post_id    name\n    content  user_id   phone     tag_id\n    created  message\n\nWhen you want to list posts with their comments users and tags you can ask for two \"tree\" paths:\n\n    posts -> comments  -> users\n    posts -> post_tags -> tags\n\nThese paths have the same root and this request can be written in URL format as:\n\n    GET \u002Frecords\u002Fposts?join=comments,users&join=tags\n\nHere you are allowed to leave out the intermediate table that binds posts to tags. In this example\nyou see all three table relation types (hasMany, belongsTo and hasAndBelongsToMany) in effect:\n\n- \"post\" has many \"comments\"\n- \"comment\" belongs to \"user\"\n- \"post\" has and belongs to many \"tags\"\n\nThis may lead to the following JSON data:\n\n    {\n        \"records\":[\n            {\n                \"id\": 1,\n                \"title\": \"Hello world!\",\n                \"content\": \"Welcome to the first post.\",\n                \"created\": \"2018-03-05T20:12:56Z\",\n                \"comments\": [\n                    {\n                        id: 1,\n                        post_id: 1,\n                        user_id: {\n                            id: 1,\n                            username: \"mevdschee\",\n                            phone: null,\n                        },\n                        message: \"Hi!\"\n                    },\n                    {\n                        id: 2,\n                        post_id: 1,\n                        user_id: {\n                            id: 1,\n                            username: \"mevdschee\",\n                            phone: null,\n                        },\n                        message: \"Hi again!\"\n                    }\n                ],\n                \"tags\": []\n            },\n            {\n                \"id\": 2,\n                \"title\": \"Black is the new red\",\n                \"content\": \"This is the second post.\",\n                \"created\": \"2018-03-06T21:34:01Z\",\n                \"comments\": [],\n                \"tags\": [\n                    {\n                        id: 1,\n                        message: \"Funny\"\n                    },\n                    {\n                        id: 2,\n                        message: \"Informational\"\n                    }\n                ]\n            }\n        ]\n    }\n\nYou see that the \"belongsTo\" relationships are detected and the foreign key value is replaced by the referenced object.\nIn case of \"hasMany\" and \"hasAndBelongsToMany\" the table name is used a new property on the object.\n\n### Batch operations\n\nWhen you want to create, read, update or delete you may specify multiple primary key values in the URL.\nYou also need to send an array instead of an object in the request body for create and update. \n\nTo read a record from this table the request can be written in URL format as:\n\n    GET \u002Frecords\u002Fposts\u002F1,2\n\nThe result may be:\n\n    [\n            {\n                \"id\": 1,\n                \"title\": \"Hello world!\",\n                \"content\": \"Welcome to the first post.\",\n                \"created\": \"2018-03-05T20:12:56Z\"\n            },\n            {\n                \"id\": 2,\n                \"title\": \"Black is the new red\",\n                \"content\": \"This is the second post.\",\n                \"created\": \"2018-03-06T21:34:01Z\"\n            }\n    ]\n\nSimilarly when you want to do a batch update the request in URL format is written as:\n\n    PUT \u002Frecords\u002Fposts\u002F1,2\n\nWhere \"1\" and \"2\" are the values of the primary keys of the records that you want to update. The body should \ncontain the same number of objects as there are primary keys in the URL:\n\n    [   \n        {\n            \"title\": \"Adjusted title for ID 1\"\n        },\n        {\n            \"title\": \"Adjusted title for ID 2\"\n        }        \n    ]\n\nThis adjusts the titles of the posts. And the return values are the number of rows that are set:\n\n    [1,1]\n\nWhich means that there were two update operations and each of them had set one row. Batch operations use database\ntransactions, so they either all succeed or all fail (successful ones get rolled back). If they fail the body will\ncontain the list of error documents. In the following response the first operation succeeded and the second operation\nof the batch failed due to an integrity violation:\n\n    [   \n        {\n            \"code\": 0,\n            \"message\": \"Success\"\n        },\n        {\n            \"code\": 1010,\n            \"message\": \"Data integrity violation\"\n        }\n    ]\n\nThe response status code will always be 424 (failed dependency) in case of any failure of one of the batch operations.\n\nTo insert multiple records into this table the request can be written in URL format as:\n\n    POST \u002Frecords\u002Fposts\n\nThe body should contain an array of records to be inserted:\n\n    [\n            {\n                \"title\": \"Hello world!\",\n                \"content\": \"Welcome to the first post.\",\n                \"created\": \"2018-03-05T20:12:56Z\"\n            },\n            {\n                \"title\": \"Black is the new red\",\n                \"content\": \"This is the second post.\",\n                \"created\": \"2018-03-06T21:34:01Z\"\n            }\n    ]\n\nThe return value is also an array containing the primary keys of the newly inserted records:\n\n    [1,2] \n\nNote that batch operation for DELETE follows the same pattern as PUT, but without a body.\n\n### Spatial support\n\nFor spatial support there is an extra set of filters that can be applied on geometry columns and that starting with an \"s\":\n\n  - \"sco\": spatial contains (geometry contains another)\n  - \"scr\": spatial crosses (geometry crosses another)\n  - \"sdi\": spatial disjoint (geometry is disjoint from another)\n  - \"seq\": spatial equal (geometry is equal to another)\n  - \"sin\": spatial intersects (geometry intersects another)\n  - \"sov\": spatial overlaps (geometry overlaps another)\n  - \"sto\": spatial touches (geometry touches another)\n  - \"swi\": spatial within (geometry is within another)\n  - \"sic\": spatial is closed (geometry is closed and simple)\n  - \"sis\": spatial is simple (geometry is simple)\n  - \"siv\": spatial is valid (geometry is valid)\n\nThese filters are based on OGC standards and so is the WKT specification in which the geometry columns are represented.\nNote that the SRID that is assumed when converting from WKT to geometry is specified by the config variable `geometrySrid` and defaults to 4326 (WGS 84).\n\n#### GeoJSON\n\nThe GeoJSON support is a read-only view on the tables and records in GeoJSON format. These requests are supported:\n\n    method path                  - operation - description\n    ----------------------------------------------------------------------------------------\n    GET    \u002Fgeojson\u002F{table}      - list      - lists records as a GeoJSON FeatureCollection\n    GET    \u002Fgeojson\u002F{table}\u002F{id} - read      - reads a record by primary key as a GeoJSON Feature\n\nThe \"`\u002Fgeojson`\" endpoint uses the \"`\u002Frecords`\" endpoint internally and inherits all functionality, such as joins and filters.\nIt also supports a \"geometry\" parameter to indicate the name of the geometry column in case the table has more than one.\nFor map views it supports the \"bbox\" parameter in which you can specify upper-left and lower-right coordinates (comma separated).\nThe following Geometry types are supported by the GeoJSON implementation:\n\n  - Point\n  - MultiPoint\n  - LineString\n  - MultiLineString\n  - Polygon\n  - MultiPolygon\n\nThe GeoJSON functionality is enabled by default, but can be disabled using the \"controllers\" configuration.\n\n## Mapping names for legacy systems\n\nTo support creating an API for (a part of) a legacy system (such as Wordpress) you may want to map the table and column \nnames as can not improve them without changing the software, while the names may need some improvement for consistency.\nThe config allows you to rename tables and columns with a comma separated list of mappings that are split with an \nequal sign, like this:\n\n    'mapping' => 'wp_posts=posts,wp_posts.ID=posts.id',\n\nThis specific example will expose the \"`wp_posts`\" table at a \"`posts`\" end-point (instead of \"`wp_posts`\") and the \ncolumn \"`ID`\" within that table as the \"`id`\" property (in lower case instead of upper case).\n\nNB: Since these two mappings overlap the first (less specific) mapping may be omitted.\n\n## Middleware\n\nYou can enable the following middleware using the \"middlewares\" config parameter:\n\n- \"firewall\": Limit access to specific IP addresses\n- \"sslRedirect\": Force connection over HTTPS instead of HTTP\n- \"cors\": Support for CORS requests (enabled by default)\n- \"xsrf\": Block XSRF attacks using the 'Double Submit Cookie' method\n- \"ajaxOnly\": Restrict non-AJAX requests to prevent XSRF attacks\n- \"apiKeyAuth\": Support for \"API Key Authentication\"\n- \"apiKeyDbAuth\": Support for \"API Key Database Authentication\"\n- \"dbAuth\": Support for \"Database Authentication\"\n- \"wpAuth\": Support for \"Wordpress Authentication\"\n- \"jwtAuth\": Support for \"JWT Authentication\"\n- \"basicAuth\": Support for \"Basic Authentication\"\n- \"reconnect\": Reconnect to the database with different parameters\n- \"authorization\": Restrict access to certain tables or columns\n- \"validation\": Return input validation errors for custom rules and default type rules\n- \"ipAddress\": Fill a protected field with the IP address on create\n- \"sanitation\": Apply input sanitation on create and update\n- \"multiTenancy\": Restricts tenants access in a multi-tenant scenario\n- \"pageLimits\": Restricts list operations to prevent database scraping\n- \"joinLimits\": Restricts join parameters to prevent database scraping\n- \"textSearch\": Search in all text fields with a simple parameter\n- \"customization\": Provides handlers for request and response customization\n- \"json\": Support read\u002Fwrite of JSON strings as JSON objects\u002Farrays\n- \"xml\": Translates all input and output from JSON to XML\n\nThe \"middlewares\" config parameter is a comma separated list of enabled middlewares.\nYou can tune the middleware behavior using middleware specific configuration parameters:\n\n- \"firewall.reverseProxy\": Set to \"true\" when a reverse proxy is used (\"\")\n- \"firewall.allowedIpAddresses\": List of IP addresses that are allowed to connect (\"\")\n- \"cors.allowedOrigins\": The origins allowed in the CORS headers (\"*\")\n- \"cors.allowHeaders\": The headers allowed in the CORS request (\"Content-Type, X-XSRF-TOKEN, X-Authorization\")\n- \"cors.allowMethods\": The methods allowed in the CORS request (\"OPTIONS, GET, PUT, POST, DELETE, PATCH\")\n- \"cors.allowCredentials\": To allow credentials in the CORS request (\"true\")\n- \"cors.exposeHeaders\": Whitelist headers that browsers are allowed to access (\"\")\n- \"cors.maxAge\": The time that the CORS grant is valid in seconds (\"1728000\")\n- \"xsrf.excludeMethods\": The methods that do not require XSRF protection (\"OPTIONS,GET\")\n- \"xsrf.cookieName\": The name of the XSRF protection cookie (\"XSRF-TOKEN\")\n- \"xsrf.headerName\": The name of the XSRF protection header (\"X-XSRF-TOKEN\")\n- \"ajaxOnly.excludeMethods\": The methods that do not require AJAX (\"OPTIONS,GET\")\n- \"ajaxOnly.headerName\": The name of the required header (\"X-Requested-With\")\n- \"ajaxOnly.headerValue\": The value of the required header (\"XMLHttpRequest\")\n- \"apiKeyAuth.mode\": Set to \"optional\" if you want to allow anonymous access (\"required\")\n- \"apiKeyAuth.header\": The name of the API key header (\"X-API-Key\")\n- \"apiKeyAuth.keys\": List of API keys that are valid (\"\")\n- \"apiKeyDbAuth.mode\": Set to \"optional\" if you want to allow anonymous access (\"required\")\n- \"apiKeyDbAuth.header\": The name of the API key header (\"X-API-Key\")\n- \"apiKeyDbAuth.usersTable\": The table that is used to store the users in (\"users\")\n- \"apiKeyDbAuth.apiKeyColumn\": The users table column that holds the API key (\"api_key\")\n- \"dbAuth.mode\": Set to \"optional\" if you want to allow anonymous access (\"required\")\n- \"dbAuth.usersTable\": The table that is used to store the users in (\"users\")\n- \"dbAuth.loginTable\": The table or view that is used to retrieve the users info for login (\"users\")\n- \"dbAuth.usernameColumn\": The users table column that holds usernames (\"username\")\n- \"dbAuth.passwordColumn\": The users table column that holds passwords (\"password\")\n- \"dbAuth.returnedColumns\": The columns returned on successful login, empty means 'all' (\"\")\n- \"dbAuth.usernameFormField\": The name of the form field that holds the username (\"username\")\n- \"dbAuth.passwordFormField\": The name of the form field that holds the password (\"password\")\n- \"dbAuth.newPasswordFormField\": The name of the form field that holds the new password (\"newPassword\")\n- \"dbAuth.registerUser\": JSON user data (or \"1\") in case you want the \u002Fregister endpoint enabled (\"\")\n- \"dbAuth.loginAfterRegistration\": 1 or zero if registered users should be logged in after registration (\"\")\n- \"dbAuth.passwordLength\": Minimum length that the password must have (\"12\")\n- \"dbAuth.sessionName\": The name of the PHP session that is started (\"\")\n- \"wpAuth.mode\": Set to \"optional\" if you want to allow anonymous access (\"required\")\n- \"wpAuth.wpDirectory\": The folder\u002Fpath where the Wordpress install can be found (\".\")\n- \"wpAuth.usernameFormField\": The name of the form field that holds the username (\"username\")\n- \"wpAuth.passwordFormField\": The name of the form field that holds the password (\"password\")\n- \"jwtAuth.mode\": Set to \"optional\" if you want to allow anonymous access (\"required\")\n- \"jwtAuth.header\": Name of the header containing the JWT token (\"X-Authorization\")\n- \"jwtAuth.leeway\": The acceptable number of seconds of clock skew (\"5\")\n- \"jwtAuth.ttl\": The number of seconds the token is valid (\"30\")\n- \"jwtAuth.secrets\": The shared secret(s) used to sign the JWT token with (\"\")\n- \"jwtAuth.algorithms\": The algorithms that are allowed, empty means 'all' (\"\")\n- \"jwtAuth.audiences\": The audiences that are allowed, empty means 'all' (\"\")\n- \"jwtAuth.issuers\": The issuers that are allowed, empty means 'all' (\"\")\n- \"jwtAuth.sessionName\": The name of the PHP session that is started (\"\")\n- \"basicAuth.mode\": Set to \"optional\" if you want to allow anonymous access (\"required\")\n- \"basicAuth.realm\": Text to prompt when showing login (\"Username and password required\")\n- \"basicAuth.passwordFile\": The file to read for username\u002Fpassword combinations (\".htpasswd\")\n- \"basicAuth.sessionName\": The name of the PHP session that is started (\"\")\n- \"reconnect.driverHandler\": Handler to implement retrieval of the database driver (\"\")\n- \"reconnect.addressHandler\": Handler to implement retrieval of the database address (\"\")\n- \"reconnect.portHandler\": Handler to implement retrieval of the database port (\"\")\n- \"reconnect.databaseHandler\": Handler to implement retrieval of the database name (\"\")\n- \"reconnect.tablesHandler\": Handler to implement retrieval of the table names (\"\")\n- \"reconnect.mappingHandler\": Handler to implement retrieval of the name mapping (\"\")\n- \"reconnect.usernameHandler\": Handler to implement retrieval of the database username (\"\")\n- \"reconnect.passwordHandler\": Handler to implement retrieval of the database password (\"\")\n- \"authorization.tableHandler\": Handler to implement table authorization rules (\"\")\n- \"authorization.columnHandler\": Handler to implement column authorization rules (\"\")\n- \"authorization.pathHandler\": Handler to implement path authorization rules (\"\")\n- \"authorization.recordHandler\": Handler to implement record authorization filter rules (\"\")\n- \"validation.handler\": Handler to implement validation rules for input values (\"\")\n- \"validation.types\": Types to enable type validation for, empty means 'none' (\"all\")\n- \"validation.tables\": Tables to enable type validation for, empty means 'none' (\"all\")\n- \"ipAddress.tables\": Tables to search for columns to override with IP address (\"\")\n- \"ipAddress.columns\": Columns to protect and override with the IP address on create (\"\")\n- \"sanitation.handler\": Handler to implement sanitation rules for input values (\"\")\n- \"sanitation.types\": Types to enable type sanitation for, empty means 'none' (\"all\")\n- \"sanitation.tables\": Tables to enable type sanitation for, empty means 'none' (\"all\")\n- \"multiTenancy.handler\": Handler to implement simple multi-tenancy rules (\"\")\n- \"pageLimits.pages\": The maximum page number that a list operation allows (\"100\")\n- \"pageLimits.records\": The maximum number of records returned by a list operation (\"1000\")\n- \"joinLimits.depth\": The maximum depth (length) that is allowed in a join path (\"3\")\n- \"joinLimits.tables\": The maximum number of tables that you are allowed to join (\"10\")\n- \"joinLimits.records\": The maximum number of records returned for a joined entity (\"1000\")\n- \"textSearch.parameter\": The name of the parameter used for the search term (\"search\")\n- \"customization.beforeHandler\": Handler to implement request customization (\"\")\n- \"customization.afterHandler\": Handler to implement response customization (\"\")\n- \"json.controllers\": Controllers to process JSON strings for (\"records,geojson\")\n- \"json.tables\": Tables to process JSON strings for (\"all\")\n- \"json.columns\": Columns to process JSON strings for (\"all\")\n- \"xml.types\": JSON types that should be added to the XML type attribute (\"null,array\")\n\nIf you don't specify these parameters in the configuration, then the default values (between brackets) are used.\n\nIn the sections below you find more information on the built-in middleware.\n\n### Authentication\n\nCurrently there are five types of authentication supported. They all store the authenticated user in the `$_SESSION` super global.\nThis variable can be used in the authorization handlers to decide wether or not somebody should have read or write access to certain tables, columns or records.\nThe following overview shows the kinds of authentication middleware that you can enable.\n\n| Name       | Middleware   | Authenticated via      | Users are stored in | Session variable        |\n| ---------- | ------------ | ---------------------- | ------------------- | ----------------------- |\n| API key    | apiKeyAuth   | 'X-API-Key' header     | configuration       | `$_SESSION['apiKey']`   |\n| API key DB | apiKeyDbAuth | 'X-API-Key' header     | database table      | `$_SESSION['apiUser']`  |\n| Database   | dbAuth       | '\u002Flogin' endpoint      | database table      | `$_SESSION['user']`     |\n| Basic      | basicAuth    | 'Authorization' header | '.htpasswd' file    | `$_SESSION['username']` |\n| JWT        | jwtAuth      | 'Authorization' header | identity provider   | `$_SESSION['claims']`   |\n\nBelow you find more information on each of the authentication types.\n\n#### API key authentication\n\nAPI key authentication works by sending an API key in a request header.\nThe header name defaults to \"X-API-Key\" and can be configured using the 'apiKeyAuth.header' configuration parameter.\nValid API keys must be configured using the 'apiKeyAuth.keys' configuration parameter (comma separated list).\n\n    X-API-Key: 02c042aa-c3c2-4d11-9dae-1a6e230ea95e\n\nThe authenticated API key will be stored in the `$_SESSION['apiKey']` variable.\n\nNote that the API key authentication does not require or use session cookies.\n\n#### API key database authentication\n\nAPI key database authentication works by sending an API key in a request header \"X-API-Key\" (the name is configurable).\nValid API keys are read from the database from the column \"api_key\" of the \"users\" table (both names are configurable).\n\n    X-API-Key: 02c042aa-c3c2-4d11-9dae-1a6e230ea95e\n\nThe authenticated user (with all it's properties) will be stored in the `$_SESSION['apiUser']` variable.\n\nNote that the API key database authentication does not require or use session cookies.\n\n#### Database authentication\n\nThe database authentication middleware defines five new routes:\n\n    method path       - parameters                      - description\n    ---------------------------------------------------------------------------------------------------\n    GET    \u002Fme        -                                 - returns the user that is currently logged in\n    POST   \u002Fregister  - username, password              - adds a user with given username and password\n    POST   \u002Flogin     - username, password              - logs a user in by username and password\n    POST   \u002Fpassword  - username, password, newPassword - updates the password of the logged in user\n    POST   \u002Flogout    -                                 - logs out the currently logged in user\n\nA user can be logged in by sending it's username and password to the login endpoint (in JSON format).\nThe authenticated user (with all it's properties) will be stored in the `$_SESSION['user']` variable.\nThe user can be logged out by sending a POST request with an empty body to the logout endpoint.\nThe passwords are stored as hashes in the password column in the users table. You can register a new user\nusing the register endpoint, but this functionality must be turned on using the \"dbAuth.registerUser\"\nconfiguration parameter.\n\nIt is IMPORTANT to restrict access to the users table using the 'authorization' middleware, otherwise all \nusers can freely add, modify or delete any account! The minimal configuration is shown below:\n\n    'middlewares' => 'dbAuth,authorization',\n    'authorization.tableHandler' => function ($operation, $tableName) {\n        return $tableName != 'users';\n    },\n\nNote that this middleware uses session cookies and stores the logged in state on the server.\n\n**Login using views with joined table**\n\nFor login operations, it is possible to use a view as the usersTable. Such view can return a filtered result from the users table, e.g., *where active = true* or it may also return a result multiple tables thru a table join. At a minimum, the view should include the ***username*** and ***password*** and a field named ***id***.\n\nHowever, views with joined tables are not insertable ([see issue 907](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fphp-crud-api\u002Fissues\u002F907) ). As a workaround, use the property ***loginTable*** to set a different reference table for login. The **usersTable** will still be set to the normal, insertable users table. \n\n#### Wordpress authentication\n\nThe Wordpress authentication middleware defines three routes:\n\n    method path       - parameters                      - description\n    ---------------------------------------------------------------------------------------------------\n    GET    \u002Fme        -                                 - returns the user that is currently logged in\n    POST   \u002Flogin     - username, password              - logs a user in by username and password\n    POST   \u002Flogout    -                                 - logs out the currently logged in user\n\nA user can be logged in by sending it's username and password to the login endpoint (in JSON format).\nThe user can be logged out by sending a POST request with an empty body to the logout endpoint.\nYou need to specify the Wordpress installation directory using the \"wpAuth.wpDirectory\" configuration parameter.\nThe middleware calls \"wp-load.php\" this allows you to use Wordpress functions in the authorization middleware, like:\n\n- wp_get_current_user()\n- is_user_logged_in()\n- is_super_admin()\n- user_can(wp_get_current_user(),'edit_posts');\n\nNote that the `$_SESSION` variable is not used by this middleware.\n\n#### Basic authentication\n\nThe Basic type supports a file (by default '.htpasswd') that holds the users and their (hashed) passwords separated by a colon (':'). \nWhen the passwords are entered in plain text they will be automatically hashed.\nThe authenticated username will be stored in the `$_SESSION['username']` variable.\nYou need to send an \"Authorization\" header containing a base64 url encoded version of your colon separated username and password, after the word \"Basic\".\n\n    Authorization: Basic dXNlcm5hbWUxOnBhc3N3b3JkMQ\n\nThis example sends the string \"username1:password1\".\n\n#### JWT authentication\n\nThe JWT type requires another (SSO\u002FIdentity) server to sign a token that contains claims. \nBoth servers share a secret so that they can either sign or verify that the signature is valid.\nClaims are stored in the `$_SESSION['claims']` variable. You need to send an \"X-Authorization\" \nheader containing a base64 url encoded and dot separated token header, body and signature after\nthe word \"Bearer\" ([read more about JWT here](https:\u002F\u002Fjwt.io\u002F)). The standard says you need to\nuse the \"Authorization\" header, but this is problematic in Apache and PHP.\n\n    X-Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6IjE1MzgyMDc2MDUiLCJleHAiOjE1MzgyMDc2MzV9.Z5px_GT15TRKhJCTHhDt5Z6K6LRDSFnLj8U5ok9l7gw\n\nThis example sends the signed claims:\n\n    {\n      \"sub\": \"1234567890\",\n      \"name\": \"John Doe\",\n      \"admin\": true,\n      \"iat\": \"1538207605\",\n      \"exp\": 1538207635\n    }\n\nNB: The JWT implementation only supports the RSA and HMAC based algorithms.\n\n##### Configure and test JWT authentication with Auth0\n\nFirst you need to create an account on [Auth0](https:\u002F\u002Fauth0.com\u002Fauth\u002Flogin).\nOnce logged in, you have to create an application (its type does not matter). Collect the `Domain`\nand `Client ID` and keep them for a later use. Then, create an API: give it a name and fill the\n`identifier` field with your API endpoint's URL.\n\nThen you have to configure the `jwtAuth.secrets` configuration in your `api.php` file.\nDon't fill it with the `secret` you will find in your Auth0 application settings but with **a\npublic certificate**. To find it, go to the settings of your application, then in \"Extra settings\".\nYou will now find a \"Certificates\" tab where you will find your Public Key in the Signing\nCertificate field.\n\nTo test your integration, you can copy the [auth0\u002Fvanilla.html](examples\u002Fclients\u002Fauth0\u002Fvanilla.html)\nfile. Be sure to fill these three variables:\n\n - `authUrl` with your Auth0 domain\n - `clientId` with your Client ID\n - `audience` with the API URL you created in Auth0\n\nNote that if you don't fill the audience parameter, it will not work because you won't get a valid JWT.\nAlso note that you should fill `jwtAuth.audiences` (with the value of the `audience`) to ensure the\ntokens are validated to be generated for your application.\n\nYou can also change the `url` variable, used to test the API with authentication.\n\n[More info](https:\u002F\u002Fauth0.com\u002Fdocs\u002Fapi-auth\u002Ftutorials\u002Fverify-access-token)\n\n##### Configure and test JWT authentication with Firebase\n\nFirst you need to create a Firebase project on the [Firebase console](https:\u002F\u002Fconsole.firebase.google.com\u002F).\nAdd a web application to this project and grab the code snippet for later use.\n\nThen you have to configure the `jwtAuth.secrets` configuration in your `api.php` file. \nThis can be done as follows:\n\na. Log a user in to your Firebase-based app, get an authentication token for that user\nb. Go to [https:\u002F\u002Fjwt.io\u002F](https:\u002F\u002Fjwt.io\u002F) and paste the token in the decoding field\nc. Read the decoded header information from the token, it will give you the correct `kid`\nd. Grab the public key via this [URL](https:\u002F\u002Fwww.googleapis.com\u002Frobot\u002Fv1\u002Fmetadata\u002Fx509\u002Fsecuretoken@system.gserviceaccount.com), which corresponds to your `kid` from previous step\ne. Now, just fill `jwtAuth.secrets` with your public key in the `api.php`\n\nAlso configure the `jwtAuth.audiences` (fill in the Firebase project ID).\n\nHere is an example of what it should look like in the configuration:\n\n```\n...,\n'middlewares' => 'cors, jwtAuth, authorization',\n        'jwtAuth.secrets' => \"ce5ced6e40dcd1eff407048867b1ed1e706686a0:-----BEGIN CERTIFICATE-----\\nMIIDHDCCAgSgAwIBAgIIExun9bJSK1wwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTkx\\nMjIyMjEyMTA3WhcNMjAwMTA4MDkzNjA3WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\\nggEPADCCAQoCggEBAKsvVDUwXeYQtySNvyI1\u002FtZAk0sj7Zx4\u002F1+YLUomwlK6vmEd\\nyl2IXOYOj3VR7FBA24A9\u002F\u002Fnnrp+mV8YOYEOdaWX7PQo0PIPFPqdA0r7CqBUWHPfQ\\n1WVHVRQY3G0c7upM97UfMes9xOrMqyvecMRk1e5S6eT12Zh2og7yiVs8gP83M1EB\\nGqseUaltaadjyT35w5B0Ny0\u002F7NdLYiv2G6Z0S821SxvSo1\u002FwfmilnBBKYYluP0PA\\n9NPznWFP6uXnX7gKxyJT9\u002F\u002FcYVxTO6+b1TT13Yvrpm1a4EuCOhLrZH6ErHQTccAM\\nhAx8mdNtbROsp0dlPKrSfqO82uFz45RXZYmSeP0CAwEAAaM4MDYwDAYDVR0TAQH\u002F\\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH\u002FBAwwCgYIKwYBBQUHAwIwDQYJ\\nKoZIhvcNAQEFBQADggEBACNsJ5m00gdTvD6j6ahURsGrNZ0VJ0YREVQ5U2Jtubr8\\nn2fuhMxkB8147ISzfi6wZR+yNwPGjlr8JkAHAC0i+Nam9SqRyfZLqsm+tHdgFT8h\\npa+R\u002FFoGrrLzxJNRiv0Trip8hZjgz3PClz6KxBQzqL+rfGV2MbwTXuBoEvLU1mYA\\no3\u002FUboJT7cNGjZ8nHXeoKMsec1\u002FH55lUdconbTm5iMU1sTDf+3StGYzTwC+H6yc2\\nY3zIq3\u002FcQUCrETkALrqzyCnLjRrLYZu36ITOaKUbtmZhwrP99i2f+H4Ab2i8jeMu\\nk61HD29mROYjl95Mko2BxL+76To7+pmn73U9auT+xfA=\\n-----END CERTIFICATE-----\\n\",\n        'jwtAuth.audiences' => 'your-project-id',\n        'cors.allowedOrigins' => '*',\n        'cors.allowHeaders' => 'X-Authorization'\n```\n\nNotes:\n - The `kid:key` pair is formatted as a string\n - Do not include spaces before or after the ':'\n - Use double quotation marks (\") around the string text\n - The string must contain the linefeeds (\\n)\n - `jwtAuth.audiences` should contain your Firebase projectId\n\nTo test your integration, you can copy the [firebase\u002Fvanilla.html](examples\u002Fclients\u002Ffirebase\u002Fvanilla.html)\nfile and the [firebase\u002Fvanilla-success.html](examples\u002Fclients\u002Ffirebase\u002Fvanilla-success.html) file,\nused as a \"success\" page and to display the API result.\n\nReplace, in both files, the Firebase configuration (`firebaseConfig` object).\n\nYou can also change the `url` variable, used to test the API with authentication.\n\n[More info](https:\u002F\u002Ffirebase.google.com\u002Fdocs\u002Fauth\u002Fadmin\u002Fverify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library)\n\n### Authorizing operations\n\nThe Authorization model acts on \"operations\". The most important ones are listed here:\n\n    method path                  - operation - description\n    ----------------------------------------------------------------------------------------\n    GET    \u002Frecords\u002F{table}      - list      - lists records\n    POST   \u002Frecords\u002F{table}      - create    - creates records\n    GET    \u002Frecords\u002F{table}\u002F{id} - read      - reads a record by primary key\n    PUT    \u002Frecords\u002F{table}\u002F{id} - update    - updates columns of a record by primary key\n    DELETE \u002Frecords\u002F{table}\u002F{id} - delete    - deletes a record by primary key\n    PATCH  \u002Frecords\u002F{table}\u002F{id} - increment - increments columns of a record by primary key\n\nThe \"`\u002Fopenapi`\" endpoint will only show what is allowed in your session. It also has a special \n\"document\" operation to allow you to hide tables and columns from the documentation.\n    \nFor endpoints that start with \"`\u002Fcolumns`\" there are the operations \"reflect\" and \"remodel\". \nThese operations can display or change the definition of the database, table or column. \nThis functionality is disabled by default and for good reason (be careful!). \nAdd the \"columns\" controller in the configuration to enable this functionality.\n\n### Authorizing tables, columns and records\n\nBy default all tables, columns and paths are accessible. If you want to restrict access to some tables you may add the 'authorization' middleware \nand define a 'authorization.tableHandler' function that returns 'false' for these tables.\n\n    'authorization.tableHandler' => function ($operation, $tableName) {\n        return $tableName != 'license_keys';\n    },\n\nThe above example will restrict access to the table 'license_keys' for all operations.\n\n    'authorization.columnHandler' => function ($operation, $tableName, $columnName) {\n        return !($tableName == 'users' && $columnName == 'password');\n    },\n\nThe above example will restrict access to the 'password' field of the 'users' table for all operations.\n\n    'authorization.recordHandler' => function ($operation, $tableName) {\n        return ($tableName == 'users') ? 'filter=username,neq,admin' : '';\n    },\n\nThe above example will disallow access to user records where the username is 'admin'. \nThis construct adds a filter to every executed query. \n\n    'authorization.pathHandler' => function ($path) {\n        return $path === 'openapi' ? false : true;\n    },\n\nThe above example will disabled the `\u002Fopenapi` route.\n\nNB: You need to handle the creation of invalid records with a validation (or sanitation) handler.\n\n### SQL GRANT authorization\n\nYou can alternatively use database permissons (SQL GRANT statements) to define the authorization model. In this case you\nshould not use the \"authorization\" middleware, but you do need to use the \"reconnect\" middleware. The handlers of the\n\"reconnect\" middleware allow you to specify the correct username and password, like this:\n\n    'reconnect.usernameHandler' => function () {\n        return 'mevdschee';\n    },\n    'reconnect.passwordHandler' => function () {\n        return 'secret123';\n    },\n\nThis will make the API connect to the database specifying \"mevdschee\" as the username and \"secret123\" as the password.\nThe OpenAPI specification is less specific on allowed and disallowed operations when you are using database permissions,\nas the permissions are not read in the reflection step.\n\nNB: You may want to retrieve the username and password from the session (the \"$_SESSION\" variable).\n\n### Sanitizing input\n\nBy default all input is accepted and sent to the database. If you want to strip (certain) HTML tags before storing you may add \nthe 'sanitation' middleware and define a 'sanitation.handler' function that returns the adjusted value.\n\n    'sanitation.handler' => function ($operation, $tableName, $column, $value) {\n        return is_string($value) ? strip_tags($value) : $value;\n    },\n\nThe above example will strip all HTML tags from strings in the input.\n\n### Type sanitation\n\nIf you enable the 'sanitation' middleware, then you (automatically) also enable type sanitation. When this is enabled you may:\n\n- send leading and trailing whitespace in a non-character field (it will be ignored).\n- send a float to an integer or bigint field (it will be rounded).\n- send a base64url encoded string (it will be converted to regular base64 encoding).\n- send a time\u002Fdate\u002Ftimestamp in any [strtotime accepted format](https:\u002F\u002Fwww.php.net\u002Fmanual\u002Fen\u002Fdatetime.formats.php) (it will be converted).\n\nYou may use the config settings \"`sanitation.types`\" and \"`sanitation.tables`\"' to define for which types and\nin which tables you want to apply type sanitation (defaults to 'all'). Example:\n\n    'sanitation.types' => 'date,timestamp',\n    'sanitation.tables' => 'posts,comments',\n\nHere we enable the type sanitation for date and timestamp fields in the posts and comments tables.\n\n### Validating input\n\nBy default all input is accepted and sent to the database. If you want to validate the input in a custom way, \nyou may add the 'validation' middleware and define a 'validation.handler' function that returns a boolean \nindicating whether or not the value is valid.\n\n    'validation.handler' => function ($operation, $tableName, $column, $value, $context) {\n        return ($column['name'] == 'post_id' && !is_numeric($value)) ? 'must be numeric' : true;\n    },\n\nWhen you edit a comment with id 4 using:\n\n    PUT \u002Frecords\u002Fcomments\u002F4\n\nAnd you send as a body:\n\n    {\"post_id\":\"two\"}\n\nThen the server will return a '422' HTTP status code and nice error message:\n\n    {\n        \"code\": 1013,\n        \"message\": \"Input validation failed for 'comments'\",\n        \"details\": {\n            \"post_id\":\"must be numeric\"\n        }\n    }\n\nYou can parse this output to make form fields show up with a red border and their appropriate error message.\n\n### Type validations\n\nIf you enable the 'validation' middleware, then you (automatically) also enable type validation. \nThis includes the following error messages:\n\n| error message       | reason                      | applies to types                            |\n| ------------------- | --------------------------- | ------------------------------------------- |\n| cannot be null      | unexpected null value       | (any non-nullable column)                   |\n| illegal whitespace  | leading\u002Ftrailing whitespace | integer bigint decimal float double boolean |\n| invalid integer     | illegal characters          | integer bigint                              |\n| string too long     | too many characters         | varchar varbinary                           |\n| invalid decimal     | illegal characters          | decimal                                     |\n| decimal too large   | too many digits before dot  | decimal                                     |\n| decimal too precise | too many digits after dot   | decimal                                     |\n| invalid float       | illegal characters          | float double                                |\n| invalid boolean     | use 1, 0, true or false     | boolean                                     |\n| invalid date        | use yyyy-mm-dd              | date                                        |\n| invalid time        | use hh:mm:ss                | time                                        |\n| invalid timestamp   | use yyyy-mm-dd hh:mm:ss     | timestamp                                   |\n| invalid base64      | illegal characters          | varbinary, blob                             |\n\nYou may use the config settings \"`validation.types`\" and \"`validation.tables`\"' to define for which types and\nin which tables you want to apply type validation (defaults to 'all'). Example:\n\n    'validation.types' => 'date,timestamp',\n    'validation.tables' => 'posts,comments',\n\nHere we enable the type validation for date and timestamp fields in the posts and comments tables.\n\nNB: Types that are enabled will be checked for null values when the column is non-nullable.\n\n### Multi-tenancy support\n\nTwo forms of multi-tenancy are supported:\n\n - Single database, where every table has a tenant column (using the \"multiTenancy\" middleware).\n - Multi database, where every tenant has it's own database (using the \"reconnect\" middleware).\n\nBelow is an explanation of the corresponding middlewares.\n\n#### Multi-tenancy middleware\n\nYou may use the \"multiTenancy\" middleware when you have a single multi-tenant database. \nIf your tenants are identified by the \"customer_id\" column, then you can use the following handler:\n\n    'multiTenancy.handler' => function ($operation, $tableName) {\n        return ['customer_id' => 12];\n    },\n\nThis construct adds a filter requiring \"customer_id\" to be \"12\" to every operation (except for \"create\").\nIt also sets the column \"customer_id\" on \"create\" to \"12\" and removes the column from any other write operation.\n\nNB: You may want to retrieve the customer id from the session (the \"$_SESSION\" variable).\n\n#### Reconnect middleware\n\nYou may use the \"reconnect\" middleware when you have a separate database for each tenant.\nIf the tenant has it's own database named \"customer_12\", then you can use the following handler:\n\n    'reconnect.databaseHandler' => function () {\n        return 'customer_12';\n    },\n\nThis will make the API reconnect to the database specifying \"customer_12\" as the database name. If you don't want\nto use the same credentials, then you should also implement the \"usernameHandler\" and \"passwordHandler\".\n\nNB: You may want to retrieve the database name from the session (the \"$_SESSION\" variable).\n\n### Prevent database scraping\n\nYou may use the \"joinLimits\" and \"pageLimits\" middleware to prevent database scraping.\nThe \"joinLimits\" middleware limits the table depth, number of tables and number of records returned in a join operation. \nIf you want to allow 5 direct direct joins with a maximum of 25 records each, you can specify:\n\n    'joinLimits.depth' => 1,\n    'joinLimits.tables' => 5,\n    'joinLimits.records' => 25,\n\nThe \"pageLimits\" middleware limits the page number and the number records returned from a list operation. \nIf you want to allow no more than 10 pages with a maximum of 25 records each, you can specify:\n\n    'pageLimits.pages' => 10,\n    'pageLimits.records' => 25,\n\nNB: The maximum number of records is also applied when there is no page number specified in the request.\n\n### Search all text fields\n\nYou may use the \"textSearch\" middleware to simplify (wildcard) text searches when listing records. \nIt allows you to specify a \"search\" parameter using:\n\n    GET \u002Frecords\u002Fposts?search=Hello\n\nIt will return all records from \"posts\" that contain \"Hello\" in one of their text (typed) fields:\n\n    {\n        \"records\":[\n            {\n                \"id\": 1,\n                \"title\": \"Hello world!\",\n                \"content\": \"Welcome to the first post.\",\n                \"created\": \"2018-03-05T20:12:56Z\"\n            }\n        ]\n    }\n\nThe example searches the fields \"title\" or \"content\" for the substring \"Hello\".\n\n### Customization handlers\n\nYou may use the \"customization\" middleware to modify request and response and implement any other functionality.\n\n    'customization.beforeHandler' => function ($operation, $tableName, $request, $environment) {\n        $environment->start = microtime(true);\n    },\n    'customization.afterHandler' => function ($operation, $tableName, $response, $environment) {\n        return $response->withHeader('X-Time-Taken', microtime(true) - $environment->start);\n    },\n\nThe above example will add a header \"X-Time-Taken\" with the number of seconds the API call has taken.\n\n### JSON encoding options\n\nYou can change the way the JSON is encoded by setting the configuration parameter \"jsonOptions\".\n\n    'jsonOptions' => JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,\n\nThe above example will set JSON options to 128+256+64 = 448, as per the list of options below:\n\n    JSON_HEX_TAG (1)\n        All \u003C and > are converted to \\u003C and \\u003E. \n    JSON_HEX_AMP (2)\n        All & are converted to \\u0026. \n    JSON_HEX_APOS (4)\n        All ' are converted to \\u0027. \n    JSON_HEX_QUOT (8)\n        All \" are converted to \\u0022. \n    JSON_FORCE_OBJECT (16)\n        Outputs an object rather than an array when a non-associative array is used. \n        Especially useful when the recipient of the output is expecting an object and \n        the array is empty. \n    JSON_NUMERIC_CHECK (32)\n        Encodes numeric strings as numbers. \n    JSON_UNESCAPED_SLASHES (64)\n        Don't escape \u002F. \n    JSON_PRETTY_PRINT (128)\n        Use whitespace in returned data to format it. \n    JSON_UNESCAPED_UNICODE (256)\n        Encode multibyte Unicode characters literally (default is to escape as \\uXXXX). \n    JSON_PARTIAL_OUTPUT_ON_ERROR (512)\n        Substitute some unencodable values instead of failing. \n    JSON_PRESERVE_ZERO_FRACTION (1024)\n        Ensures that float values are always encoded as a float value. \n    JSON_UNESCAPED_LINE_TERMINATORS (2048)\n        The line terminators are kept unescaped when JSON_UNESCAPED_UNICODE is supplied. \n        It uses the same behaviour as it was before PHP 7.1 without this constant. \n        Available as of PHP 7.1.0. \n\nSource: [PHP's JSON constants documentation](https:\u002F\u002Fwww.php.net\u002Fmanual\u002Fen\u002Fjson.constants.php) \n\n### JSON middleware\n\nYou may use the \"json\" middleware to read\u002Fwrite JSON strings as JSON objects and arrays.\n\nJSON strings are automatically detected when the \"json\" middleware is enabled.\n\nYou may limit the scanning of by specifying specific table and\u002For field names: \n\n    'json.tables' => 'products',\n    'json.columns' => 'properties',\n\nThis will change the output of:\n\n    GET \u002Frecords\u002Fproducts\u002F1\n\nWithout \"json\" middleware the output will be:\n\n    {\n        \"id\": 1,\n        \"name\": \"Calculator\",\n        \"price\": \"23.01\",\n        \"properties\": \"{\\\"depth\\\":false,\\\"model\\\":\\\"TRX-120\\\",\\\"width\\\":100,\\\"height\\\":null}\",\n    }\n\nWith \"json\" middleware the output will be:\n\n    {\n        \"id\": 1,\n        \"name\": \"Calculator\",\n        \"price\": \"23.01\",\n        \"properties\": {\n            \"depth\": false,\n            \"model\": \"TRX-120\",\n            \"width\": 100,\n            \"height\": null\n        },\n    }\n\nThis also applies when creating or modifying JSON string fields (also when using batch operations).\n\nNote that JSON string fields cannot be partially updated and that this middleware is disabled by default.\nYou can enable the \"json\" middleware using the \"middlewares\" configuration setting.\n\n### XML middleware\n\nYou may use the \"xml\" middleware to translate input and output from JSON to XML. This request:\n\n    GET \u002Frecords\u002Fposts\u002F1\n\nOutputs (when \"pretty printed\"):\n\n    {\n        \"id\": 1,\n        \"user_id\": 1,\n        \"category_id\": 1,\n        \"content\": \"blog started\"\n    }\n\nWhile (note the \"format\" query parameter):\n\n    GET \u002Frecords\u002Fposts\u002F1?format=xml\n\nOutputs:\n\n    \u003Croot>\n        \u003Cid>1\u003C\u002Fid>\n        \u003Cuser_id>1\u003C\u002Fuser_id>\n        \u003Ccategory_id>1\u003C\u002Fcategory_id>\n        \u003Ccontent>blog started\u003C\u002Fcontent>\n    \u003C\u002Froot>\n\nThis functionality is disabled by default and must be enabled using the \"middlewares\" configuration setting.\n\n### File uploads\n\nFile uploads are supported through the [FileReader API](https:\u002F\u002Fcaniuse.com\u002F#feat=filereader), check out the [example](https:\u002F\u002Fgithub.com\u002Fmevdschee\u002Fphp-crud-api\u002Fblob\u002Fmaster\u002Fexamples\u002Fclients\u002Fupload\u002Fvanilla.html).\n\n## OpenAPI specification\n\nOn the \"\u002Fopenapi\" end-point the OpenAPI 3.0 (formerly called \"Swagger\") specification is served. \nIt is a machine readable instant documentation of your API. To learn more, check out these links:\n\n- [Swagger Editor](https:\u002F\u002Feditor.swagger.io\u002F) can be used to view and debug the generated specification.\n- [OpenAPI specification](https:\u002F\u002Fswagger.io\u002Fspecification\u002F) is a manual for creating an OpenAPI specification.\n- [Swagger Petstore](https:\u002F\u002Fpetstore.swagger.io\u002F) is an example documentation that is generated using OpenAPI.\n\n## Cache\n\nThere are 4 cache engines that can be configured by the \"cacheType\" config parameter:\n\n- TempFile (default)\n- Redis\n- Memcache\n- Memcached\n\nYou can install the dependencies for the last three engines by running:\n\n    sudo apt install php-redis redis\n    sudo apt install php-memcache memcached\n    sudo apt install php-memcached memcached\n\nThe default engine has no dependencies and will use temporary files in the system \"temp\" path.\n\nYou may use the \"cachePath\" config parameter to specify the file system path for the temporary files or\nin case that you use a non-default \"cacheType\" the hostname (optionally with port) of the cache server.\n\n## Types\n\nThese are the supported types with their length, category, JSON type and format:\n\n| type       | length | category  | JSON type | format              |\n| ---------- | ------ | --------- | --------- | ------------------- |\n| varchar    | 255    | character | string    |                     |\n| clob       |        | character | string    |                     |\n| boolean    |        | boolean   | boolean   |                     |\n| integer    |        | integer   | number    |                     |\n| bigint     |        | integer   | number    |                     |\n| float      |        | float     | number    |                     |\n| double     |        | float     | number    |                     |\n| decimal    | 19,4   | decimal   | string    |                     |\n| date       |        | date\u002Ftime | string    | yyyy-mm-dd          | \n| time       |        | date\u002Ftime | string    | hh:mm:ss            |\n| timestamp  |        | date\u002Ftime | string    | yyyy-mm-dd hh:mm:ss |\n| varbinary  | 255    | binary    | string    | base64 encoded      |\n| blob       |        | binary    | string    | base64 encoded      |\n| geometry   |        | other     | string    | well-known text     |\n\nNote that geometry is a non-jdbc type and thus has limited support.\n\n## Data types in JavaScript\n\nJavascript and Javascript object notation (JSON) are not very well suited for reading database records. Decimal, date\u002Ftime, binary and geometry types must be represented as strings in JSON (binary is base64 encoded, geometries are in WKT format). Below are two more serious issues described.\n\n### 64 bit integers\n\nJavaScript does not support 64 bit integers. All numbers are stored as 64 bit floating point values. The mantissa of a 64 bit floating point number is only 53 bit and that is why all integer numbers bigger than 53 bit may cause problems in JavaScript.\n\n### Inf and NaN floats\n\nThe valid floating point values 'Infinite' (calculated with '1\u002F0') and 'Not a Number' (calculated with '0\u002F0') cannot be expressed in JSON, as they are not supported by the [JSON specification](https:\u002F\u002Fwww.json.org). When these values are stored in a database then you cannot read them as this script outputs database records as JSON.\n\n## Errors\n\nThe following errors may be reported:\n\n| Error | HTTP response code        | Message\n| ----- | ------------------------- | --------------\n| 1000  | 404 Not found    ","PHP-CRUD-API 是一个单文件 PHP 脚本，用于为 MySQL、PostgreSQL、SQL Server 或 SQLite 数据库添加 REST API。其核心功能包括自动化的 CRUD 操作支持以及对地理空间数据（如 GeoJSON）的处理能力。该项目支持多数据库系统，并且能够通过简单的配置快速部署。适用于需要快速搭建后端 API 服务以支持前端应用或移动应用开发的场景，尤其是当项目预算有限或时间紧迫时。此外，它还可以作为 TreeQL 的 PHP 实现参考，方便开发者集成到现有的 Laravel、Symfony 或 SlimPHP 等框架中使用。",2,"2026-06-11 03:18:15","top_language"]