Add brackets, update README, refactored naming
This commit is contained in:
parent
f29d1a0d7a
commit
eef63af0b0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
/bin/
|
||||
/vendor/
|
||||
composer.lock
|
||||
/.idea/
|
||||
/.phpstorm/
|
||||
/.vscode/
|
116
README.md
116
README.md
@ -1,48 +1,66 @@
|
||||
# joopschilder/php-async
|
||||
## Introduction
|
||||
This package provides functions to run callables asynchronously in PHP. Return values are shared via System-V shared memory.<br/>
|
||||
To use this package, you'll need PHP >= 7.0.0 with `ext-sysvshm` and `ext-pcntl`.<br/>
|
||||
You should consider the state of this package to be <i>experimental</i>.<br/><br/>
|
||||
<b>Note:</b> This package should not be used in a CGI environment.
|
||||
The key that is used to access the block of shared memory is created based on the inode information of one of the source files.
|
||||
This means that, whenever multiple instances (processes) from the same project source are created, they will try to use the same block of memory and collisions will occur.
|
||||
I might swap the `ftok()` call for a random string generator somewhere down the road.<br/><br/>
|
||||
<b>Note:</b> It is possible (but discouraged) to change the amount of available shared memory.
|
||||
If you wish to do so, it's as simple as calling either `Runtime::_setSharedMemorySizeMB(<amount of MB>);` or `Runtime::_setSharedMemorySizeB(<amount of bytes>);`.<br/>
|
||||
If you want to use 32MB for example, call `Runtime::_setSharedMemorySizeMB(32);`.<br/>
|
||||
Be sure to make this call before using any of the asynchronous functionalities.
|
||||
# `php-async`
|
||||
|
||||
## Installation
|
||||
This package is available on <a href="https://packagist.org/packages/joopschilder/php-async">Packagist</a>
|
||||
and can be installed using <a href="https://getcomposer.org/">Composer</a>:<br/>
|
||||
|
||||
This package is available on [Packagist](https://packagist.org/packages/joopschilder/php-async) and can be installed using [Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
$ composer require joopschilder/async-php
|
||||
```
|
||||
|
||||
It's also possible to manually add it to your `composer.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"joopschilder/php-async": "dev-master"
|
||||
"joopschilder/php-async": "~1.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
#### Functions
|
||||
The library exposes three functions in the global namespace that provide indirect access to the class `Asynchronous`:
|
||||
* `async(callable $function, ...$parameters)` to run something asynchronously, giving back a `Promise`;
|
||||
* `async_wait_all()` to wait for all currently running jobs to finish;
|
||||
* `async_reap_zombies()` to clean up any zombie processes during runtime if any exist;
|
||||
## What is this?
|
||||
|
||||
#### Promises
|
||||
Whenever you call `async(...)`, a `Promise` instance is returned.<br/>
|
||||
This package provides functions to run callables asynchronously in PHP. Return values are shared via System-V shared memory.
|
||||
To use this package, you need PHP >= 7 with `ext-sysvshm` and `ext-pcntl`.
|
||||
|
||||
You should consider the state of this package to be __highly experimental__.
|
||||
|
||||
___Note:__ This package should not be used in a CGI environment._
|
||||
The key that is used to access the block of shared memory is created based on the inode information of one of the source files.
|
||||
This means that, whenever multiple instances (processes) from the same project source are created, they will try to use the same block of memory and collisions will occur.
|
||||
I might swap the `ftok()` call for a multi-instance supporting mechanism later on (feel free to do so yourself).
|
||||
|
||||
___Note:__ It is possible (but discouraged) to change the amount of available shared memory._
|
||||
If you wish to do so, it's as simple as calling either `Runtime::setSharedMemorySizeMB(<amount of MB>)` or `Runtime::setSharedMemorySizeB(<amount of bytes>)`.
|
||||
If you want to use 32MB for example, call `Runtime::setSharedMemorySizeMB(32)`.<br/>
|
||||
Be sure to make this call before using any of the asynchronous functionalities.
|
||||
|
||||
## What is this not?
|
||||
|
||||
This is, as you probably guessed by now, not intended for use in a production environment.
|
||||
I'm not saying you _can't_, I'm just saying you _shouldn't_.
|
||||
|
||||
The code is _not_ unit tested. It has been documented throughout though, so feel free to take a look.
|
||||
|
||||
## Usage
|
||||
|
||||
### Functions
|
||||
|
||||
The library exposes three functions in the global namespace that provide indirect access to the `Asynchronous` class:
|
||||
|
||||
* `async(callable $function, ...$parameters)` to run something asynchronously, returning a `Promise`;
|
||||
* `async_wait_all()` to wait for all currently running jobs to finish;
|
||||
* `async_cleanup()` to clean up any zombie processes during runtime if any exist;
|
||||
|
||||
### A `Promise`, you say?
|
||||
|
||||
`async(...)` returns an instance of `JoopSchilder\Asynchronous\Promise`.
|
||||
A `Promise` is considered to be resolved when the function it belongs to returned a value or finished execution.
|
||||
To block execution until a promise is resolved, simply call the `resolve()` method on the promise.
|
||||
It's possible to check whether the promise has been resolved in a non-blocking way by calling the `isResolved()` method.<br/>
|
||||
It's possible to check whether the promise has been resolved in a non-blocking way by calling the `isResolved()` method.
|
||||
You can actually return anything that is serializable in PHP: objects, arrays, strings, you name it.
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
```php
|
||||
$promise = async(function() {
|
||||
sleep(random_int(1, 5));
|
||||
return getmypid();
|
||||
@ -53,16 +71,15 @@ $promise = async(function() {
|
||||
$promise->resolve();
|
||||
$pid = $promise->getValue();
|
||||
```
|
||||
The shutdown handler and destructors should take care of the cleanup.<br/>
|
||||
#### Asynchronous curl requests
|
||||
... though you should probably look into curl multi handles for this: <a href="http://php.net/manual/en/function.curl-multi-init.php">curl_multi_init()</a>.
|
||||
The shutdown handler and destructors should take care of the cleanup.
|
||||
|
||||
### Asynchronous curl requests
|
||||
|
||||
The only reason `curl` is used here is to provide an intuitive example.
|
||||
If you really wanted to perform concurrent http requests you should look into either [`curl_multi_init`](http://php.net/manual/en/function.curl-multi-init.php) or just use [Guzzle](http://docs.guzzlephp.org/en/stable/).
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Create the body for the process...
|
||||
$process = function(string $url) {
|
||||
$job = function(string $url) {
|
||||
$handle = curl_init($url);
|
||||
curl_setopt($handle, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
|
||||
@ -71,30 +88,27 @@ $process = function(string $url) {
|
||||
file_put_contents(uniqid('download_'), $response);
|
||||
};
|
||||
|
||||
// Define some urls we want to download...
|
||||
$urls = [
|
||||
'example.com',
|
||||
'example2.com',
|
||||
'some.other.domain'
|
||||
];
|
||||
|
||||
// And there we go.
|
||||
foreach($urls as $url)
|
||||
async($process, $url);
|
||||
$urls = ['example.com', 'example2.com', 'some.other.domain'];
|
||||
foreach($urls as $url) {
|
||||
async($job, $url);
|
||||
}
|
||||
```
|
||||
That's all there is to it.
|
||||
|
||||
## Tips
|
||||
If you're on a UNIX system, you can make use of the tools `ipcs` and `ipcrm` to monitor and manage the shared memory blocks.<br/>
|
||||
To track what's happening in real time, I like to use:<br/>
|
||||
|
||||
If you're using a UNIX system, you can make use of the tools `ipcs` and `ipcrm` to monitor and manage System V shared memory blocks.
|
||||
To see what's happening, you can use:
|
||||
|
||||
```bash
|
||||
$ watch -n 1 "ipcs -m --human && ipcs -m -p && ipcs -m -t && ipcs -m -u"
|
||||
```
|
||||
<br/>To clean all 'unused' shared memory blocks (they might remain resident in RAM if your program terminated unexpectedly):<br/>
|
||||
|
||||
To clean all 'unused' shared memory blocks (they might stay in RAM if your program terminated unexpectedly), run
|
||||
|
||||
```bash
|
||||
$ ipcrm -a
|
||||
```
|
||||
|
||||
## What's next?
|
||||
- Improving stability
|
||||
- Add an explaining diagram
|
||||
Who really knows?
|
||||
|
@ -1,15 +1,22 @@
|
||||
{
|
||||
"name": "joopschilder/php-async",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Joop Schilder",
|
||||
"email": "j.n.m.schilder@st.hanze.nl"
|
||||
"email": "jnmschilder@protonmail.com",
|
||||
"homepage": "https://joopschilder.nl/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"files": ["src/functions.php"],
|
||||
"files": [
|
||||
"lib/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"JoopSchilder\\Asynchronous\\": ["src"]
|
||||
"JoopSchilder\\Asynchronous\\": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
|
20
composer.lock
generated
Normal file
20
composer.lock
generated
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b0b3e06a09fa46405754b75a4beba98c",
|
||||
"packages": [],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"ext-pcntl": "*",
|
||||
"ext-sysvshm": "*"
|
||||
},
|
||||
"platform-dev": []
|
||||
}
|
@ -16,14 +16,14 @@ if (!function_exists('async')) {
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('async_reap_zombies')) {
|
||||
if (!function_exists('async_cleanup')) {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function async_reap_zombies()
|
||||
function async_cleanup()
|
||||
{
|
||||
Asynchronous::reap();
|
||||
Asynchronous::cleanup();
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,11 @@
|
||||
|
||||
namespace JoopSchilder\Asynchronous;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class Asynchronous
|
||||
* @package JoopSchilder\Asynchronous
|
||||
* Responsible for management of child processes and shared memory.
|
||||
*/
|
||||
class Asynchronous
|
||||
@ -18,12 +21,39 @@ class Asynchronous
|
||||
private $shm;
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronous constructor.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
/*
|
||||
* The reason we do this is for when the shm block
|
||||
* already exists. We attach, remove, detach and reattach
|
||||
* to ensure a clean state.
|
||||
*/
|
||||
$this->attachToSharedMemory();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (Runtime::isChild()) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::getInstance()->freeSharedMemoryBlock();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param callable $function
|
||||
* @param mixed ...$parameters
|
||||
* @return Promise|null;
|
||||
* @return Promise|null
|
||||
*/
|
||||
public static function run(callable $function, ...$parameters)
|
||||
public static function run(callable $function, ...$parameters): ?Promise
|
||||
{
|
||||
/*
|
||||
* Prepare for fork
|
||||
@ -31,16 +61,10 @@ class Asynchronous
|
||||
$instance = self::getInstance();
|
||||
$promiseKey = Promise::generatePromiseKey();
|
||||
|
||||
/*
|
||||
* Fork the parent
|
||||
*/
|
||||
$pid = pcntl_fork();
|
||||
|
||||
/*
|
||||
* The fork failed. Instead of returning a promise, we return null.
|
||||
*/
|
||||
if ($pid == -1)
|
||||
if (-1 === $pid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parent process. We keep track of the PID of the child process
|
||||
@ -66,25 +90,25 @@ class Asynchronous
|
||||
* the Promise to be able to resolve.
|
||||
*/
|
||||
Runtime::markAsChild();
|
||||
$instance->_attachToShm();
|
||||
$instance->attachToSharedMemory();
|
||||
|
||||
try {
|
||||
$response = call_user_func($function, ...$parameters);
|
||||
shm_put_var($instance->shm, $promiseKey, $response ?? Promise::RESPONSE_NONE);
|
||||
|
||||
exit(0);
|
||||
|
||||
} catch (\Throwable $throwable) {
|
||||
} catch (Throwable $throwable) {
|
||||
shm_put_var($instance->shm, $promiseKey, Promise::RESPONSE_ERROR);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function reap()
|
||||
public static function cleanup(): void
|
||||
{
|
||||
/*
|
||||
* Iterate over all child process PIDs and check
|
||||
@ -93,58 +117,35 @@ class Asynchronous
|
||||
$instance = self::getInstance();
|
||||
foreach ($instance->children as $index => $pid) {
|
||||
$response = pcntl_waitpid($pid, $status, WNOHANG);
|
||||
if ($response === $pid)
|
||||
if ($response === $pid) {
|
||||
unset($instance->children[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function waitForChildren()
|
||||
public static function waitForChildren(): void
|
||||
{
|
||||
self::getInstance()->_awaitChildren();
|
||||
self::getInstance()->awaitChildProcesses();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function removeShmBlock()
|
||||
public static function removeShmBlock(): void
|
||||
{
|
||||
self::getInstance()->_removeShmBlock();
|
||||
self::getInstance()->freeSharedMemoryBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function childCount()
|
||||
{
|
||||
return count(self::getInstance()->children);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Private methods below
|
||||
*/
|
||||
|
||||
/**
|
||||
* Asynchronous constructor.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
/*
|
||||
* The reason we do this is for when the shm block
|
||||
* already exists. We attach, remove, detach and reattach
|
||||
* to ensure a clean state.
|
||||
*/
|
||||
$this->_attachToShm(); // Attach
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function _awaitChildren()
|
||||
private function awaitChildProcesses(): self
|
||||
{
|
||||
/*
|
||||
* Wait for the children to terminate
|
||||
@ -157,14 +158,12 @@ class Asynchronous
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function _removeShmBlock()
|
||||
private function freeSharedMemoryBlock(): self
|
||||
{
|
||||
/*
|
||||
* Detach from the shared memory block
|
||||
*/
|
||||
if (is_resource($this->shm)) {
|
||||
shm_remove($this->shm);
|
||||
shm_detach($this->shm);
|
||||
@ -177,7 +176,7 @@ class Asynchronous
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function _attachToShm()
|
||||
private function attachToSharedMemory(): self
|
||||
{
|
||||
$this->shm = shm_attach(Runtime::getSharedMemoryKey(), Runtime::getSharedMemorySize());
|
||||
|
||||
@ -188,14 +187,9 @@ class Asynchronous
|
||||
/**
|
||||
* @return Asynchronous
|
||||
*/
|
||||
private static function getInstance()
|
||||
private static function getInstance(): self
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
/*
|
||||
* This is executed once during runtime;
|
||||
* when a functionality from this class
|
||||
* is used for the first time.
|
||||
*/
|
||||
self::$instance = new static();
|
||||
self::registerHandlers();
|
||||
}
|
||||
@ -207,32 +201,17 @@ class Asynchronous
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static function registerHandlers()
|
||||
private static function registerHandlers(): void
|
||||
{
|
||||
$instance = self::getInstance();
|
||||
|
||||
/*
|
||||
* The shutdown handler
|
||||
*/
|
||||
register_shutdown_function(function () use (&$instance) {
|
||||
if (Runtime::isChild())
|
||||
if (Runtime::isChild()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$instance->_awaitChildren();
|
||||
$instance->_removeShmBlock();
|
||||
$instance->awaitChildProcesses();
|
||||
$instance->freeSharedMemoryBlock();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (Runtime::isChild())
|
||||
return;
|
||||
|
||||
$instance = self::getInstance();
|
||||
$instance->_removeShmBlock();
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,10 @@
|
||||
|
||||
namespace JoopSchilder\Asynchronous;
|
||||
|
||||
/**
|
||||
* Class Promise
|
||||
* @package JoopSchilder\Asynchronous
|
||||
*/
|
||||
class Promise
|
||||
{
|
||||
/*
|
||||
@ -43,8 +47,9 @@ class Promise
|
||||
* shm keys).
|
||||
*/
|
||||
self::$generatedKey++;
|
||||
if (self::$generatedKey > 99999999)
|
||||
if (self::$generatedKey > 99999999) {
|
||||
self::$generatedKey = 0;
|
||||
}
|
||||
|
||||
return $promiseKey;
|
||||
}
|
||||
@ -65,7 +70,7 @@ class Promise
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isResolved()
|
||||
public function isResolved(): bool
|
||||
{
|
||||
$this->attempt();
|
||||
|
||||
@ -76,7 +81,7 @@ class Promise
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isVoid()
|
||||
public function isVoid(): bool
|
||||
{
|
||||
return $this->getValue() === self::RESPONSE_NONE;
|
||||
}
|
||||
@ -85,7 +90,7 @@ class Promise
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isError()
|
||||
public function isError(): bool
|
||||
{
|
||||
return $this->getValue() === self::RESPONSE_ERROR;
|
||||
}
|
||||
@ -103,14 +108,15 @@ class Promise
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function resolve()
|
||||
public function resolve(): self
|
||||
{
|
||||
/*
|
||||
* Actually block execution until a value is written to
|
||||
* the expected memory location of this Promise.
|
||||
*/
|
||||
while (!$this->isResolved())
|
||||
usleep(1000);
|
||||
while (!$this->isResolved()) {
|
||||
usleep(50);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -128,24 +134,28 @@ class Promise
|
||||
* garbage collector has noticed that there are no more
|
||||
* references to this Promise instance.
|
||||
*/
|
||||
if (Runtime::isChild())
|
||||
if (Runtime::isChild()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shm_has_var($this->shm, $this->key))
|
||||
if (shm_has_var($this->shm, $this->key)) {
|
||||
shm_remove_var($this->shm, $this->key);
|
||||
}
|
||||
|
||||
shm_detach($this->shm);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function attempt()
|
||||
private function attempt(): self
|
||||
{
|
||||
if (shm_has_var($this->shm, $this->key))
|
||||
if (shm_has_var($this->shm, $this->key)) {
|
||||
$this->value = shm_get_var($this->shm, $this->key);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace JoopSchilder\Asynchronous;
|
||||
|
||||
/**
|
||||
@ -34,15 +33,10 @@ class Runtime
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Public
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getSharedMemorySize()
|
||||
public static function getSharedMemorySize(): int
|
||||
{
|
||||
return self::$sharedMemSize;
|
||||
}
|
||||
@ -51,14 +45,14 @@ class Runtime
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getSharedMemoryKey()
|
||||
public static function getSharedMemoryKey(): int
|
||||
{
|
||||
/*
|
||||
* Use the filename as an identifier to create the
|
||||
* System V IPC key.
|
||||
* Use the filename as an identifier to create the System V IPC key.
|
||||
*/
|
||||
if (is_null(self::$sharedMemKey))
|
||||
if (is_null(self::$sharedMemKey)) {
|
||||
self::$sharedMemKey = ftok(__FILE__, 't');
|
||||
}
|
||||
|
||||
return self::$sharedMemKey;
|
||||
}
|
||||
@ -67,7 +61,7 @@ class Runtime
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isChild()
|
||||
public static function isChild(): bool
|
||||
{
|
||||
return !self::$inParentRuntime;
|
||||
}
|
||||
@ -78,30 +72,30 @@ class Runtime
|
||||
* To be used by internal classes.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @param int $size_mb
|
||||
* @param int $sizeMegaBytes
|
||||
*/
|
||||
public static function _setSharedMemorySizeMB(int $size_mb)
|
||||
public static function setSharedMemorySizeMB(int $sizeMegaBytes): void
|
||||
{
|
||||
self::$sharedMemSize = abs($size_mb) * (1024 ** 2);
|
||||
self::$sharedMemSize = abs($sizeMegaBytes) * (1024 ** 2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $size_b
|
||||
* @param int $sizeBytes
|
||||
*/
|
||||
public static function _setSharedMemorySizeB(int $size_b)
|
||||
public static function setSharedMemorySizeB(int $sizeBytes): void
|
||||
{
|
||||
self::$sharedMemSize = $size_b;
|
||||
self::$sharedMemSize = $sizeBytes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function markAsChild()
|
||||
public static function markAsChild(): void
|
||||
{
|
||||
self::$inParentRuntime = false;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user