{"id":20278535,"url":"https://github.com/jeremiah-shaulov/deno_php_world","last_synced_at":"2025-09-22T14:30:57.798Z","repository":{"id":62421944,"uuid":"340813879","full_name":"jeremiah-shaulov/deno_php_world","owner":"jeremiah-shaulov","description":"Extend Deno world with PHP, by running commandline PHP interpreter in the background, to use both languages in single application.","archived":false,"fork":false,"pushed_at":"2025-01-06T07:03:40.000Z","size":287,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-06T08:18:59.824Z","etag":null,"topics":["deno","php","polyglot"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jeremiah-shaulov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-21T04:06:32.000Z","updated_at":"2025-01-06T07:03:20.000Z","dependencies_parsed_at":"2024-01-02T19:39:25.307Z","dependency_job_id":"480d7f29-8442-4907-a364-d4147a3c5d93","html_url":"https://github.com/jeremiah-shaulov/deno_php_world","commit_stats":null,"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremiah-shaulov%2Fdeno_php_world","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremiah-shaulov%2Fdeno_php_world/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremiah-shaulov%2Fdeno_php_world/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeremiah-shaulov%2Fdeno_php_world/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeremiah-shaulov","download_url":"https://codeload.github.com/jeremiah-shaulov/deno_php_world/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233856689,"owners_count":18740989,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deno","php","polyglot"],"created_at":"2024-11-14T13:23:55.545Z","updated_at":"2025-09-22T14:30:57.683Z","avatar_url":"https://github.com/jeremiah-shaulov.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# php_world\n\nThis module extends Deno world with PHP, by running command-line PHP interpreter in background, or by connecting to a PHP-FPM service.\n\nThere are several possible reasons to use `php_world`:\n\n1. If you have a large PHP application, and you wish to convert it to Javascript/Typescript, but it's impossible to achieve at once. In this case `php_world` allows you to start writing new code in Javascript/Typescript, and convert each part of the application later, as desired.\n2. If you want to benefit from PHP functionality or third-party PHP libraries/SDKs or database drivers.\n\n## Requirements\n\nPHP-CLI or PHP-FPM at least version 8.0 must be installed on your system.\n\n## Examples\n\n### Usage\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n// ...\n// and at last, terminate the interpreter\nawait g.exit();\n```\n\nRun the script as follows:\n\n```bash\ndeno run --unstable --allow-net --allow-run=php main.ts\n```\n\nBy default `php_world` will execute `php` CLI command.\nIf in your system PHP appears under different name, you need to set `settings.php_cli_name` before accessing `php_world` interfaces.\nIf you wish to use PHP-FPM instead, set `settings.php_fpm.listen`.\n\n```ts\nimport {g, c, settings} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nsettings.php_cli_name = 'php7.4';\n// now access php_world interfaces\n// ...\n// and at last, terminate the interpreter\nawait g.exit();\n```\nThere are several configurable settings:\n\n1. `settings.php_cli_name` - PHP-CLI command name (default `php`). To use command name with arguments, assign array of strings, like `['docker', 'exec', '-i', 'daddy_cool', 'php']`.\n2. `settings.unix_socket_name` - `php_world` uses socket channel to communicate with the remote interpreter. By default it uses random (free) TCP port. On non-Windows systems you can use unix-domain socket. Set `settings.unix_socket_name` to full path of socket node file, where it will be created.\n3. `settings.stdout` - Allows to redirect PHP process echo output (see below).\n4. `settings.php_fpm.listen` - If set, `php_world` will use PHP-FPM service, not CLI. Set this to what appears in your PHP-FPM pool configuration file (see line that contains `listen = ...`).\n5. `settings.php_fpm.*` - There are some more PHP-FPM related settings that will be explained below.\n6. `settings.init_php_file` - Path to PHP script file. If specified, will `chdir()` to it's directory, and execute this script as part of initialization process.\n7. `settings.interpreter_script` - Use manually installed interpreter script (by default will use embedded one).\n7. `onsymbol` - Callback that resolves Deno world entities, that can be accessed from PHP.\n\n### Interface\n\n`php_world` library exports the following symbols:\n\n1. `PhpInterpreter` - Constructor for new PHP interpreter to run in the background.\n2. `php` - Default interpreter (created with `new PhpInterpreter`).\n3. `g` - The same as `php.g`. Contains all the PHP functions, global constants and variables.\n4. `c` - The same as `php.c`. Contains classes.\n5. `settings` - The same as `php.settings`. Allows to modify interpreter settings.\n6. `InterpreterError` - Class for exceptions propagated from PHP.\n7. `InterpreterExitError` - This error is thrown in case PHP interpreter exits or crashes.\n8. `start_proxy` - Function that creates FastCGI proxy node between Web server and PHP-FPM, where PHP script can access Deno environment, and vise versa.\n\n### Calling functions\n\nEach function becomes async, because calling it involves IPC (interprocess communication) with the background PHP interpreter.\n\n```ts\nimport {g} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nconst {eval: php_eval, phpversion, class_exists, exit} = g;\n\nconsole.log(await phpversion());\nawait php_eval('class Hello {}');\nconsole.log(await class_exists('Hello'));\nawait exit();\n```\n\nIt's important to call `exit()` at the end of Deno script. This function terminates the interpreter, and frees all the resources. After this function called, `php_world` can be used again, and a new interpreter instance will be spawned. It's OK to call `exit()` several times.\n\n### Global constants\n\nConstant's value must be awaited-for.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nconsole.log(await g.PHP_VERSION);\nconsole.log((await g.FAKE) === undefined); // unexisting constants have \"undefined\" value\n```\n\n### Global variables\n\nLike constants, variables are present in the `g` namespace, but their names begin with '$'.\n\nVariable's value must be awaited-for. But setting new value returns immediately (and doesn't imply synchronous operations - the value will be set in the background, and there's no result that we need to await for).\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nconsole.log((await g.$ten) === undefined); // unexisting variables have \"undefined\" value\ng.$ten = 10;\nconsole.log(await g.$ten);\n```\n\nIndividual keys can be accessed.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\ng.$_SERVER['hello']['world'] = true;\nconsole.log(await g.$_SERVER['hello']);\n```\n\nIt's possible to unset a key.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nconsole.log(await g.$_SERVER['argc']); // likely to print '1'\ndelete g.$_SERVER['argc'];\nconsole.log((await g.$_SERVER['argc']) === undefined); // prints \"true\"\n```\n\n### Classes\n\nClasses are present in the `c` namespace.\n\n### Class-static constants\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nconst {eval: php_eval} = g;\nconst {Value} = c;\n\nawait php_eval('class Value {const TEN = 10;}');\nconsole.log((await Value.NINE) === undefined); // unexisting constants have \"undefined\" value\nconsole.log(await Value.TEN);\n```\n\n### Class-static variables\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nconst {eval: php_eval} = g;\nconst {Value} = c;\n\nawait php_eval('class Value {static $ten = 10;}');\nconsole.log((await Value.$nine) === undefined); // unexisting variables have \"undefined\" value\nconsole.log(await Value.$ten);\n```\n\n### Class-static methods\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nconst {eval: php_eval} = g;\nconst {Value} = c;\n\nawait php_eval\n(\t`\tclass Value\n\t\t{\tstatic function get_ten()\n\t\t\t{\treturn 10;\n\t\t\t}\n\t\t}\n\t`\n);\nconsole.log(await Value.get_ten());\n```\n\n### Class construction and destruction\n\nTo create a class instance, call class constructor, and await for the result. It returns handle to remote PHP object.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nconst {eval: php_eval} = g;\nconst {Value} = c;\n\nawait php_eval('class Value {}');\nlet value = await new Value;\n```\n\nEach instance created with `new`, must be destroyed with `Symbol.asyncDispose` or `Symbol.dispose` (delayed).\n\n```ts\nvalue[Symbol.dispose]();\n```\n\nOr bind the created object to a \"using\" variable.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nconst {eval: php_eval} = g;\nconst {Value} = c;\n\nawait php_eval('class Value {}');\nusing value = await new Value;\n```\n\nFor debugging purposes it's possible to query number of currently allocated objects. This number must reach 0 at the end of the script.\n\n```ts\nimport {g, c, php} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nconsole.log(await php.n_objects()); // prints 0\n{\tusing obj = await new c.Exception('Test');\n\tconsole.log(await php.n_objects()); // prints 1\n}\nconsole.log(await php.n_objects()); // prints 0\nawait php.g.exit();\n```\n\n### Instance variables\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nconst {eval: php_eval} = g;\nconst {Value} = c;\n\n{\tawait php_eval('class Value {public $ten;}');\n\tusing value = await new Value;\n\tvalue.ten = 10;\n\tconsole.log(await value.ten);\n}\n\nawait php.g.exit();\n```\n\n### Instance methods\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nconst {eval: php_eval} = g;\nconst {Value} = c;\n\nawait php_eval\n(\t`\tclass Value\n\t\t{\tpublic $var;\n\n\t\t\tfunction get_twice_var()\n\t\t\t{\treturn $this-\u003evar * 2;\n\t\t\t}\n\t\t}\n\t`\n);\nusing value = await new Value;\nvalue.var = 10;\nconsole.log(await value.get_twice_var());\n```\n\n### Objects returned from functions\n\nWhen a function is called, and returned a value, this value is JSON-serialized on PHP side, and JSON-parsed in the Deno world.\nObjects returned from functions are dumb default objects, without methods.\n\nHowever it's possible to get object handle as in example with instance construction. To do so you need to get special property called `this` from the object, before awaiting for the result.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\n{\tawait g.eval\n\t(\t`\tfunction get_ex($msg)\n\t\t\t{\treturn new Exception($msg);\n\t\t\t}\n\t\t`\n\t);\n\n\tusing ex = await g.get_ex('The message').this;\n\tconsole.log(await ex.getMessage()); // prints 'The message'\n}\n\nawait php.g.exit();\n```\n\nAt last, the object must be disposed (with `Symbol.asyncDispose` or `Symbol.dispose`). This doesn't necessarily destroys the object on PHP side, but it stops holding a reference to the object.\n\n### Get variables as objects\n\nIn the same fashion, it's possible to get object-handle to a variable.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\n{\tawait g.eval\n\t(\t`\tglobal $e;\n\t\t\t$e = new Exception('The message');\n\t\t`\n\t);\n\n\tusing ex = await g.$e.this;\n\tconsole.log(await ex.getMessage()); // prints 'The message'\n}\n\nawait php.g.exit();\n```\n\n### Objects behavior\n\nRemote PHP objects are represented in Deno as opaque `Proxy` objects, and they don't feel like real Typescript objects. Most of magic behavior is missing. For example they don't convert to strings automatically (because `toString()` magic method is synchronous). Only the following object features work:\n\n1. Getting, setting and deleting properties.\n2. `instanceof` operator.\n3. Async iterators.\n\nExample:\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\n{\tusing obj = await new c.ArrayObject(['a', 'b', 'c']);\n\tconsole.log(obj instanceof c.ArrayObject); // prints \"true\"\n\tfor await (const item of obj)\n\t{\tconsole.log(item);\n\t}\n}\n\nawait php.g.exit();\n```\n\n### Namespaces\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\n{\tawait g.eval\n\t(\t`\tnamespace MainNs;\n\n\t\t\tfunction get_twice($value)\n\t\t\t{\treturn $value * 2;\n\t\t\t}\n\n\t\t\tclass Value\n\t\t\t{\tpublic $var;\n\n\t\t\t\tfunction get_triple_var()\n\t\t\t\t{\treturn $this-\u003evar * 3;\n\t\t\t\t}\n\t\t\t}\n\t\t`\n\t);\n\n\tconsole.log(await g.MainNs.get_twice(10));\n\n\tusing value = await new c.MainNs.Value;\n\tvalue.var = 10;\n\tconsole.log(await value.get_triple_var());\n}\n\nawait php.g.exit();\n```\n\n### Accessing Deno world from PHP\n\nWhen you pass an object from Deno to PHP, and this object is not a plain `Object` or `Array` (`obj.constructor!=Object \u0026\u0026 obj.constructor!=Array`), a handler to remote Deno object is created on PHP side.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nclass FirstClass\n{\tget_value()\n\t{\treturn 'the value';\n\t}\n}\n\ng.$first_class = new FirstClass;\n\nawait g.eval\n(\t`\tglobal $first_class;\n\n\t\tvar_dump($first_class-\u003eget_value()); // prints: string(9) \"the value\"\n\t`\n);\nawait g.exit();\n```\n\nAlso on PHP side 2 global variables get automatically defined at the beginning of the script: `$globalThis` and `$window`. They are identical, so you can use whatever you prefer.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait g.eval\n(\t`\tglobal $window;\n\n\t\tvar_dump($window-\u003eparseInt('123px'));\n\n\t\tvar_dump($window-\u003eMath-\u003epow(10, 3));\n\n\t\tvar_dump($window-\u003eDeno-\u003epid);\n\n\t\tvar_dump($window-\u003eeval('Deno.pid'));\n\t`\n);\nawait g.exit();\n```\n\nWhen accessing async values, they're automatically awaited.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait g.eval\n(\t`\tglobal $window;\n\n\t\tvar_dump($window-\u003efetch('http://example.com/')-\u003etext());\n\t`\n);\nawait g.exit();\n```\n\nJavascript functions and classes are not distinguishable entities (functions can be used as classes). They both can be referred to from PHP through `DenoWorld` namespace.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait g.eval\n(\t`\tglobal $keys_func;\n\n\t\tuse DenoWorld\\\\Map;\n\n\t\t$m = new Map;\n\t\t$m-\u003eset('k1', 'v1');\n\t\t$m-\u003eset('k2', 'v2');\n\n\t\tvar_dump($m-\u003esize);\n\t\tvar_dump(count($m));\n\t\tvar_dump(iterator_to_array($m-\u003ekeys()));\n\t\t$keys_func = $m-\u003ekeys-\u003ebind($m);\n\t`\n);\nlet keys_func = await g.$keys_func;\nconsole.log([...keys_func()]);\nawait g.exit();\n```\n\nSome class names are invalid in PHP, and cause errors. Classes called \"Array\" and \"Object\" are such.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\ntry\n{\tawait g.eval\n\t(\t`\tvar_dump(new DenoWorld\\\\Array('a', 'b', 'c'));\n\t\t`\n\t);\n}\ncatch (e)\n{\tconsole.error(e); // Error: syntax error, unexpected 'Array' (T_ARRAY), expecting identifier (T_STRING)\n}\n\nawait g.exit();\n```\n\nBut you can rename them.\n\n```ts\nimport {g, c} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait g.eval\n(\t`\tglobal $window;\n\n\t\t$window-\u003eArr = $window-\u003eArray;\n\t\t$a = new DenoWorld\\\\Arr('a', 'b', 'c');\n\t\tvar_dump($a);\n\t\t$a-\u003esplice(1, 1, 'B', 'B');\n\t\tvar_dump($a);\n\t\tvar_dump($window-\u003eJSON-\u003estringify($a));\n\t`\n);\n\nawait g.exit();\n```\n\nThe following object features are supported:\n\n1. Getting and setting properties. They can be accessed as `$obj-\u003eprop` or `$obj['prop']`.\n2. `isset($obj-\u003eprop)` and `unset($obj-\u003eprop)`.\n3. Calling object methods. And calling objects as functions (like `$window-\u003eNumber('123')`), if this makes sense.\n4. When converting a Deno-world object to string, it's `toString()` will be called on Deno side.\n5. `foreach` iteration. If Deno object has `Symbol.iterator` or `Symbol.asyncIterator`, they will be used. Otherwise object properties will be iterated (as usual in PHP).\n6. If Deno object has property called `length`, or `size`, `count($obj)` will return it's value.\n\nIf a requested Deno class doesn't exist, you can handle this situation, and maybe load it before accessing.\n\n```ts\nimport {g, c, settings} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nsettings.onsymbol = name =\u003e\n{\tif (name == 'Scientific')\n\t{\tclass Scientific\n\t\t{\tconstructor(public n=0)\n\t\t\t{\n\t\t\t}\n\n\t\t\ttwice()\n\t\t\t{\treturn this.n*2;\n\t\t\t}\n\t\t}\n\t\treturn Scientific;\n\t}\n};\n\nawait g.eval\n(\t`\tuse DenoWorld\\\\Scientific;\n\n\t\t$obj = new Scientific(10);\n\t\tvar_dump($obj-\u003etwice());\n\t`\n);\n\nawait g.exit();\n```\n\nTo access toplevel functions that are not in `globalThis`, but must be handled by `onsymbol()`, you can call them as static functions of `DenoWorld` class.\n\n```ts\nimport {g, c, settings} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nsettings.onsymbol = name =\u003e\n{\tif (name == 'hello')\n\t{\tfunction hello()\n\t\t{\treturn 'hello';\n\t\t}\n\t\treturn hello;\n\t}\n};\n\nawait g.eval\n(\t`\tvar_dump(DenoWorld::hello());\n\t`\n);\n\nawait g.exit();\n```\n\nIf a Deno object has method called `dispose()`, it will be called once this object becomes not in use on PHP side.\n\n```ts\nimport {php} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nclass MyFile\n{\tprotected fh: Deno.File | undefined;\n\tprivate buffer = new Uint8Array(8*1024);\n\n\tstatic async open(path: string, options?: Deno.OpenOptions)\n\t{\tlet self = new MyFile;\n\t\tself.fh = await Deno.open(path, options);\n\t\treturn self;\n\t}\n\n\tdispose()\n\t{\tthis.fh?.close();\n\t}\n\n\tasync read()\n\t{\tlet n = await this.fh?.read(this.buffer);\n\t\treturn n==null ? null : new TextDecoder().decode(this.buffer.subarray(0, n));\n\t}\n}\n\nphp.settings.onsymbol = name =\u003e\n{\tswitch (name)\n\t{\tcase 'MyFile': return MyFile;\n\t}\n};\n\nphp.g.eval\n(\t`\t$f = DenoWorld\\\\MyFile::open('/etc/passwd');\n\t\twhile (($chunk = $f-\u003eread()) !== null)\n\t\t{\techo $chunk;\n\t\t}\n\t`\n);\n```\n\nThird and the last PHP global variable that this library defines is called `$php`. It contains reference to current PHP interpreter on Deno side (instance of `PhpInterpreter`).\nYou can pass it to Deno functions, if they want to use current PHP interpreter that called them.\n\n```ts\nimport {g, c, settings, PhpInterpreter} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nsettings.onsymbol = name =\u003e\n{\tif (name == 'get_rating')\n\t{\treturn get_rating;\n\t}\n};\n\nasync function get_rating(php: PhpInterpreter)\n{\treturn await php.g.str_repeat('*', await php.g.$cur_rating);\n}\n\nawait g.eval\n(\t`\tglobal $php, $cur_rating;\n\n\t\t$cur_rating = 5;\n\n\t\tvar_dump(DenoWorld::get_rating($php));\n\t`\n);\n\nawait g.exit();\n```\n\nFor informational purposes there's function that returns number of deno objects, that PHP-side currently holds.\nInitially there're 2: $php and $globalThis ($window === $globalThis).\nAs you request Deno objects, this number will grow, and once you free references, this number will be decreased.\n\n```ts\nimport {g, php} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nconsole.log(await php.n_deno_objects()); // prints 2\nawait g.eval\n(\t`\tglobal $window, $var;\n\t\t$var = $window-\u003eDeno;\n\t`\n);\nconsole.log(await php.n_deno_objects()); // prints 3\nawait g.eval\n(\t`\tglobal $window, $var2;\n\t\t$var2 = new DenoWorld\\\\Map;\n\t`\n);\nconsole.log(await php.n_deno_objects()); // prints 4\nawait g.eval\n(\t`\tglobal $var, $var2;\n\t\t$var = null;\n\t\t$var2 = null;\n\t`\n);\nconsole.log(await php.n_deno_objects()); // prints 2\nawait g.eval\n(\t`\tglobal $php, $window, $globalThis;\n\t\t$php = null;\n\t\t$window = null;\n\t\t$globalThis = null;\n\t`\n);\nconsole.log(await php.n_deno_objects()); // prints 0\n\nawait g.exit();\n```\n\n### Execution flow and exceptions\n\nWhen you call PHP functions, if function's result is not awaited-for, the function will work in background. You can continue calling functions, and they all will be executed in the same sequence they requested. If a function threw exception, all subsequent operations will be skipped till the end of current microtask iteration.\n\n```ts\nimport {g, c, php} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait g.eval\n(\t`\tfunction failure($msg)\n\t\t{\tglobal $n;\n\t\t\t$n++;\n\t\t\tthrow new Exception($msg);\n\t\t}\n\t`\n);\n\ng.failure('Test 1'); // $n gets the value of 1\ng.failure('Test 2'); // this will no be executed, so $n will remain 1\ng.failure('Test 3'); // not executed\ntry\n{\t// await for anything will throw exception\n\t// we can use php.ready() to just await for all pending operations\n\tawait php.ready();\n}\ncatch (e)\n{\tconsole.log(e.message); // prints 'Test 1'\n}\nconsole.log(await g.$n); // prints 1\n```\n\nIf you don't await any operation within current microtask iteration, the exception will be lost.\n\n```ts\nimport {g, c, php} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait g.eval\n(\t`\tfunction failure($msg)\n\t\t{\tglobal $n;\n\t\t\t$n++;\n\t\t\tthrow new Exception($msg);\n\t\t}\n\t`\n);\n\ng.failure('Test 1'); // $n gets the value of 1\nqueueMicrotask\n(\tasync () =\u003e\n\t{\tg.failure('Test 2'); // $n gets the value of 2\n\t\tg.failure('Test 3'); // this will no be executed, so $n remains 2\n\t\ttry\n\t\t{\tawait php.ready(); // throws error 'Test 2'\n\t\t}\n\t\tcatch (e)\n\t\t{\tconsole.log(e.message); // prints 'Test 2'\n\t\t}\n\t\tconsole.log(await g.$n); // prints 2\n\t}\n);\n```\n\nPHP exceptions are propagated to Deno as instances of InterpreterError class.\n\n```ts\nimport {g, c, InterpreterError} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait g.eval\n(\t`\tfunction failure($msg)\n\t\t{\tthrow new Exception($msg);\n\t\t}\n\t`\n);\n\ntry\n{\tawait g.failure('Test');\n}\ncatch (e)\n{\tconsole.log(e instanceof InterpreterError);\n\tconsole.log(e.message);\n}\n```\n\nInterpreterError has the following fields: `message`, `fileName`, `lineNumber`, `phpStack` (string).\nAlso `stack` field is modified to contain traces from PHP.\n\nIf PHP interpreter exits (not as result of calling `g.exit()`), `InterpreterExitError` exception is thrown.\n\n```ts\nimport {g, InterpreterExitError} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\ntry\n{\tawait g.eval('exit(100);');\n}\ncatch (e)\n{\tif (e instanceof InterpreterExitError)\n\t{\tconsole.log(`PHP exited with code ${e.code}`);\n\t}\n}\n```\n\nThe InterpreterExitError class has the following fields: `message`, `code` (process exit status code).\n\n### Running several PHP interpreters in parallel\n\nExported `php` symbol is a default instance of `PhpInterpreter` class that created by calling `export const php = new PhpInterpreter` inside the library. `PhpInterpreter` class allows you to run more instances of PHP interpreter, either PHP-CLI, or PHP-FPM.\n\n```ts\nimport {g, c, PhpInterpreter} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait using int_1 = new PhpInterpreter; // binding to \"await using\" calls `int_1.g.exit()` automatically at the end of the block\nawait using int_2 = new PhpInterpreter;\n\nlet pid_0 = await g.posix_getpid();\nlet pid_1 = await int_1.g.posix_getpid();\nlet pid_2 = await int_2.g.posix_getpid();\n\nconsole.log(`${pid_0}, ${pid_1}, ${pid_2}`);\n\nawait g.exit();\n```\n\n### Limitations of PHP-CLI\n\nUsing PHP-CLI backend is simple, but there are disadvantages.\n\nIf some PHP script file declares a function, or some other kind of object, such file cannot be included (or required) multiple times. PHP complains on \"Cannot redeclare function\". Practically this means that to execute the same script multiple times, new PHP interpreters must be spawned. Respawning process is slow, and you will not benefit from opcache.\n\nHowever, it's possible to reorganize the application in such a way, that script files you run directly don't declare objects, but call `require_once()` for files that do declare them.\n\nAnother disadvantage is that functions like `header()` and `setcookie()` do nothing in PHP-CLI.\n\n### Using PHP-FPM\n\nTo use PHP-FPM backend (that must be installed on your system), set `settings.php_fpm.listen` to PHP-FPM service address. You can find it in your PHP-FPM pool configuration file.\n\nTo get started you can create a new pool file like this (substitute `username` with the user from which you run your deno script):\n\n```ini\n[username]\nuser = username\ngroup = username\nlisten = [::1]:8989\n\n; if \"listen\" is unix-domain socket, set also the following:\n;listen.owner = username\n;listen.group = username\n\npm = dynamic\npm.max_children = 5\npm.start_servers = 2\npm.min_spare_servers = 1\npm.max_spare_servers = 3\n```\n\n```ts\nimport {g, c, php, settings} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nsettings.php_fpm.listen = '[::1]:8989';\nconsole.log(await g.php_sapi_name());\nawait g.exit(); // in case of PHP-FPM, g.exit() doesn't actually call exit() on PHP side, but it terminates a FCGI request\nphp.close_idle(); // close idle connection to PHP-FPM (otherwise deno script will not exit immediately)\n```\n\nCommon problems:\n\n1. If using unix-domain socket for PHP-FPM service, it must be accessible by deno script. In PHP-FPM pool configuration one of `listen.owner` or `listen.group` must be set to deno script user.\n2. If using unix-domain socket for communication with PHP world (`settings.unix_socket_name`), it must be accessible by PHP interpreter. One of `user` or `group` must be set to deno script user.\n\nIf `settings.stdout` is set to `inherit` (default value), echo output, together with headers set with `header()` or `setcookie()` can be taken as `Response` object.\n\n```ts\nimport {g, c, php, settings} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nsettings.php_fpm.listen = '[::1]:8989';\n\nphp.g.echo(`Hello`);\n\nawait php.g.exit();\nphp.close_idle();\n```\n\nEach `PhpInterpreter` instance (including the default one, that was used in the example above) establishes connection with PHP-FPM service by making a FastCGI request to it.\nPHP script will run till you call `g.exit()`. In case of PHP-FPM `g.exit()` works specially: it doesn't call `exit()` on PHP side, but it terminates the FastCGI request.\nPHP `echo` output will be received as FastCGI response. The response can start arriving before you call `g.exit()` - usually it happens after echoing some portion of output.\n\nThe response can be caught and examined in a callback function set to `settings.php_fpm.onresponse`.\nThis callback will be called when headers and the first portion of body were received.\nThe callback will get `ResponseWithCookies` object that is subclass of `Response` (that built-in `fetch()` returns).\nThis object will contain headers and body reader, that you can use to read everything echoed from the script.\n\nIf you want to read the response body in the callback, you need not return till you read all the response body. After returning from the callback, the response can be destroyed (it will be destroyed if you called `g.exit()` earlier).\n\nThe body can be read in regular way, as you do with `fetch()`.\n\n```ts\nimport {g, c, php, settings} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nimport {readAll} from 'https://deno.land/std@0.203.0/streams/mod.ts';\n\nsettings.php_fpm.listen = '/run/php/php-fpm.jeremiah.sock';\nsettings.php_fpm.onresponse = async response =\u003e\n{\tconsole.log(response.headers);\n\tif (response.body)\n\t{\tlet body = await readAll(response.body);\n\t\tconsole.log('BODY: ' + new TextDecoder().decode(body));\n\t}\n};\n\nawait g.eval\n(\t`\theader('X-Hello: All');\n\t\techo \"Response body\";\n\t`\n);\n\nconsole.log('Essentially this is it');\nawait g.exit();\nconsole.log('Exited');\nphp.close_idle();\n```\n\nBy default this library reuses connections to PHP-FPM. This can be controlled by adjusting `settings.php_fpm.keep_alive_timeout`.\nThis number of milliseconds each connection will remain idle after the request, so Deno script would not exit naturally if you don't call `php.close_idle()`.\n\n### Creating FastCGI proxy\n\nIf you have Apache (or Nginx) + PHP-FPM setup, you can create Deno node in the middle, so Apache will connect to your Deno application, and it will proxy the request further to PHP-FPM.\nAnd by default this will work as there was no Deno at all. But PHP scrips will be able to access Deno world, and vise versa.\n\n```ts\nimport {start_proxy, PhpRequest} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nconsole.log(`Server started`);\n\nlet proxy = start_proxy\n(\t{\tfrontend_listen: '/tmp/jeremiah.sock',\n\t\tbackend_listen: '/run/php/php-fpm.jeremiah.sock',\n\t\tmax_conns: 128,\n\t\tkeep_alive_timeout: 10_000,\n\t\tkeep_alive_max: Number.MAX_SAFE_INTEGER,\n\t\tunix_socket_name: '',\n\t\tmax_name_length: 256,\n\t\tmax_value_length: 4*1024, // \"HTTP_COOKIE\" param can have this length\n\t\tmax_file_size: 10*1024*1024, // is respected by `php.request.post.parse()`\n\t\tasync onrequest(php: PhpRequest)\n\t\t{\t// Log incoming request\n\t\t\tconsole.log(php.request.url);\n\n\t\t\t// Register Deno-world symbol resolver\n\t\t\tphp.settings.onsymbol = name =\u003e\n\t\t\t{\tswitch (name)\n\t\t\t\t{\t// ...\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// If .php file, forward the request to PHP-FPM\n\t\t\tif (php.script_filename.endsWith('.php'))\n\t\t\t{\tawait php.proxy(); // PHP gets this request, and sends the response to client\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If other kind of file, handle it, or just ignore to return 404\n\t\t\tif (php.request.url.startsWith('/page-1.html'))\n\t\t\t{\t// If we want to access POST parameters and uploaded files, we need to call `parse()` (otherwise request.post will contain nothing)\n\t\t\t\tawait php.request.post.parse();\n\n\t\t\t\t// Generate the response\n\t\t\t\tphp.request.responseHeaders.set('content-type', 'text/html');\n\t\t\t\tawait php.request.respond({status: 200, body: 'Page 1'});\n\t\t\t}\n\t\t},\n\t\tonerror(error: Error)\n\t\t{\n\t\t}\n\t}\n);\n```\n\nFor each incoming request `onrequest()` will be called, where you can do one of 3 things:\n1. call `await php.proxy()` to forward the request to backend PHP-FPM\n2. Handle the request manually\n3. Do nothing (without awaiting), to let the library generate 404 response\n\nThe `onrequest()` callback gets 1 argument of type `PhpRequest` that extends `PhpInterpreter`. It has 2 extra fields:\n1. `script_filename: string` - requested script file. It's the same as `this.request.params.get('SCRIPT_FILENAME')`, but cannot be undefined.\n2. `request: ServerRequest` - contains information about incoming request: it's headers, GET and POST parameters, cookies and uploaded files.\n\nTo handle incoming request, you need to call `await request.respond()` with optional `status: number`, `headers: Headers`, `setCookies: SetCookies` and `body: Uint8Array | ReadableStream\u003cUint8Array\u003e | string`.\n\nFor more information on `ServerRequest` object see [x/fcgi](https://deno.land/x/fcgi) library.\n\n`start_proxy()` returns handle, that has `addr: Deno.Addr` of the frontend listener, and method `stop()` that will terminate the proxy. `stop()` returns promise that will be fullfilled after all the requests are completed.\n\n### Dealing with PHP echo output\n\nThere's setting that provides control on how PHP output is processed: `settings.stdout`.\n\n```ts\nstdout: 'inherit'|'piped'|'null' = 'inherit'\n```\nIt's default value is `inherit`. For PHP-CLI this value means to pass PHP output to Deno. So `g.echo(\"msg\\n\")` works like `console.log(\"msg\")`.\n\nAs usual, it's possible to use PHP output buffering to catch the output.\n\n```ts\nimport {g} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\ng.ob_start();\ng.echo(\"A\");\ng.echo(\"B\");\ng.echo(\"C\");\nlet output = await g.ob_get_clean();\nconsole.log(output); // prints \"ABC\"\n\nawait g.exit();\n```\nBut this is not good for large outputs, because the whole output will be stored in RAM.\n\nSetting `settings.stdout` to `piped` allows to catch PHP output. Initially the output will be passed to Deno, as in the `inherit` case, but you'll be able to call `php.get_stdout_reader()` to get `ReadableStream\u003cUint8Array\u003e` object from which the output can be read. To stop reading the output from that reader, and to redirect it back to `Deno.stdout`, call `php.drop_stdout_reader()`. This will cause the reader stream to end (`EOF`).\n\n```ts\nimport {php, settings} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\nimport {readAll} from 'https://deno.land/std@0.203.0/streams/mod.ts';\n\nsettings.stdout = 'piped';\n\nlet stdout = await php.get_stdout_reader();\nphp.g.echo(\"*\".repeat(10)); // no await\nphp.g.echo(\".\"); // queue another function call\nphp.drop_stdout_reader(); // reader stream will end here\n\nlet data = new TextDecoder().decode(await readAll(stdout));\nconsole.log(data == \"*\".repeat(10)+\".\"); // prints \"true\"\n\nawait php.g.exit();\n```\n\nThis technique doesn't work good with PHP-FPM, because output can be buffered in the middle between PHP and Deno.\n\nAnother option for `settings.stdout` is `'null'` meaning to ignore the output.\n\n### Interpreter script\n\nThis library uses interpreter script that executes commands sent from Deno end.\n\nIf using PHP-CLI, the default behavior is to pass the whole contents of the interpreter script (that is embedded to this library) as command line argument to PHP command.\n\nIf using PHP-FPM, the default behavior is to create temporary file in system temporary directory, write the interpreter script to this file, and pass it's filename to PHP-FPM service.\n\nIn certain circumstances such default behavior is not wanted.\nAnother option is to download the interpreter script [from here](https://deno.land/x/php_world@v0.0.40/php/deno-php-world.php),\ninstall it to your system together with the application, and set `settings.interpreter_script` setting to the path of this file.\nThis file must be accessible by PHP.\n\nPlacing your interpreter script to WWW accessible place must not be a security risk.\nThis script will agree to execute commands only if certain parameters are set.\nFor PHP-CLI, this script reads parameters from STDIN, and only if `php_sapi_name()` returns `cli`.\nFor PHP-FPM, parameters are passed through FastCGI server environment variable called `$_SERVER['DENO_WORLD_HELO']`.\nIf your HTTP server is not configured to pass such variable, the interpreter script will not execute commands when is accessed through WWW.\n\n### How fast is deno_world?\n\n`deno_world` spawns a background PHP process, and uses it to execute PHP operations. Every operation, like function call, or getting or setting a variable, sends requests to the PHP process and awaits for responses.\n\nFirst of all, spawning takes time, but it happens once (or several times if your application calls `g.exet()` to terminate the interpreter, and then uses the interpreter again). Then every request to execute an operation, not only executes it, but implies many other operations.\n\nWhat price you pay depends on operation weight. Executing many lightweight operations implies much overhead. And vise versa, if calling PHP functions that do a lot of work, the commission will be negligible.\n\nUnderstanding this, lets measure the overhead of average `deno_world` API call.\n\nHow much time takes to call the following function in PHP?\n\n```php\nfunction dec()\n{\tglobal $n;\n\treturn $n--;\n}\n```\nLet's use this time as a measuring unit, and measure how slower is `deno_world` over native PHP.\n\n```ts\nimport {g} from 'https://deno.land/x/php_world@v0.0.40/mod.ts';\n\nawait g.eval\n(\t`\tfunction dec()\n\t\t{\tglobal $n;\n\t\t\treturn $n--;\n\t\t}\n\n\t\tfunction php_ops_per_sec(int $bench_times)\n\t\t{\tglobal $n;\n\t\t\t$n = $bench_times;\n\t\t\t$start_time = microtime(true);\n\t\t\twhile (dec());\n\t\t\treturn $bench_times / (microtime(true) - $start_time);\n\t\t}\n\t`\n);\n\nlet {php_ops_per_sec, dec} = g;\n\nasync function deno_ops_per_sec(bench_times: number)\n{\tg.$n = bench_times;\n\tlet start_time = Date.now() / 1000;\n\twhile (await dec());\n\treturn bench_times / (Date.now()/1000 - start_time);\n}\n\nlet php_native_time = await php_ops_per_sec(10_000_000);\nconsole.log(`PHP native: ${php_native_time} ops/sec`);\n\nlet api_time = await deno_ops_per_sec(100_000);\nconsole.log(`API: ${api_time} ops/sec, (${Math.round(php_native_time/api_time)} times slower)`);\n```\nOn my computer i get the following result:\n\n```\nPHP native: 6447752.089756534 ops/sec\nAPI: 26301.94602735204 ops/sec, (245 times slower)\n```\nThis is for the most elementary operation that we can measure. What if this operation would be heavier?\n\n```php\nfunction dec()\n{\tglobal $n, $v;\n\t$v = base64_encode('0123456789ABCDEF0123456789ABCDEF');\n\treturn $n--;\n}\n```\nThe results on my computer are these:\n\n```\nPHP native: 3049521.541919448 ops/sec\nAPI: 24679.170501055585 ops/sec, (124 times slower)\n```\n\nAnd for a much slower operation?\n\n```php\nfunction dec()\n{\tglobal $n, $v;\n\t$v = base64_encode(str_repeat('*', 25600));\n\treturn $n--;\n}\n```\nResults (php_ops_per_sec(1_000_000) and deno_ops_per_sec(10_000)):\n\n```\nPHP native: 57892.50582551152 ops/sec\nAPI: 16806.72188093417 ops/sec, (3 times slower)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeremiah-shaulov%2Fdeno_php_world","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeremiah-shaulov%2Fdeno_php_world","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeremiah-shaulov%2Fdeno_php_world/lists"}