PHP¶
Code in PHP defines function main($g). It runs on the official image of PHP (php:8.4-cli unless the settings name another version, 8.3 at least), with only the packages you add from Packagist.
In short
- Write
function main($g). - It runs on
php:8.4-cli, unless the settings name another version. - Refuse a request with
throw new AmErrors($message, 400).
| hello.php | |
|---|---|
| Runtime | PHP 8.3 or later : the released versions of php:<version>-cli (8.5, 8.4, 8.4.12), no release candidate |
| FFI extension | the runner loads API Maker with it : the image of the sandboxes gets it when it is built, and is not built without it |
main |
function main(AmG $g) (the type is optional : function main($g) runs as well), or code which returns a function |
| Answer | what main returns; null lets the API go on |
Calls of $g->sys |
typed : named arguments, each with its type (getAll('mysql_8', 'inventory', 'customers', limit: 10)), and what a call answers has its type (a list of AmObject, ?AmObject, int, UpdateManyResult) |
| Values | objects of JSON are AmObjects : $x->name and $x['name'] are the same, a key which is not there is null |
| Packages | Packagist through Composer : guzzlehttp/guzzle ^7.9 |
| Printing | echo, print_r, var_dump go to the console of the account |
The request and the answer¶
| Read the request, shape the answer | |
|---|---|
$g->req(anAmRequest) and$g->res(anAmResponse) areAmObjects whose keys are documented properties, so an editor of PHP completes them : read and change them with->or[],foreachover them,->toArray()for plain arrays.- What you change of
$g->reqin a pre hook is what the API sees.
Bytes as the answer¶
A file, a picture, a PDF : return its bytes in an AmBytes, or put that in $g->res->output, and name their kind of content. A text of PHP holds bytes as well as letters : AmBytes says that these are bytes. The answer is those bytes as they are, whatever the kind of content, with the headers of the code.
| A picture as the answer | |
|---|---|
Calling API Maker : $g->sys¶
Each method of $g->sys->db, $g->sys->system and $g->sys->cache is a call of the server, with its types. It takes named arguments, each with its type : what a call is about comes first and needs no name (the instance, the database, the table), the rest is given by name. What it answers has its type : rows are a list of AmObject, a row an AmObject or null, a count an int, what an API describes a class whose keys are documented properties ($changed->updatedRowsCount). A name no call has is an error of PHP (Unknown named parameter) : a key is never sent under a wrong name.
Every request runs in a fiber : while one waits for the server, the other requests of the runner go on.
Rows as they arrive¶
| Sum a large table | |
|---|---|
System APIs and cache¶
Errors¶
| Refuse a request | |
|---|---|
- Any other exception answers
500with its class, message and the lines of your code (InvalidArgumentException: x is 1, thenat orders.php(7)). - A warning is an error of the code :
Warning: Undefined array key "qty". Notices and deprecations are not.
Logs, printing and shared values¶
Hooks¶
| Pre hook : only my rows | |
|---|---|
| Post hook : hide a column | |
|---|---|
Your own functions and classes¶
Each piece of code is compiled in a namespace of its own : functions and classes of the same name in two custom APIs (or in two versions of one) never meet. Classes of PHP and of your packages are found as usual, catch (Exception $e) included.
Utility classes¶
What a utility class written in PHP declares (classes, functions, constants) is there for the other PHP code of the account, by its name.
<?php
const TAX_RATE = 0.2;
class Tax
{
public static function add($amount)
{
return cents($amount * (1 + TAX_RATE)); // cents() : a function of another utility class
}
}
<?php
function main(AmG $g)
{
return ['total' => Tax::add($g->req->body->amount), 'rate' => TAX_RATE];
}
- A utility class has no
namespaceof its own : API Maker loads each class in a namespace it chooses and imports what the code names (Tax,cents,TAX_RATE), so a class which was saved again is loaded next to the one it was. Name things without a leading\. - The file of a class is run once per sandbox.
- In the Git repository the class is
src/utility_classes/<folder>---<name>/<name>.php, andcomposer.jsonloads those files (autoload.files).
Test cases¶
A test case written in PHP has a function per test : every function whose name starts with test_. What it throws is its failure.
<?php
function test_the_cost_of_a_parcel(AmG $g)
{
$g->sys->test->mock('db.getAll', when: ['collection' => 'rates'], returns: [['per_kg' => 2.5]]);
$out = $g->sys->test->runCustomApi('shippingCost', body: ['weight' => 4]);
if ($out->cost != 10) {
throw new Exception("the cost is {$out->cost}");
}
}
$g->sys->test:runCustomApi($name, version: …, body: …, query: …, params: …, headers: …),runCustomApiFull(…)for the whole answer (data,statusCode,headers,logs),mock($method, when: …, returns: …),mocks([new Mock(…), …]),clearMocks($method = null),calls($method = null).- A test case has no
namespaceof its own. The custom API under test can be written in any language.
Packages¶
Add guzzlehttp/guzzle in Sandbox Settings, then :
| A package of Packagist | |
|---|---|
- Composer and what it unpacks with come into the image only when you add a package.
- Versions are constraints of Composer :
7.9.2,^7.9,~2.0 || ^3.0.
In the code editor¶
The editor completes $g as PHP writes it, the names of your instances, databases and tables, and the columns of a table on the rows a call answers. More in the code editor in every language.
- Inside the brackets of a call the editor offers its named arguments, each with its type and what it is for (
limit:,find:,select:…) ; insidefind: ['it offers the columns of the table, insideselect: 'andsort: 'too. The same for the classes of what a call takes :new GetAll(. - What a call answers has its type : the columns are offered after
$rows[0]->,$row->,$row['and$row->get(', in aforeachand in the function given togetAllByStream;$changed->(anupdateMany) offersupdatedRowsCount,$saved->offersrow,rowsandisList, the whole answer of$g->sys->db->fullofferssuccess,data,errors… - The table is the one the call names : in the call, in a variable (
$table + [...]), in named arguments (getById(instance: 'mysql_8', ...)) or in a function which returns the query. $g->req->and$g->res->offer the keys of the request and of the answer,$g->req->auth->the opened tokens.
Reference¶
$g-> |
PHP |
|---|---|
req, res, shared |
AmRequest, AmResponse, AmObject : each an AmObject (->, [], foreach, ->get($key, $default), ->toArray()) |
sys->db |
getAll, getById, query, saveSingleOrMultiple, masterSave, arrayOperations, updateById, updateMany, replaceById, removeById, removeByQuery, aggregate, count, distinct, distinctQuery, getAllByStream, queryByStream; sys->db->gen->getAllGen… |
sys->db->full, sys->db->gen->full, sys->system->full, sys->cache->full |
the same calls, each with its whole answer : ApiResponse (success, statusCode, data, totalCount, errors, warnings) |
sys->system |
encrypt, encryptWith, decrypt, decryptWith, hash, getSecret, getSecrets, callExternalApi, callExternalApis, getToken, getTokens, executeQuery, getTableMeta, getTableMetas, createIndexes, dropIndexes, dropIndexesOfMany, getIndexes, getIndexesOfMany, emitEvent, emitEventWS, isValidDataForTable(s), isValidDataForCustomAPI(s), multiTenantInstance(s)Updated, isValidConnectionString(s), getLock, releaseLock |
sys->cache |
getKey, getKeys, setKey, setKeys, removeKey, removeKeys, resetCacheDB, resetCacheDBs, resetCacheCustomApis, resetCacheCustomApisOfTenant, resetCacheSystemApis, resetCacheSystemApisOfTenant |
sys->call('db.getAll', …) |
any API by its name in JavaScript, with its arguments as they are |
| what a call takes | GetAll, GetById, Save, Query, DeepItem, GetToken, SetKey, Mock… : classes made with the same named arguments (new GetAll('mysql_8', 'inventory', 'customers', limit: 10)), given in place of them and to the calls of several |
logger |
log, debug, info, warn, error |
| errors | throw new AmErrors($message, $code) or new AmErrors([[...], ...]); ->getCode(), ->getMessage(), ->errors (the list, each a ResponseError) |
Method names may be written with underscores too (get_by_id).
Debugging¶
- The debug runner of PHP loads Xdebug, which connects to the server for each request : the Debug tab of the API testing page takes it while the page is open, else the code runs on.
- Write variables as PHP does in a condition, a watch or a logpoint (
$total > 100,{$cart->name}). - Learn more Debugging.