Commit 422d81d8 authored by rlgy's avatar rlgy

账单

parent cae4b465
......@@ -24,6 +24,3 @@ composer.lock
# linux file system
/.directory
# vendor files
vendor
<?php
namespace Codeception\Lib\Connector\Yii2;
use yii\base\Event;
use yii\db\Connection;
/**
* Class ConnectionWatcher
* This class will watch for new database connection and store a reference to the connection object.
* @package Codeception\Lib\Connector\Yii2
*/
class ConnectionWatcher
{
private $handler;
/** @var Connection[] */
private $connections = [];
public function __construct()
{
$this->handler = function (Event $event) {
if ($event->sender instanceof Connection) {
$this->connectionOpened($event->sender);
}
};
}
protected function connectionOpened(Connection $connection)
{
$this->debug('Connection opened!');
if ($connection instanceof Connection) {
$this->connections[] = $connection;
}
}
public function start()
{
Event::on(Connection::class, Connection::EVENT_AFTER_OPEN, $this->handler);
$this->debug('watching new connections');
}
public function stop()
{
Event::off(Connection::class, Connection::EVENT_AFTER_OPEN, $this->handler);
$this->debug('no longer watching new connections');
}
public function closeAll()
{
$count = count($this->connections);
$this->debug("closing all ($count) connections");
foreach ($this->connections as $connection) {
$connection->close();
}
}
protected function debug($message)
{
$title = (new \ReflectionClass($this))->getShortName();
if (is_array($message) or is_object($message)) {
$message = stripslashes(json_encode($message));
}
codecept_debug("[$title] $message");
}
}
<?php
namespace Codeception\Lib\Connector\Yii2;
use yii\base\Event;
use yii\db\Connection;
use yii\db\Transaction;
/**
* Class TransactionForcer
* This class adds support for forcing transactions as well as reusing PDO objects.
* @package Codeception\Lib\Connector\Yii2
*/
class TransactionForcer extends ConnectionWatcher
{
private $ignoreCollidingDSN;
private $pdoCache = [];
private $dsnCache;
private $transactions = [];
public function __construct(
$ignoreCollidingDSN
) {
parent::__construct();
$this->ignoreCollidingDSN = $ignoreCollidingDSN;
}
protected function connectionOpened(Connection $connection)
{
parent::connectionOpened($connection);
/**
* We should check if the known PDO objects are the same, in which case we should reuse the PDO
* object so only 1 transaction is started and multiple connections to the same database see the
* same data (due to writes inside a transaction not being visible from the outside).
*
*/
$key = md5(json_encode([
'dsn' => $connection->dsn,
'user' => $connection->username,
'pass' => $connection->password,
'attributes' => $connection->attributes,
'emulatePrepare' => $connection->emulatePrepare,
'charset' => $connection->charset
]));
/*
* If keys match we assume connections are "similar enough".
*/
if (isset($this->pdoCache[$key])) {
$connection->pdo = $this->pdoCache[$key];
} else {
$this->pdoCache[$key] = $connection->pdo;
}
if (isset($this->dsnCache[$connection->dsn])
&& $this->dsnCache[$connection->dsn] !== $key
&& !$this->ignoreCollidingDSN
) {
$this->debug(<<<TEXT
You use multiple connections to the same DSN ({$connection->dsn}) with different configuration.
These connections will not see the same database state since we cannot share a transaction between different PDO
instances.
You can remove this message by adding 'ignoreCollidingDSN = true' in the module configuration.
TEXT
);
Debug::pause();
}
if (isset($this->transactions[$key])) {
$this->debug('Reusing PDO, so no need for a new transaction');
return;
}
$this->debug('Transaction started for: ' . $connection->dsn);
$this->transactions[$key] = $connection->beginTransaction();
}
public function rollbackAll()
{
/** @var Transaction $transaction */
foreach ($this->transactions as $transaction) {
if ($transaction->db->isActive) {
$transaction->rollBack();
$this->debug('Transaction cancelled; all changes reverted.');
}
}
$this->transactions = [];
$this->pdoCache = [];
$this->dsnCache = [];
}
}
<?php
class ConfigExtendsCest
{
/**
* @param CliGuy $I
*/
public function runIncludedSuites(\CliGuy $I)
{
$I->amInPath('tests/data/config_extends');
$I->executeCommand('run');
$I->seeInShellOutput('UnitCest');
$I->seeInShellOutput('OK (1 test, 1 assertion)');
$I->dontSeeInShellOutput('Exception');
}
}
paths:
tests: tests
log: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 2048M
extensions:
enabled:
- Codeception\Extension\RunFailed
actor: Tester
extends: codeception.common.yml
<?php
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
*
* @SuppressWarnings(PHPMD)
*/
class UnitTester extends \Codeception\Actor
{
use _generated\UnitTesterActions;
/**
* Define custom actions here
*/
}
# Codeception Test Suite Configuration
#
# Suite for unit (internal) tests.
class_name: UnitTester
extends: unit.suite.common.yml
<?php
class UnitCest
{
public function successful(UnitTester $I)
{
$I->assertTrue(true);
}
}
<?php //[STAMP] b6de2e232db397e7e5da4d9205ba0a55
namespace _generated;
// This class was automatically generated by build task
// You should not change it manually as it will be overwritten on next build
// @codingStandardsIgnoreFile
trait RetryTesterActions
{
/**
* @return \Codeception\Scenario
*/
abstract protected function getScenario();
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that two variables are equal. If you're comparing floating-point values,
* you can specify the optional "delta" parameter which dictates how great of a precision
* error are you willing to tolerate in order to consider the two values equal.
*
* Regular example:
* ```php
* <?php
* $I->assertEquals($element->getChildrenCount(), 5);
* ```
*
* Floating-point example:
* ```php
* <?php
* $I->assertEquals($calculator->add(0.1, 0.2), 0.3, 'Calculator should add the two numbers correctly.', 0.01);
* ```
*
* @param $expected
* @param $actual
* @param string $message
* @param float $delta
* @see \Codeception\Module\Asserts::assertEquals()
*/
public function assertEquals($expected, $actual, $message = null, $delta = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEquals', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that two variables are not equal. If you're comparing floating-point values,
* you can specify the optional "delta" parameter which dictates how great of a precision
* error are you willing to tolerate in order to consider the two values not equal.
*
* Regular example:
* ```php
* <?php
* $I->assertNotEquals($element->getChildrenCount(), 0);
* ```
*
* Floating-point example:
* ```php
* <?php
* $I->assertNotEquals($calculator->add(0.1, 0.2), 0.4, 'Calculator should add the two numbers correctly.', 0.01);
* ```
*
* @param $expected
* @param $actual
* @param string $message
* @param float $delta
* @see \Codeception\Module\Asserts::assertNotEquals()
*/
public function assertNotEquals($expected, $actual, $message = null, $delta = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that two variables are same
*
* @param $expected
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertSame()
*/
public function assertSame($expected, $actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertSame', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that two variables are not same
*
* @param $expected
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertNotSame()
*/
public function assertNotSame($expected, $actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotSame', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that actual is greater than expected
*
* @param $expected
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertGreaterThan()
*/
public function assertGreaterThan($expected, $actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterThan', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that actual is greater or equal than expected
*
* @param $expected
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertGreaterThanOrEqual()
*/
public function assertGreaterThanOrEqual($expected, $actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterThanOrEqual', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that actual is less than expected
*
* @param $expected
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertLessThan()
*/
public function assertLessThan($expected, $actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessThan', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that actual is less or equal than expected
*
* @param $expected
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertLessThanOrEqual()
*/
public function assertLessThanOrEqual($expected, $actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessThanOrEqual', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that haystack contains needle
*
* @param $needle
* @param $haystack
* @param string $message
* @see \Codeception\Module\Asserts::assertContains()
*/
public function assertContains($needle, $haystack, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertContains', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that haystack doesn't contain needle.
*
* @param $needle
* @param $haystack
* @param string $message
* @see \Codeception\Module\Asserts::assertNotContains()
*/
public function assertNotContains($needle, $haystack, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotContains', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that string match with pattern
*
* @param string $pattern
* @param string $string
* @param string $message
* @see \Codeception\Module\Asserts::assertRegExp()
*/
public function assertRegExp($pattern, $string, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertRegExp', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that string not match with pattern
*
* @param string $pattern
* @param string $string
* @param string $message
* @see \Codeception\Module\Asserts::assertNotRegExp()
*/
public function assertNotRegExp($pattern, $string, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotRegExp', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that a string starts with the given prefix.
*
* @param string $prefix
* @param string $string
* @param string $message
* @see \Codeception\Module\Asserts::assertStringStartsWith()
*/
public function assertStringStartsWith($prefix, $string, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsWith', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that a string doesn't start with the given prefix.
*
* @param string $prefix
* @param string $string
* @param string $message
* @see \Codeception\Module\Asserts::assertStringStartsNotWith()
*/
public function assertStringStartsNotWith($prefix, $string, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsNotWith', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that variable is empty.
*
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertEmpty()
*/
public function assertEmpty($actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEmpty', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that variable is not empty.
*
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertNotEmpty()
*/
public function assertNotEmpty($actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEmpty', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that variable is NULL
*
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertNull()
*/
public function assertNull($actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNull', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that variable is not NULL
*
* @param $actual
* @param string $message
* @see \Codeception\Module\Asserts::assertNotNull()
*/
public function assertNotNull($actual, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotNull', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that condition is positive.
*
* @param $condition
* @param string $message
* @see \Codeception\Module\Asserts::assertTrue()
*/
public function assertTrue($condition, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertTrue', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that the condition is NOT true (everything but true)
*
* @param $condition
* @param string $message
* @see \Codeception\Module\Asserts::assertNotTrue()
*/
public function assertNotTrue($condition, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotTrue', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that condition is negative.
*
* @param $condition
* @param string $message
* @see \Codeception\Module\Asserts::assertFalse()
*/
public function assertFalse($condition, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFalse', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that the condition is NOT false (everything but false)
*
* @param $condition
* @param string $message
* @see \Codeception\Module\Asserts::assertNotFalse()
*/
public function assertNotFalse($condition, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotFalse', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks if file exists
*
* @param string $filename
* @param string $message
* @see \Codeception\Module\Asserts::assertFileExists()
*/
public function assertFileExists($filename, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileExists', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks if file doesn't exist
*
* @param string $filename
* @param string $message
* @see \Codeception\Module\Asserts::assertFileNotExists()
*/
public function assertFileNotExists($filename, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileNotExists', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $expected
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertGreaterOrEquals()
*/
public function assertGreaterOrEquals($expected, $actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterOrEquals', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $expected
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertLessOrEquals()
*/
public function assertLessOrEquals($expected, $actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessOrEquals', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertIsEmpty()
*/
public function assertIsEmpty($actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsEmpty', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $key
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertArrayHasKey()
*/
public function assertArrayHasKey($key, $actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArrayHasKey', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $key
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertArrayNotHasKey()
*/
public function assertArrayNotHasKey($key, $actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArrayNotHasKey', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Checks that array contains subset.
*
* @param array $subset
* @param array $array
* @param bool $strict
* @param string $message
* @see \Codeception\Module\Asserts::assertArraySubset()
*/
public function assertArraySubset($subset, $array, $strict = null, $message = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArraySubset', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $expectedCount
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertCount()
*/
public function assertCount($expectedCount, $actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertCount', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $class
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertInstanceOf()
*/
public function assertInstanceOf($class, $actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertInstanceOf', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $class
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertNotInstanceOf()
*/
public function assertNotInstanceOf($class, $actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotInstanceOf', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* @param $type
* @param $actual
* @param $description
* @see \Codeception\Module\Asserts::assertInternalType()
*/
public function assertInternalType($type, $actual, $description = null) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('assertInternalType', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Fails the test with message.
*
* @param $message
* @see \Codeception\Module\Asserts::fail()
*/
public function fail($message) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('fail', func_get_args()));
}
/**
* [!] Method is generated. Documentation taken from corresponding module.
*
* Handles and checks exception called inside callback function.
* Either exception class name or exception instance should be provided.
*
* ```php
* <?php
* $I->expectException(MyException::class, function() {
* $this->doSomethingBad();
* });
*
* $I->expectException(new MyException(), function() {
* $this->doSomethingBad();
* });
* ```
* If you want to check message or exception code, you can pass them with exception instance:
* ```php
* <?php
* // will check that exception MyException is thrown with "Don't do bad things" message
* $I->expectException(new MyException("Don't do bad things"), function() {
* $this->doSomethingBad();
* });
* ```
*
* @param $exception string or \Exception
* @param $callback
* @see \Codeception\Module\Asserts::expectException()
*/
public function expectException($exception, $callback) {
return $this->getScenario()->runStep(new \Codeception\Step\Action('expectException', func_get_args()));
}
}
<?php
namespace Faker\Calculator;
use InvalidArgumentException;
class TCNo
{
/**
* Generates Turkish Identity Number Checksum
* Gets first 9 digit as prefix and calcuates checksums
*
* https://en.wikipedia.org/wiki/Turkish_Identification_Number
*
* @param string $identityPrefix
* @return string Checksum (two digit)
*/
public static function checksum($identityPrefix)
{
if (strlen((string)$identityPrefix) !== 9) {
throw new InvalidArgumentException('Argument should be an integer and should be 9 digits.');
}
$oddSum = 0;
$evenSum = 0;
$identityArray = array_map('intval', str_split($identityPrefix)); // Creates array from int
foreach ($identityArray as $index => $digit) {
if ($index % 2 == 0) {
$evenSum += $digit;
} else {
$oddSum += $digit;
}
}
$tenthDigit = (7 * $evenSum - $oddSum) % 10;
$eleventhDigit = ($evenSum + $oddSum + $tenthDigit) % 10;
return $tenthDigit . $eleventhDigit;
}
/**
* Checks whether an TCNo has a valid checksum
*
* @param string $tcNo
* @return boolean
*/
public static function isValid($tcNo)
{
return self::checksum(substr($tcNo, 0, -2)) === substr($tcNo, -2, 2);
}
}
<?php
namespace Faker\Provider\ms_MY;
use \Faker\Generator;
class Address extends \Faker\Provider\Address
{
/**
* @link https://en.wikipedia.org/wiki/Addresses_in_Malaysia
*/
protected static $addressFormats = array(
'{{streetAddress}}, {{township}}, {{townState}}',
);
protected static $streetAddressFormats = array(
'{{buildingPrefix}}{{buildingNumber}}, {{streetName}}'
);
/**
* Most of the time 'No.' is not needed, and 'Lot' is less used.
*/
protected static $buildingPrefix = array(
'','','','','','',
'No. ','No. ','No. ',
'Lot ',
);
protected static $buildingNumber = array(
'%','%','%',
'%#','%#','%#','%#',
'%##',
'%-%',
'?-##-##',
'%?-##'
);
protected static $streetNameFormats = array(
'{{streetPrefix}} %',
'{{streetPrefix}} %/%',
'{{streetPrefix}} %/%#',
'{{streetPrefix}} %/%?',
'{{streetPrefix}} %/%#?',
'{{streetPrefix}} %?',
'{{streetPrefix}} %#?',
'{{streetPrefix}} {{streetSuffix}}',
'{{streetPrefix}} {{streetSuffix}} %',
'{{streetPrefix}} {{streetSuffix}} %/%',
'{{streetPrefix}} {{streetSuffix}} %/%#',
'{{streetPrefix}} {{streetSuffix}} %/%?',
'{{streetPrefix}} {{streetSuffix}} %/%#?',
'{{streetPrefix}} {{streetSuffix}} %?',
'{{streetPrefix}} {{streetSuffix}} %#?',
);
protected static $townshipFormats = array(
'{{townshipPrefix}} {{townshipSuffix}}',
'{{townshipPrefix}} {{townshipSuffix}}',
'{{townshipPrefix}} {{townshipSuffix}}',
'{{townshipPrefix}} {{townshipSuffix}}',
'{{townshipPrefix}} {{townshipSuffix}}',
'{{townshipPrefix}} {{townshipSuffix}}',
'{{townshipPrefixAbbr}}%',
'{{townshipPrefixAbbr}}%#',
'{{townshipPrefixAbbr}}%#?',
);
/**
* 'Jalan' & 'Jln' are more frequently used than 'Lorong'
*
* @link https://en.wikipedia.org/wiki/List_of_roads_in_Kuala_Lumpur#Standard_translations
*/
protected static $streetPrefix = array(
'Jln','Jln',
'Jalan','Jalan','Jalan',
'Lorong'
);
/**
* @link https://en.wikipedia.org/wiki/List_of_roads_in_Kuala_Lumpur
* @link https://en.wikipedia.org/wiki/List_of_roads_in_Ipoh
* @link https://en.wikipedia.org/wiki/Transportation_in_Seremban#Inner_city_roads
* @link https://en.wikipedia.org/wiki/List_of_streets_in_George_Town,_Penang
*/
protected static $streetSuffix = array(
'Air Itam','Alor','Ampang','Ampang Hilir','Anson','Ariffin',
'Bangsar','Baru','Bellamy','Birch','Bijih Timah','Bukit Aman','Bukit Bintang','Bukit Petaling','Bukit Tunku',
'Cantonment','Cenderawasih','Chan Sow Lin','Chow Kit','Cinta','Cochrane','Conlay',
'D. S. Ramanathan','Damansara','Dang Wangi','Davis','Dewan Bahasa','Dato Abdul Rahman','Dato\'Keramat','Dato\' Maharaja Lela','Doraisamy',
'Eaton',
'Faraday',
'Galloway','Genting Klang','Gereja',
'Hang Jebat','Hang Kasturi','Hang Lekir','Hang Lekiu','Hang Tuah','Hospital',
'Imbi','Istana',
'Jelutong',
'Kampung Attap','Kebun Bunga','Kedah','Keliling','Kia Peng','Kinabalu','Kuala Kangsar','Kuching',
'Ledang','Lembah Permai','Loke Yew','Lt. Adnan','Lumba Kuda',
'Madras','Magazine','Maharajalela','Masjid','Maxwell','Mohana Chandran','Muda',
'P. Ramlee','Padang Kota Lama','Pahang','Pantai Baharu','Parlimen','Pasar','Pasar Besar','Perak','Perdana','Petaling','Prangin','Pudu','Pudu Lama',
'Raja','Raja Abdullah','Raja Chulan','Raja Laut','Rakyat','Residensi','Robson',
'S.P. Seenivasagam','Samarahan 1','Selamat','Sempadan','Sentul','Serian 1','Sasaran','Sin Chee','Sultan Abdul Samad','Sultan Azlan Shah','Sultan Iskandar','Sultan Ismail','Sultan Sulaiman','Sungai Besi','Syed Putra',
'Tan Cheng Lock','Thambipillay','Tugu','Tuanku Abdul Halim','Tuanku Abdul Rahman','Tun Abdul Razak','Tun Dr Ismail','Tun H S Lee','Tun Ismail','Tun Perak','Tun Razak','Tun Sambanthan',
'U-Thant','Utama',
'Vermont','Vivekananda',
'Wan Kadir','Wesley','Wisma Putra',
'Yaacob Latif','Yap Ah Loy','Yap Ah Shak','Yap Kwan Seng','Yew',
'Zaaba','Zainal Abidin'
);
/**
* @link https://en.wikipedia.org/wiki/List_of_Petaling_Jaya_city_sections
* @link https://en.wikipedia.org/wiki/UEP_Subang_Jaya#History
*/
protected static $townshipPrefixAbbr = array(
'SS','Seksyen ','PJS','PJU','USJ ',
);
/**
* 'Bandar' and 'Taman' are the most common township prefix
*
* @link https://en.wikipedia.org/wiki/Template:Greater_Kuala_Lumpur > Townships
* @link https://en.wikipedia.org/wiki/Template:Johor > Townships
* @link https://en.wikipedia.org/wiki/Template:Kedah > Townships
* @link https://en.wikipedia.org/wiki/Template:Kelantan > Townships
* @link https://en.wikipedia.org/wiki/Template:Melaka > Townships
* @link https://en.wikipedia.org/wiki/Template:Negeri_Sembilan > Townships
* @link https://en.wikipedia.org/wiki/Template:Perak > Townships
* @link https://en.wikipedia.org/wiki/Template:Penang > Townships
* @link https://en.wikipedia.org/wiki/Template:Selangor > Townships
* @link https://en.wikipedia.org/wiki/Template:Terengganu > Townships
*/
protected static $townshipPrefix = array(
'Alam','Apartment','Ara',
'Bandar','Bandar','Bandar','Bandar','Bandar','Bandar',
'Bandar Bukit','Bandar Seri','Bandar Sri','Bandar Baru','Batu','Bukit',
'Desa','Damansara',
'Kampung','Kampung Baru','Kampung Baru','Kondominium','Kota',
'Laman','Lembah',
'Medan',
'Pandan','Pangsapuri','Petaling','Puncak',
'Seri','Sri',
'Taman','Taman','Taman','Taman','Taman','Taman',
'Taman Desa',
);
protected static $townshipSuffix = array(
'Aman','Amanjaya','Anggerik','Angkasa','Antarabangsa','Awan',
'Bahagia','Bangsar','Baru','Belakong','Bendahara','Bestari','Bintang','Brickfields',
'Casa','Changkat','Country Heights',
'Damansara','Damai','Dato Harun','Delima','Duta',
'Flora',
'Gembira','Genting',
'Harmoni','Hartamas',
'Impian','Indah','Intan',
'Jasa','Jaya',
'Keramat','Kerinchi','Kiara','Kinrara','Kuchai',
'Laksamana',
'Mahkota','Maluri','Manggis','Maxwell','Medan','Melawati','Menjalara','Meru','Mulia','Mutiara',
'Pahlawan','Perdana','Pertama','Permai','Pelangi','Petaling','Pinang','Puchong','Puteri','Putra',
'Rahman','Rahmat','Raya','Razak','Ria',
'Saujana','Segambut','Selamat','Selatan','Semarak','Sentosa','Seputeh','Setapak','Setia Jaya','Sinar','Sungai Besi','Sungai Buaya','Sungai Long','Suria',
'Tasik Puteri','Tengah','Timur','Tinggi','Tropika','Tun Hussein Onn','Tun Perak','Tunku',
'Ulu','Utama','Utara',
'Wangi',
);
/**
* @link https://en.wikipedia.org/wiki/Template:Greater_Kuala_Lumpur
* @link https://en.wikipedia.org/wiki/Template:Johor
* @link https://en.wikipedia.org/wiki/Template:Kedah
* @link https://en.wikipedia.org/wiki/Template:Kelantan
* @link https://en.wikipedia.org/wiki/Template:Labuan
* @link https://en.wikipedia.org/wiki/Template:Melaka
* @link https://en.wikipedia.org/wiki/Template:Negeri_Sembilan
* @link https://en.wikipedia.org/wiki/Template:Pahang
* @link https://en.wikipedia.org/wiki/Template:Perak
* @link https://en.wikipedia.org/wiki/Template:Perlis
* @link https://en.wikipedia.org/wiki/Template:Penang
* @link https://en.wikipedia.org/wiki/Template:Sabah
* @link https://en.wikipedia.org/wiki/Template:Sarawak
* @link https://en.wikipedia.org/wiki/Template:Selangor
* @link https://en.wikipedia.org/wiki/Template:Terengganu
*/
protected static $towns = array(
'johor' => array(
'Ayer Hitam',
'Batu Pahat','Bukit Gambir','Bukit Kepong','Bukit Naning',
'Desaru',
'Endau',
'Gelang Patah','Gemas Baharu',
'Iskandar Puteri',
'Jementah','Johor Lama','Johor Bahru',
'Kempas','Kluang','Kota Iskandar','Kota Tinggi','Kukup','Kulai',
'Labis ','Larkin','Layang-Layang',
'Mersing','Muar',
'Pagoh','Paloh','Parit Jawa','Pasir Gudang','Pekan Nanas','Permas Jaya','Pontian Kechil',
'Renggam',
'Segamat','Senai','Simpang Renggam','Skudai','Sri Gading',
'Tangkak','Tebrau',
'Ulu Tiram',
'Yong Peng',
),
'kedah' => array(
'Alor Setar',
'Baling','Bukit Kayu Hitam',
'Changlun',
'Durian Burung',
'Gurun',
'Jitra',
'Kepala Batas','Kuah','Kuala Kedah','Kuala Ketil','Kulim',
'Langgar','Lunas',
'Merbok',
'Padang Serai','Pendang',
'Serdang','Sintok','Sungai Petani',
'Tawar, Baling',
'Yan',
),
'kelantan' => array(
'Bachok','Bunut Payong',
'Dabong',
'Gua Musang',
'Jeli',
'Ketereh','Kota Bharu','Kuala Krai',
'Lojing',
'Machang',
'Pasir Mas','Pasir Puteh',
'Rantau Panjang',
'Salor',
'Tok Bali',
'Wakaf Bharu','Wakaf Che Yeh',
),
'kl' => array(
'Ampang',
'Bandar Tasik Selatan','Bandar Tun Razak','Bangsar','Batu','Brickfields','Bukit Bintang','Bukit Jalil','Bukit Tunku',
'Cheras','Chow Kit',
'Damansara Town Centre','Dang Wangi','Desa Petaling','Desa Tun Hussein Onn',
'Jinjang',
'Kampung Baru','Kampung Kasipillay','Kampung Pandan','Kampung Sungai Penchala','Kepong','KLCC','Kuchai Lama',
'Lake Gardens','Lembah Pantai',
'Medan Tuanku','Mid Valley City','Mont Kiara',
'Pantai Dalam','Pudu',
'Salak South','Segambut','Semarak','Sentul','Setapak','Setiawangsa','Seputeh','Sri Hartamas','Sri Petaling','Sungai Besi',
'Taman Desa','Taman Melawati','Taman OUG','Taman Tun Dr Ismail','Taman U-Thant','Taman Wahyu','Titiwangsa','Tun Razak Exchange',
'Wangsa Maju',
),
'labuan' => array(
'Batu Manikar',
'Kiamsam',
'Layang-Layang',
'Rancha-Rancha'
),
'melaka' => array(
'Alor Gajah',
'Bandaraya Melaka','Batu Berendam','Bukit Beruang','Bukit Katil',
'Cheng',
'Durian Tunggal',
'Hang Tuah Jaya',
'Jasin',
'Klebang',
'Lubuk China',
'Masjid Tanah',
'Naning',
'Pekan Asahan',
'Ramuan China',
'Simpang Ampat',
'Tanjung Bidara','Telok Mas',
'Umbai',
),
'nsembilan' => array(
'Ayer Kuning','Ampangan',
'Bahau','Batang Benar',
'Chembong',
'Dangi',
'Gemas',
'Juasseh',
'Kuala Pilah',
'Labu','Lenggeng','Linggi',
'Mantin',
'Nilai',
'Pajam','Pedas','Pengkalan Kempas','Port Dickson',
'Rantau','Rompin',
'Senawang','Seremban','Sungai Gadut',
'Tampin','Tiroi',
),
'pahang' => array(
'Bandar Tun Razak','Bentong','Brinchang','Bukit Fraser','Bukit Tinggi',
'Chendor',
'Gambang','Genting Highlands','Genting Sempah',
'Jerantut',
'Karak','Kemayan','Kota Shahbandar','Kuala Lipis','Kuala Pahang','Kuala Rompin','Kuantan',
'Lanchang','Lubuk Paku',
'Maran','Mengkuang','Mentakab',
'Nenasi',
'Panching',
'Pekan','Penor',
'Raub',
'Sebertak','Sungai Lembing',
'Tanah Rata','Tanjung Sepat','Tasik Chini','Temerloh','Teriang','Tringkap',
),
'penang' => array(
'Air Itam',
'Balik Pulau','Batu Ferringhi','Batu Kawan','Bayan Lepas','Bukit Mertajam','Butterworth',
'Gelugor','George Town',
'Jelutong',
'Kepala Batas',
'Nibong Tebal',
'Permatang Pauh','Pulau Tikus',
'Simpang Ampat',
'Tanjung Bungah','Tanjung Tokong',
),
'perak' => array(
'Ayer Tawar',
'Bagan Serai','Batu Gajah','Behrang','Bidor','Bukit Gantang','Bukit Merah',
'Changkat Jering','Chemor','Chenderiang',
'Damar Laut',
'Gerik','Gopeng','Gua Tempurung',
'Hutan Melintang',
'Ipoh',
'Jelapang',
'Kamunting','Kampar','Kuala Kangsar',
'Lekir','Lenggong','Lumut',
'Malim Nawar','Manong','Menglembu',
'Pantai Remis','Parit','Parit Buntar','Pasir Salak','Proton City',
'Simpang Pulai','Sitiawan','Slim River','Sungai Siput','Sungkai',
'Taiping','Tambun','Tanjung Malim','Tanjung Rambutan','Tapah','Teluk Intan',
'Ulu Bernam',
),
'perlis' => array(
'Arau',
'Beseri',
'Chuping',
'Kaki Bukit','Kangar','Kuala Perlis',
'Mata Ayer',
'Padang Besar',
'Sanglang','Simpang Empat',
'Wang Kelian',
),
'putrajaya' => array(
'Precinct 1','Precinct 4','Precinct 5',
'Precinct 6','Precinct 8','Precinct 10',
'Precinct 11','Precinct 12','Precinct 13',
'Precinct 16','Precinct 18','Precinct 19',
),
'sabah' => array(
'Beaufort','Bingkor',
'Donggongon',
'Inanam',
'Kinabatangan','Kota Belud','Kota Kinabalu','Kuala Penyu','Kimanis','Kundasang',
'Lahad Datu','Likas','Lok Kawi',
'Manggatal',
'Nabawan',
'Papar','Pitas',
'Ranau',
'Sandakan','Sapulut','Semporna','Sepanggar',
'Tambunan','Tanjung Aru','Tawau','Tenom','Tuaran',
'Weston',
),
'sarawak' => array(
'Asajaya',
'Ba\'kelalan','Bario','Batu Kawa','Batu Niah','Betong','Bintulu',
'Dalat','Daro',
'Engkilili',
'Julau',
'Kapit','Kota Samarahan','Kuching',
'Lawas','Limbang','Lubok Antu',
'Marudi','Matu','Miri',
'Oya',
'Pakan',
'Sadong Jaya','Sematan','Sibu','Siburan','Song','Sri Aman','Sungai Tujoh',
'Tanjung Kidurong','Tanjung Manis','Tatau',
),
'selangor' => array(
'Ampang','Assam Jawa',
'Balakong','Bandar Baru Bangi','Bandar Baru Selayang','Bandar Sunway','Bangi','Banting','Batang Kali','Batu Caves','Bestari Jaya','Bukit Lanjan',
'Cheras','Cyberjaya',
'Damansara','Dengkil',
'Ijok',
'Jenjarom',
'Kajang','Kelana Jaya','Klang','Kuala Kubu Bharu','Kuala Selangor','Kuang',
'Lagong',
'Morib',
'Pandamaran','Paya Jaras','Petaling Jaya','Port Klang','Puchong',
'Rasa','Rawang',
'Salak Tinggi','Sekinchan','Selayang','Semenyih','Sepang','Serendah','Seri Kembangan','Shah Alam','Subang','Subang Jaya','Sungai Buloh',
'Tanjung Karang','Tanjung Sepat',
'Ulu Klang','Ulu Yam',
),
'terengganu' => array(
'Ajil',
'Bandar Ketengah Jaya','Bandar Permaisuri','Bukit Besi','Bukit Payong',
'Chukai',
'Jerteh',
'Kampung Raja','Kerteh','Kijal','Kuala Besut','Kuala Berang','Kuala Dungun','Kuala Terengganu',
'Marang','Merchang',
'Pasir Raja',
'Rantau Abang',
'Teluk Kalung',
'Wakaf Tapai',
)
);
/**
* @link https://en.wikipedia.org/wiki/States_and_federal_territories_of_Malaysia
*/
protected static $states = array(
'johor' => array(
'Johor Darul Ta\'zim',
'Johor'
),
'kedah' => array(
'Kedah Darul Aman',
'Kedah'
),
'kelantan' => array(
'Kelantan Darul Naim',
'Kelantan'
),
'kl' => array(
'KL',
'Kuala Lumpur',
'WP Kuala Lumpur'
),
'labuan' => array(
'Labuan'
),
'melaka' => array(
'Malacca',
'Melaka'
),
'nsembilan' => array(
'Negeri Sembilan Darul Khusus',
'Negeri Sembilan'
),
'pahang' => array(
'Pahang Darul Makmur',
'Pahang'
),
'penang' => array(
'Penang',
'Pulau Pinang'
),
'perak' => array(
'Perak Darul Ridzuan',
'Perak'
),
'perlis' => array(
'Perlis Indera Kayangan',
'Perlis'
),
'putrajaya' => array(
'Putrajaya'
),
'sabah' => array(
'Sabah'
),
'sarawak' => array(
'Sarawak'
),
'selangor' => array(
'Selangor Darul Ehsan',
'Selangor'
),
'terengganu' => array(
'Terengganu Darul Iman',
'Terengganu'
)
);
/**
* @link https://ms.wikipedia.org/wiki/Senarai_negara_berdaulat
*/
protected static $country = array(
'Abkhazia','Afghanistan','Afrika Selatan','Republik Afrika Tengah','Akrotiri dan Dhekelia','Albania','Algeria','Amerika Syarikat','Andorra','Angola','Antigua dan Barbuda','Arab Saudi','Argentina','Armenia','Australia','Austria','Azerbaijan',
'Bahamas','Bahrain','Bangladesh','Barbados','Belanda','Belarus','Belgium','Belize','Benin','Bhutan','Bolivia','Bonaire','Bosnia dan Herzegovina','Botswana','Brazil','Brunei Darussalam','Bulgaria','Burkina Faso','Burundi',
'Cameroon','Chad','Chile','Republik Rakyat China','Republik China di Taiwan','Colombia','Comoros','Republik Demokratik Congo','Republik Congo','Kepulauan Cook','Costa Rica','Côte d\'Ivoire (Ivory Coast)','Croatia','Cuba','Curaçao','Cyprus','Republik Turki Cyprus Utara','Republik Czech',
'Denmark','Djibouti','Dominika','Republik Dominika',
'Ecuador','El Salvador','Emiriah Arab Bersatu','Eritrea','Estonia',
'Kepulauan Faroe','Fiji','Filipina','Finland',
'Gabon','Gambia','Georgia','Ghana','Grenada','Greece (Yunani)','Guatemala','Guinea','Guinea-Bissau','Guinea Khatulistiwa','Guiana Perancis','Guyana',
'Habsyah (Etiopia)','Haiti','Honduras','Hungary',
'Iceland','India','Indonesia','Iran','Iraq','Ireland','Israel','Itali',
'Jamaika','Jepun','Jerman','Jordan',
'Kanada','Kazakhstan','Kemboja','Kenya','Kiribati','Korea Selatan','Korea Utara','Kosovo','Kuwait','Kyrgyzstan',
'Laos','Latvia','Lesotho','Liberia','Libya','Liechtenstein','Lithuania','Lubnan','Luxembourg',
'Macedonia','Madagaskar','Maghribi','Malawi','Malaysia','Maldives','Mali','Malta','Kepulauan Marshall','Mauritania','Mauritius','Mesir','Mexico','Persekutuan Micronesia','Moldova','Monaco','Montenegro','Mongolia','Mozambique','Myanmar',
'Namibia','Nauru','Nepal','New Zealand','Nicaragua','Niger','Nigeria','Niue','Norway',
'Oman','Ossetia Selatan',
'Pakistan','Palau','Palestin','Panama','Papua New Guinea','Paraguay','Perancis','Peru','Poland','Portugal',
'Qatar',
'Romania','Russia','Rwanda',
'Sahara Barat','Saint Kitts dan Nevis','Saint Lucia','Saint Vincent dan Grenadines','Samoa','San Marino','São Tomé dan Príncipe','Scotland','Senegal','Sepanyol','Serbia','Seychelles','Sierra Leone','Singapura','Slovakia','Slovenia','Kepulauan Solomon','Somalia','Somaliland','Sri Lanka','Sudan','Sudan Selatan','Suriname','Swaziland','Sweden','Switzerland','Syria',
'Tajikistan','Tanjung Verde','Tanzania','Thailand','Timor Leste','Togo','Tonga','Transnistria','Trinidad dan Tobago','Tunisia','Turki','Turkmenistan','Tuvalu',
'Uganda','Ukraine','United Kingdom','Uruguay','Uzbekistan',
'Vanuatu','Kota Vatican','Venezuela','Vietnam',
'Yaman',
'Zambia','Zimbabwe',
);
/**
* Return a building prefix
*
* @example 'No.'
*
* @return @string
*/
public static function buildingPrefix()
{
return static::randomElement(static::$buildingPrefix);
}
/**
* Return a building number
*
* @example '123'
*
* @return @string
*/
public static function buildingNumber()
{
return static::toUpper(static::lexify(static::numerify(static::randomElement(static::$buildingNumber))));
}
/**
* Return a street prefix
*
* @example 'Jalan'
*/
public function streetPrefix()
{
$format = static::randomElement(static::$streetPrefix);
return $this->generator->parse($format);
}
/**
* Return a complete streename
*
* @example 'Jalan Utama 7'
*
* @return @string
*/
public function streetName()
{
$format = static::toUpper(static::lexify(static::numerify(static::randomElement(static::$streetNameFormats))));
return $this->generator->parse($format);
}
/**
* Return a randown township
*
* @example Taman Bahagia
*
* @return @string
*/
public function township()
{
$format = static::toUpper(static::lexify(static::numerify(static::randomElement(static::$townshipFormats))));
return $this->generator->parse($format);
}
/**
* Return a township prefix abbreviation
*
* @example 'USJ'
*
* @return @string
*/
public function townshipPrefixAbbr()
{
return static::randomElement(static::$townshipPrefixAbbr);
}
/**
* Return a township prefix
*
* @example 'Taman'
*
* @return @string
*/
public function townshipPrefix()
{
return static::randomElement(static::$townshipPrefix);
}
/**
* Return a township suffix
*
* @example 'Bahagia'
*/
public function townshipSuffix()
{
return static::randomElement(static::$townshipSuffix);
}
/**
* Return a postcode based on state
*
* @example '55100'
* @link https://en.wikipedia.org/wiki/Postal_codes_in_Malaysia#States
*
* @param null|string $state 'state' or null
*
* @return @string
*/
public static function postcode($state = null)
{
$format = array(
'perlis' => array( // (01000 - 02800)
'0' . mt_rand(1000, 2800)
),
'kedah' => array( // (05000 - 09810)
'0' . mt_rand(5000, 9810)
),
'penang' => array( // (10000 - 14400)
mt_rand(10000, 14400)
),
'kelantan' => array( // (15000 - 18500)
mt_rand(15000, 18500)
),
'terengganu' => array( // (20000 - 24300)
mt_rand(20000, 24300)
),
'pahang' => array( // (25000 - 28800 | 39000 - 39200 | 49000, 69000)
mt_rand(25000, 28800),
mt_rand(39000, 39200),
mt_rand(49000, 69000)
),
'perak' => array( // (30000 - 36810)
mt_rand(30000, 36810)
),
'selangor' => array( // (40000 - 48300 | 63000 - 68100)
mt_rand(40000, 48300),
mt_rand(63000, 68100)
),
'kl' => array( // (50000 - 60000)
mt_rand(50000, 60000),
),
'putrajaya' => array( // (62000 - 62988)
mt_rand(62000, 62988)
),
'nsembilan' => array( // (70000 - 73509)
mt_rand(70000, 73509)
),
'melaka' => array( // (75000 - 78309)
mt_rand(75000, 78309)
),
'johor' => array( // (79000 - 86900)
mt_rand(79000, 86900)
),
'labuan' => array( // (87000 - 87033)
mt_rand(87000, 87033)
),
'sabah' => array( // (88000 - 91309)
mt_rand(88000, 91309)
),
'sarawak' => array( // (93000 - 98859)
mt_rand(93000, 98859)
)
);
$postcode = is_null($state) ? static::randomElement($format) : $format[$state];
return (string)static::randomElement($postcode);
}
/**
* Return the complete town address with matching postcode and state
*
* @example 55100 Bukit Bintang, Kuala Lumpur
*
* @return @string
*/
public function townState()
{
$state = static::randomElement(array_keys(static::$states));
$postcode = static::postcode($state);
$town = static::randomElement(static::$towns[$state]);
$state = static::randomElement(static::$states[$state]);
return $postcode . ' ' . $town . ', ' . $state;
}
/**
* Return a random city (town)
*
* @example 'Ampang'
*
* @return @string
*/
public function city()
{
$state = static::randomElement(array_keys(static::$towns));
return static::randomElement(static::$towns[$state]);
}
/**
* Return a random state
*
* @example 'Johor'
*
* @return @string
*/
public function state()
{
$state = static::randomElement(array_keys(static::$states));
return static::randomElement(static::$states[$state]);
}
}
<?php
namespace Faker\Provider\ms_MY;
class Company extends \Faker\Provider\Company
{
protected static $formats = array(
'{{companyName}} {{companySuffix}}',
'{{industry}} {{lastNameMalay}} {{companySuffix}}',
'{{industry}} {{firstNameMaleChinese}} {{companySuffix}}',
'{{industry}} {{firstNameMaleIndian}} {{companySuffix}}',
);
/**
* There are more Private Limited Companies(Sdn Bhd) than Public Listed Companies(Berhad)
*
* @link http://www.risscorporateservices.com/types-of-business-entities.html
*/
protected static $companySuffix = array(
'Berhad',
'Bhd',
'Bhd.',
'Enterprise',
'Sdn Bhd','Sdn Bhd','Sdn Bhd','Sdn Bhd',
'Sdn. Bhd.','Sdn. Bhd.','Sdn. Bhd.','Sdn. Bhd.'
);
/**
* @link https://en.wikipedia.org/wiki/List_of_companies_of_Malaysia
*/
protected static $companies = array(
'Adventa','AirAsia','AmBank','Astro Malaysia Holdings','Astro Radio','Axiata',
'Berjaya Group','Bonia','Boustead Holdings','BSA Manufacturing','Bufori','Bumiputra-Commerce Holdings','Bursa Malaysia',
'Capital Dynamics','Celcom','CIMB',
'Digi Telecommunications','DRB-HICOM',
'Edaran Otomobil Nasional (EON)',
'Friendster',
'Gamuda','Genting Group','Golden Hope','Golden Screen Cinemas','Guthrie',
'HELP International Corporation',
'iMoney.my','IOI Group','Iskandar Investment','The Italian Baker',
'Jaring','JobStreet.com','Johor Corporation','Johor Land',
'Khazanah Nasional','Khind Holdings','KLCC Properties','Keretapi Tanah Melayu (KTM)','Konsortium Transnasional (KTB)','Kulim (Malaysia)',
'Lam Eng Rubber','Lion Group',
'Magnum Corporation','Maybank','Malaysia Airlines','Malaysia Airports','Marrybrown','Maxis Communications','MBO Cinemas','Media Prima','MIMOS','MISC','Modenas','MUI Group','Mydin',
'NAZA Group','New Straits Times Press',
'OYL Industries',
'Parkson','Pensonic','Permodalan Nasional','Perodua','Petronas','PLUS','Pos Malaysia','Prasarana Malaysia','Proton Holdings','Public Bank',
'Ramly Group','Ranhill Holdings','Resort World','RHB Bank','Royal Selangor',
'Scientex Incorporated','Scomi','Sime Darby','SIRIM','Sunway Group','Supermax',
'Tan Chong Motor','Tanjong','Tenaga Nasional','Telekom Malaysia(TM)','TGV Cinemas','Top Glove',
'U Mobile','UEM Group','UMW Holdings',
'VADS','ViTrox',
'Wasco Energy',
'YTL Corporation'
);
/**
* @link http://www.daftarsyarikat.biz/perkhidmatan-dan-konsultasi/pendaftaran-lesen-kementerian-kewangan/senarai-kod-bidang/
*/
protected static $industry = array(
'Agen Pengembaraan','Agen Penghantaran','Agen Perkapalan','Agensi Kredit Dan Pemfaktoran','Air','Akseso Kenderaan','Aksesori','Aksesori Jentera Berat','Aksesori Penghubung Dan Telekomunikasi','Aksesori Senjata Api','Akuatik','Akustik Dan Gelombang','Alat Forensik Dan Aksesori','Alat Gani','Alat Ganti','Alat Ganti Dan Kelengkapan Bot','Alat Hawa Dingin','Alat Hawa Dingin Kenderaan','Alat Kebombaan','Alat Kelengkapan Perubatan','Alat Keselamatan, Perlindungan Dan Kawalan Perlindungan Dan Kawalan','Alat Muzik Dan Aksesori','Alat Muzik, Kesenian dan Aksesori','Alat Penghasil Nyalaan','Alat penyelamat','Alat Penyimpan Tenaga Dan Aksesori','Alat Perhubungan','Alat Semboyan','Alat-Alat Marin','Alatganti Dan Kelengkapan Pesawat','Alatulis','Animation','Anti Kakis','Artis Dan Penghibur Profesional','Audio Visual',
'Bagasi Dan Beg dari kulit','Bahan Api Nuklear','Bahan Bacaan','Bahan Bacaan Terbitan Luar Negara','Bahan Bakar','Bahan Binaan','Bahan dan Peralatan Solekan dan Andaman','Bahan Letupan','Bahan Peledak','Bahan Pelincir','Bahan pembungkusan','Bahan Pencuci Dan Pembersihan','Bahan Pendidikan','Bahan Penerbitan Elektronik Dan Muzik','Bahan Surih, Drafting Dan Alat Lukis','Bahan Tambah','Bahan Tarpaulin Dan Kanvas','Baik Pulih Kasut Dan Barangan Kulit','Baikpulih Barang-Barang Logam','Baja Dan Nutrien Tumbuhan','Baka','Bangunan','Bantuan Kecemasan DanAmbulan','Bantuan Kemanusiaan','Barangan Hiasan Dalaman Dan Aksesori','Barangan PVC','Barge','Bas','Basah','Basikal','Bekalan Pejabat Dan Alatulis','Bekas','Belon Panas','Benih Semaian','Bill Board','Bioteknologi','Bot','Bot Malim','Bot Tunda','Brangan Logam','Broker Insuran','Broker Perkapalan','Bunga Api Dan Mercun','Butang Dan Bekalan Jahitan',
'Cat','Cenderamata Dan Hadiah','Cetakan Hologram','Cetakan Keselamatan','Chalet','Cloud Seeding','Complete Rounds','Customization and maintenance including data',
'Dadah Berjadual','Dan Aksesori','Darat','Dasar Dan Peraturan','Data management –Provide services including Disaster','Dll','DNA','Dobi','Dokumentasi Dan Panduarah',
'Elektronik','Empangan','Enjin Kenderaan','Enjin, Komponen Enjin Dan Aksesori','Entry, data processing',
'Fabrik','Faksimili','Feri','Filem dan Mikrofilem','Filem Siap Untuk Tayangan','Fotografi',
'Gas','Gas Turbine','Geographic Information System','Geologi','Graphic Design',
'Habitat Dan Tempat Kurungan Haiwan','Haiwan Ternakan, Bukan Ternakan dan Akuatik','Hak Harta Intelek','Hardware','Hardware','Hardware and Software leasing','Hasil Sampingan Dan Sisa Perladangan','Helikopter','Hiasan Dalaman','Hiasan Jalan','Hidrografi','Homestay','Hortikultur','Hotel','Hubungan Antarabangsa','Hutan Dan Ladang Hutan',
'ICT security and firewall, Encryption, PKI, Anti Virus','Industri','Infrastructure','Internet',
'Jentera','Jentera Berat','Jentera Berat','Jet Ski',
'Kabel Elektrik Dan Aksesori','Kain','Kajian Telekomunikasi','Kakitangan Iktisas','Kakitangan Separa Iktisas','Kamera dan Aksesori','Kanvas','Kapal','Kapal Angkasa Dan Alatganti','Kapal Laut','Kapal Selam','Kapal Selam','Kapal Terbang','Kawalan Keselamatan','Kawalan Serangga Perosak, Anti Termite','Kawasan','Kayu','Kediaman','Kelengkapan','Kelengkapan Dan Aksesori','Kelengkapan Hospital Dan Makmal','Kelengkapan Pakaian','Kelengkapan Sasaran','Kemudahan Awam','Kemudahan Awam','Kenderaan','Kenderaan Bawah 3 Ton','Kenderaan Ber Rel Dan kereta Kabel','Kenderaan Jenazah','Kenderaan Kegunaan Khusus','Kenderaan Kegunaan Khusus','Kenderaan Melebihi 3Ton','Kenderaan Rekreasi','Kenderaan Udara','Kereta','Kerja Pembaikan Kapal Angkasa','Kerja-Kerja Khusus','Kerja-kerja Mengetuk dan Mengecat','Kerja-Kerja Pembaikan Kenderaan Ber Rel Dan Kereta Kabel','Kerja-Kerja Penyelenggaraan Sistem Kenderaan','Kertas','Kertas Komputer','Khidmat Guaman','Khidmat Latihan, Tenaga Pengajar dan Moderator','Khidmat Udara','Kit Pendidikan','Kodifikasi','Kolam Kumbahan','Komponen Dan Aksesori Elektrik','Komponen Enjin Pembakaran Dalaman','Kontena','Kotak','Kren','Kunci, Perkakasan Perlindungan Dan Aksesori','Kusyen dan Bumbung',
'Label','Ladang','Lagu','Lain-lain Media Pengiklanan','Laminating','Lampu, Komponen Lampu Dan Aksesori','Laut','Lesen','LIDAR','Lilin','Logam','Lokomotif Dan Troli Elektrik','Lori',
'Maintenance','Makanan','Makanan Bermasak','Makanan Bermasak','Makanan Dan Bahan Mentah Kering','Makanan dan Minuman','Makanan Haiwan','Makmal','Malim Kapal','Marker','Mechanisation System','Media Cetak','Media Elektronik','Medium Penyimpanan','Membaik Pulih Bateri','Membaik Pulih Tayar','Membaik Pulih TempatDuduk','Membaiki Buff Fuel Tank','Membaikpulih BahanTerbitan Dan Manuskrip','Membekal Air','Membeli Barang Lusuh Perlu Permit','Membeli Barang Lusuh Tanpa Permit','Membersih Kawasan','Membersih Kenderaan','Membersih Pantai','Memproses Air','Memproses Filem','Menangkap','Mencetak Borang','Mencetak Buku, Majalah, Laporan Akhbar','Mencetak Continuous Stationery Forms','Mencetak Fail, Kad Perniagaan Dan Kad Ucapan','Mencetak Label, Poster dan Pelekat','Mencetak Label, Poster, Pelekat dan Iron On','Mencuci Kolam Renang','Menembak Haiwan','Mengangkat Sampah','Mengangkut Mayat','Mengikat Dan Melepas Tali Kapal','Menjahit Bukan Pakaian','Menjahit Pakaian Dan Kelengkapan','Menjilid Kulit Keras','Menjilid Kulit Lembut','Menyelam','Mereka-Cipta Dan Seni Halus','Mesin Dan Kelengkapan Bengkel','Mesin dan Kelengkapan Khusus','Mesin dan peralatan makmal','Mesin dan Peralatan Pejabat','Mesin dan Peralatan Woksyop','Mesin Pengimbas','Mesin-Mesin Pejabat','Mesin-Mesin Pejabat Dan Aksesori','Minuman Tambahan','Motel','Motor Dan Alat Ubah','Motosikal','Multimedia-products services and maintenance','Multimodal Transport Operator',
'Negotiator','Networking-supply','Nylon',
'Oceanografi',
'P.A Sistem Dan Alat Muzik','Paip Air Dan Komponen','Paip Dan Kelengkapan','Pakaian','Pakaian Keselamatan, Kelengkapan Dan Aksesori','Pakaian Sukan Dan Aksesori','Palet','Pameran pertunjukan, taman hiburan dan karnival','Papan Tanda dan Aksesori','Pejabat','Pekakas Perubatan Pakai Buang','Pelancar Misil Dan Roket','Pelupusan Dan Perawatan Sisa berbahaya','Pelupusan Dan Perawatan Sisa tidak berbahaya','Pelupusan dan Rawatan Sisa Radio Aktif dan Nuklear','Peluru Berpandu','Peluru Dan Bom','Pemadam Api','Pembaikan Alat Keselamatan','Pembaikan Kenderaan Yang Tidak Berenjin','Pembajaan','Pembersihan Bangunan Dan Pejabat','Pembersihan Tumpahan Minyak','Pembuat','Pembuat','Pembuat Keselamatan','Pembungkusan','Pembungkusan Dan Penyimpanan','Pemeliharaan Bahan Bahan Sejarah Dan Tempat Bersejarah','Pemetaan','Pemetaan Utiliti Bawah Tanah','Pemilik Kapal','Pemungut Hutang','Pencahayaan','Pencelup','Pencucuh','Penerbitan Elektronik Atas Talian','Pengangkutan Lori','Pengatur Huruf','Pengeluaran Filem','Pengenalan Dan Pas Keselamatan Bersalut','Penghantar Notis','Penghantaran Dokumen','Pengkomersilan','Pengurusan Jenazah Dan Kelengkapan','Pengurusan Kewangan Dan Korporat','Pengurusan Pelabuhan','Penjana Kuasa','Pensijilan dan Pengiktirafan','Penterjemahan','Penulisan – Semua Jenis Penulisan','Penyediaan Akaun dan Pengauditan','Penyediaan Pentas','Penyelenggaraan','Penyelenggaraan Kapal Terbang','Penyelenggaraan Misil','Penyelenggaraan Simulator Helikopter','Penyelenggaraan Simulator Kapal','Penyelenggaraan Simulator Kapal Terbang','PenyelenggaraanHelikopter','Penyelenggaran Dan Pembaikan Senjata','Penyiaran','Penyiasat Persendirian','Penyimpanan Rekod',
'Perabot','Perabot Jalan Raya','Perabot Pejabat','Perabot, Perabot Makmal dan Kelengkapan Berasaskan','Peralatan','Peralatan Dan Kelengkapan Hospital','Peralatan Dan Kelengkapan Pertanian','Peralatan Dan Kelengkapan Perubatan','Peralatan Dan Perkakas Domestik','Peralatan Kawalan Api','Peralatan Kawalan Keselamatan','Peralatan Keselamatan','Peralatan Keselamatan dan Senjata','Peralatan Makmal Pengukuran, Pencerapan Dan Sukat','Peralatan Makmal serta Aksesori','Peralatan Marin','Peralatan Memancing','Peralatan Memburu','Peralatan Pemantauan Dan Pengesanan','Peralatan Pemprosesan Fotografi, Mikrofilem','Peralatan Pengawalan Perosak Tanaman','Peralatan Percetakan Serta Aksesori','Peralatan Perindustrian Hiliran','Peralatan Perindustrian Huluan','Peralatan Perkhemahan Dan Aktiviti Luar','Peralatan Servis Dan Selenggara','Peralatan Sistem Bunyi, Pembesar Suara dan Projektor','Peralatan Sistem Kumbahan Dan Aksesori','Peralatan Sukan','Peralatan Untuk Orang Kurang Upaya Dan Pemulihan','Perhubungan','Perikanan Dan Akuakultur','Perkakas','Perkakas Elektrik Dan Aksesori','Perkakas Elektronik Dan Aksesori','Perkakasan Dan Bahan Kebersihan Diri Dan Mandian, Kelengkapan Bilik Air','Perkakasan Penyuntingan','Perkhidmatan Fotostat','Perkhidmatan Mel Pukal','Permainan','Perosak, Rumpai','Persembahan','Pertanian','Perundingan','Pesakit','Pesawat','Pesawat Udara','Pest Control','Pestaria','Pewarna','Pisah Warna','Plastik','Plastik','Printers, storage area network','Production Testing, Surface Well Testing and Wire Line Services','Pump','Pusat Latihan','Pvc',
'Racun Berjadual','Racun Serangga','Radar Dan Alatganti','Rakaman','Rawatan Hutan','Reaktor dan Instrumen Nuklear','Rekabentuk Percetakan','Renting','Resort','Roket Dan Sub Sistem, Pelancar','Rotan','Ruang Niaga','Rumah Kediaman','Rumah Tumpangan',
'Salvage Boat','Sampan','Sampel dan Sampel Awetan Haiwan','Sand Blasting Dan Mengecat Untuk Kapal','Satelit','Satelit Dan Alatganti','Semua Peralatan Sukatan','Senjata Api','Serangga','Sesalur','Shelf packages including maintenance','Ship Chandling','Ship Trimming','Simulator','Simulator Bot','Simulator serta lain-lain','Sisa Perawatan','Sistem Elektrik','Sistem Elektronik','Sistem Pencegah Kebakaran','Sistem Perhubungan','Sistem, Peralatan, Alat Ganti Keretapi Dan Aksesori','Software','Solekan','Split','Stesen Janakuasa, Peralatan','Stevedor','Stor','Sub Sistem Roket','Sukan','Sumber Air','Sungai','Syarikat Insuran','Syarikat pelelong awam','System development',
'Tag','Talian Paip','Taman','Tanaman','Tanda Dan Stiker','Tangki','Tasik','Tatahias Haiwan','Teknologi Hijau','Teknologi Maklumat Dan Komunikasi','Tekstil','Tekstil Guna Semula Kakitangan','Tekstil Pakai Buang Kakitangan','Telecommunication','Telekomunikasi','Telly Clerk','Tempat Letak Kereta','Tenaga Buruh','Ternakan','Terusan','Topografi','Trailer Dan Aksesori','Tukun Tiruan','Tumbuhan',
'Ubat Haiwan','Ubat Tidak Berjadual','Ujian Makmal','Ukuran',
'Varnishing',
'WAN','Wayar Elektrik Dan Aksesori','Wireless'
);
/**
* Return a random company name
*
* @example 'AirAsia'
*/
public static function companyName()
{
return static::randomElement(static::$companies);
}
/**
* Return a random industry
*
* @example 'Automobil'
*/
public static function industry()
{
return static::randomElement(static::$industry);
}
}
<?php
namespace Faker\Provider\ms_MY;
class Miscellaneous extends \Faker\Provider\Miscellaneous
{
/**
* @link https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Malaysia
*/
protected static $jpjNumberPlateFormats = array(
'{{peninsularPrefix}}{{validAlphabet}}{{validAlphabet}} {{numberSequence}}',
'{{peninsularPrefix}}{{validAlphabet}}{{validAlphabet}} {{numberSequence}}',
'{{peninsularPrefix}}{{validAlphabet}}{{validAlphabet}} {{numberSequence}}',
'{{peninsularPrefix}}{{validAlphabet}}{{validAlphabet}} {{numberSequence}}',
'W{{validAlphabet}}{{validAlphabet}} {{numberSequence}} {{validAlphabet}}',
'KV {{numberSequence}} {{validAlphabet}}',
'{{sarawakPrefix}} {{numberSequence}} {{validAlphabet}}',
'{{sabahPrefix}} {{numberSequence}} {{validAlphabet}}',
'{{specialPrefix}} {{numberSequence}}',
);
/**
* Some alphabet has higher frequency that coincides with the current number
* of registrations. E.g. W = Wilayah Persekutuan
*
* @link https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Malaysia#Current_format
*/
protected static $peninsularPrefix = array(
'A','A','B','C','D','F','J','J','K','M','N','P','P','R','T','V',
'W','W','W','W','W','W',
);
/**
* @link https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Malaysia#Current_format_2
*/
protected static $sarawakPrefix = array(
'QA','QK','QB','QC','QL','QM','QP','QR','QS','QT'
);
/**
* @link https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Malaysia#Current_format_3
*/
protected static $sabahPrefix = array(
'SA','SAA','SAB','SAC','SB','SD','SG',
'SK','SL','SS','SSA','ST','STA','SU'
);
/**
* @link https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Malaysia#Commemorative_plates
*/
protected static $specialPrefix = array(
'1M4U',
'A1M',
'BAMbee',
'Chancellor',
'G','G1M','GP','GT',
'Jaguh',
'K1M','KRISS',
'LOTUS',
'NAAM','NAZA','NBOS',
'PATRIOT','Perdana','PERFECT','Perodua','Persona','Proton','Putra','PUTRAJAYA',
'RIMAU',
'SAM','SAS','Satria','SMS','SUKOM',
'T1M','Tiara','TTB',
'U','US',
'VIP',
'WAJA',
'XIIINAM','XOIC','XXVIASEAN','XXXIDB',
'Y'
);
/**
* Chances of having an empty alphabet will be 1/24
*
* @link https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Malaysia#Current_format
*/
protected static $validAlphabets = array(
'A','B','C','D','E','F',
'G','H','J','K','L','M',
'N','P','Q','R','S','T',
'U','V','W','X','Y',''
);
/**
* Return a valid Malaysia JPJ(Road Transport Department) vehicle licence plate number
*
* @example 'WKN 2368'
*
* @return @string
*/
public function jpjNumberPlate()
{
$formats = static::toUpper(static::lexify(static::bothify(static::randomElement(static::$jpjNumberPlateFormats))));
return $this->generator->parse($formats);
}
/**
* Return Peninsular prefix alphabet
*
* @example 'W'
*
* @return @string
*/
public static function peninsularPrefix()
{
return static::randomElement(static::$peninsularPrefix);
}
/**
* Return Sarawak state prefix alphabet
*
* @example 'QA'
*
* @return @string
*/
public static function sarawakPrefix()
{
return static::randomElement(static::$sarawakPrefix);
}
/**
* Return Sabah state prefix alphabet
*
* @example 'SA'
*
* @return @string
*/
public static function sabahPrefix()
{
return static::randomElement(static::$sabahPrefix);
}
/**
* Return specialty licence plate prefix
*
* @example 'G1M'
*
* @return @string
*/
public static function specialPrefix()
{
return static::randomElement(static::$specialPrefix);
}
/**
* Return a valid license plate alphabet
*
* @example 'A'
*
* @return @string
*/
public static function validAlphabet()
{
return static::randomElement(static::$validAlphabets);
}
/**
* Return a valid number sequence between 1 and 9999
*
* @example '1234'
*
* @return @integer
*/
public static function numberSequence()
{
return mt_rand(1, 9999);
}
}
<?php
namespace Faker\Provider\ms_MY;
class Payment extends \Faker\Provider\Payment
{
protected static $bankFormats = array(
'{{localBank}}',
'{{foreignBank}}',
'{{governmentBank}}'
);
/**
* @link http://www.muamalat.com.my/consumer-banking/internet-banking/popup-ibg.html
*/
protected static $bankAccountNumberFormats = array(
'##########',
'###########',
'############',
'#############',
'##############',
'###############',
'################',
);
/**
* @link https://en.wikipedia.org/wiki/List_of_banks_in_Malaysia
*/
protected static $localBanks = array(
'Affin Bank',
'Alliance Bank',
'AmBank',
'CIMB Bank',
'Hong Leong Bank ',
'Maybank',
'Public Bank',
'RHB Bank'
);
/**
* @link https://en.wikipedia.org/wiki/List_of_banks_in_Malaysia#List_of_foreign_banks_(commercial)
*/
protected static $foreignBanks = array(
'Bangkok Bank Berhad',
'Bank of America Malaysia Berhad',
'Bank of China (Malaysia) Berhad',
'Bank of Tokyo-Mitsubishi UFJ (Malaysia) Berhad',
'BNP Paribas Malaysia Berhad',
'China Construction Bank',
'Citibank Berhad',
'Deutsche Bank (Malaysia) Berhad',
'HSBC Bank Malaysia Berhad',
'India International Bank (Malaysia) Berhad',
'Industrial and Commercial Bank of China (Malaysia) Berhad',
'J.P. Morgan Chase Bank Berhad',
'Mizuho Bank (Malaysia) Berhad',
'National Bank of Abu Dhabi Malaysia Berhad',
'OCBC Bank (Malaysia) Berhad',
'Standard Chartered Bank Malaysia Berhad',
'Sumitomo Mitsui Banking Corporation Malaysia Berhad',
'The Bank of Nova Scotia Berhad',
'United Overseas Bank (Malaysia) Bhd.'
);
/**
* @link https://en.wikipedia.org/wiki/List_of_banks_in_Malaysia#Development_Financial_Institutions_(Government-owned_banks)_(full_list)
*/
protected static $governmentBanks = array(
'Agro Bank Malaysia',
'Bank Pembangunan Malaysia Berhad (BPMB) (The development bank of Malaysia)',
'Bank Rakyat',
'Bank Simpanan Nasional',
'Credit Guarantee Corporation Malaysia Berhad (CGC)',
'Export-Import Bank of Malaysia Berhad (Exim Bank)',
'Malaysia Debt Ventures Berhad',
'Malaysian Industrial Development Finance Berhad (MIDF)',
'SME Bank Berhad',
'Sabah Development Bank Berhad (SDB)',
'Sabah Credit Corporation (SCC)',
'Tabung Haji',
);
/**
* @link https://en.wikipedia.org/wiki/List_of_banks_in_Malaysia#Investment-Link_Funds_(Insurance_Companies_-_Takaful_included)
*/
protected static $insuranceCompanies = array(
'AIA Malaysia',
'AIG Malaysia',
'Allianz Malaysia',
'AXA AFFIN Life Insurance',
'Berjaya General Insurance',
'Etiqa Insurance',
'Great Eastern Insurance',
'Hong Leong Assurance',
'Kurnia Insurans Malaysia',
'Manulife Malaysia Insurance',
'MSIG Malaysia',
'Prudential Malaysia',
'Tokio Marine Life Malaysia Insurance',
'UNI.ASIA General Insurance',
'Zurich Insurance Malaysia',
);
/**
* @link http://www.bankswiftcode.org/malaysia/
*/
protected static $swiftCodes = array(
'ABNAMY2AXXX','ABNAMYKLPNG','ABNAMYKLXXX','AFBQMYKLXXX','AIBBMYKLXXX',
'AISLMYKLXXX','AMMBMYKLXXX','ARBKMYKLXXX',
'BIMBMYKLXXX','BISLMYKAXXX','BKCHMYKLXXX','BKKBMYKLXXX','BMMBMYKLXXX',
'BNMAMYKLXXX','BNPAMYKAXXX','BOFAMY2XLBN','BOFAMY2XXXX','BOTKMYKAXXX',
'BOTKMYKXXXX',
'CHASMYKXKEY','CHASMYKXXXX','CIBBMYKAXXX','CIBBMYKLXXX','CITIMYKLJOD',
'CITIMYKLLAB','CITIMYKLPEN','CITIMYKLXXX','COIMMYKLXXX','CTBBMYKLXXX',
'DABEMYKLXXX','DBSSMY2AXXX','DEUTMYKLBLB','DEUTMYKLGMO','DEUTMYKLISB',
'DEUTMYKLXXX',
'EIBBMYKLXXX','EOBBMYKLXXX','EXMBMYKLXXX',
'FEEBMYKAXXX',
'HBMBMYKLXXX','HDSBMY2PSEL','HDSBMY2PXXX','HLBBMYKLIBU','HLBBMYKLJBU',
'HLBBMYKLKCH','HLBBMYKLPNG','HLBBMYKLXXX','HLIBMYKLXXX','HMABMYKLXXX',
'HSBCMYKAXXX','HSTMMYKLGWS','HSTMMYKLXXX',
'KAFBMYKLXXX','KFHOMYKLXXX',
'MBBEMYKAXXX','MBBEMYKLBAN','MBBEMYKLBBG','MBBEMYKLBWC','MBBEMYKLCSD',
'MBBEMYKLIPH','MBBEMYKLJOB','MBBEMYKLKEP','MBBEMYKLKIN','MBBEMYKLKLC',
'MBBEMYKLMAL','MBBEMYKLPEN','MBBEMYKLPGC','MBBEMYKLPJC','MBBEMYKLPJY',
'MBBEMYKLPKG','MBBEMYKLPSG','MBBEMYKLPUD','MBBEMYKLSAC','MBBEMYKLSBN',
'MBBEMYKLSHA','MBBEMYKLSUB','MBBEMYKLWSD','MBBEMYKLXXX','MBBEMYKLYSL',
'MFBBMYKLXXX','MHCBMYKAXXX',
'NOSCMY2LXXX','NOSCMYKLXXX',
'OABBMYKLXXX','OCBCMYKLXXX','OSKIMYKLXXX',
'PBBEMYKLXXX','PBLLMYKAXXX','PCGLMYKLXXX','PERMMYKLXXX','PHBMMYKLXXX',
'PTRDMYKLXXX','PTROMYKLFSD','PTROMYKLXXX',
'RHBAMYKLXXX','RHBBMYKAXXX','RHBBMYKLXXX','RJHIMYKLXXX',
'SCBLMYKXLAB','SCBLMYKXXXX','SMBCMYKAXXX',
'UIIBMYKLXXX','UOVBMYKLCND','UOVBMYKLXXX',
);
/**
* @link https://en.wikipedia.org/wiki/Malaysian_ringgit
*/
protected static $currencySymbol = array(
'RM'
);
/**
* Return a Malaysian Bank
*
* @example 'Maybank'
*
* @return @string
*/
public function bank()
{
$formats = static::randomElement(static::$bankFormats);
return $this->generator->parse($formats);
}
/**
* Return a Malaysian Bank account number
*
* @example '1234567890123456'
*
* @return @string
*/
public function bankAccountNumber()
{
$formats = static::randomElement(static::$bankAccountNumberFormats);
return static::numerify($formats);
}
/**
* Return a Malaysian Local Bank
*
* @example 'Public Bank'
*
* @return @string
*/
public static function localBank()
{
return static::randomElement(static::$localBanks);
}
/**
* Return a Malaysian Foreign Bank
*
* @example 'Citibank Berhad'
*
* @return @string
*/
public static function foreignBank()
{
return static::randomElement(static::$foreignBanks);
}
/**
* Return a Malaysian Government Bank
*
* @example 'Bank Simpanan Nasional'
*
* @return @string
*/
public static function governmentBank()
{
return static::randomElement(static::$governmentBanks);
}
/**
* Return a Malaysian insurance company
*
* @example 'AIA Malaysia'
*
* @return @string
*/
public static function insurance()
{
return static::randomElement(static::$insuranceCompanies);
}
/**
* Return a Malaysian Bank SWIFT Code
*
* @example 'MBBEMYKLXXX'
*
* @return @string
*/
public static function swiftCode()
{
return static::toUpper(static::lexify(static::randomElement(static::$swiftCodes)));
}
/**
* Return the Malaysian currency symbol
*
* @example 'RM'
*
* @return @string
*/
public static function currencySymbol()
{
return static::randomElement(static::$currencySymbol);
}
}
<?php
namespace Faker\Provider\ms_MY;
use Faker\Provider\DateTime;
use Faker\Generator;
class Person extends \Faker\Provider\Person
{
protected static $firstNameFormat = array(
'{{firstNameMaleMalay}}',
'{{firstNameFemaleMalay}}',
'{{firstNameMaleChinese}}',
'{{firstNameFemaleChinese}}',
'{{firstNameMaleIndian}}',
'{{firstNameFemaleIndian}}',
'{{firstNameMaleChristian}}',
'{{firstNameFemaleChristian}}',
);
/**
* @link https://en.wikipedia.org/wiki/Malaysian_names
*/
protected static $maleNameFormats = array(
//Malay
'{{muhammadName}}{{haji}}{{titleMaleMalay}}{{firstNameMaleMalay}} {{lastNameMalay}} bin {{titleMaleMalay}}{{firstNameMaleMalay}} {{lastNameMalay}}',
'{{muhammadName}}{{haji}}{{titleMaleMalay}}{{firstNameMaleMalay}} {{lastNameMalay}} bin {{titleMaleMalay}}{{firstNameMaleMalay}}',
'{{muhammadName}}{{haji}}{{titleMaleMalay}}{{firstNameMaleMalay}} {{lastNameMalay}} bin {{titleMaleMalay}}{{lastNameMalay}}',
'{{muhammadName}}{{haji}}{{titleMaleMalay}}{{firstNameMaleMalay}} {{lastNameMalay}}',
'{{muhammadName}}{{haji}}{{titleMaleMalay}}{{firstNameMaleMalay}} bin {{titleMaleMalay}}{{firstNameMaleMalay}} {{lastNameMalay}}',
'{{muhammadName}}{{haji}}{{titleMaleMalay}}{{firstNameMaleMalay}} bin {{titleMaleMalay}}{{firstNameMaleMalay}}',
'{{muhammadName}}{{haji}}{{titleMaleMalay}}{{firstNameMaleMalay}} bin {{titleMaleMalay}}{{lastNameMalay}}',
//Chinese
'{{lastNameChinese}} {{firstNameMaleChinese}}',
'{{lastNameChinese}} {{firstNameMaleChinese}}',
'{{lastNameChinese}} {{firstNameMaleChinese}}',
'{{lastNameChinese}} {{firstNameMaleChinese}}',
'{{lastNameChinese}} {{firstNameMaleChinese}}',
'{{firstNameMaleChristian}} {{lastNameChinese}} {{firstNameMaleChinese}}',
//Indian
'{{initialIndian}} {{firstNameMaleIndian}}',
'{{initialIndian}} {{lastNameIndian}}',
'{{firstNameMaleIndian}} a/l {{firstNameMaleIndian}}',
'{{firstNameMaleIndian}} a/l {{firstNameMaleIndian}} {{lastNameIndian}}',
'{{firstNameMaleIndian}} {{lastNameIndian}} a/l {{lastNameIndian}}',
'{{firstNameMaleIndian}} {{lastNameIndian}} a/l {{firstNameMaleIndian}} {{lastNameIndian}}',
'{{firstNameMaleIndian}} {{lastNameIndian}}',
);
/**
* @link https://en.wikipedia.org/wiki/Malaysian_names
*/
protected static $femaleNameFormats = array(
//Malay
'{{nurName}}{{hajjah}}{{firstNameFemaleMalay}} {{lastNameMalay}} binti {{titleMaleMalay}}{{firstNameMaleMalay}} {{lastNameMalay}}',
'{{nurName}}{{hajjah}}{{firstNameFemaleMalay}} {{lastNameMalay}} binti {{titleMaleMalay}}{{firstNameMaleMalay}}',
'{{nurName}}{{hajjah}}{{firstNameFemaleMalay}} {{lastNameMalay}} binti {{titleMaleMalay}}{{lastNameMalay}}',
'{{nurName}}{{hajjah}}{{firstNameFemaleMalay}} {{lastNameMalay}}',
'{{nurName}}{{hajjah}}{{firstNameFemaleMalay}} binti {{titleMaleMalay}}{{firstNameMaleMalay}} {{lastNameMalay}}',
'{{nurName}}{{hajjah}}{{firstNameFemaleMalay}} binti {{titleMaleMalay}}{{firstNameMaleMalay}}',
'{{nurName}}{{hajjah}}{{firstNameFemaleMalay}} binti {{titleMaleMalay}}{{lastNameMalay}}',
//Chinese
'{{lastNameChinese}} {{firstNameFemaleChinese}}',
'{{lastNameChinese}} {{firstNameFemaleChinese}}',
'{{lastNameChinese}} {{firstNameFemaleChinese}}',
'{{lastNameChinese}} {{firstNameFemaleChinese}}',
'{{lastNameChinese}} {{firstNameFemaleChinese}}',
'{{firstNameFemaleChristian}} {{lastNameChinese}} {{firstNameFemaleChinese}}',
//Indian
'{{initialIndian}}{{firstNameFemaleIndian}}',
'{{initialIndian}}{{lastNameIndian}}',
'{{firstNameFemaleIndian}} a/l {{firstNameMaleIndian}}',
'{{firstNameFemaleIndian}} a/l {{firstNameMaleIndian}} {{lastNameIndian}}',
'{{firstNameFemaleIndian}} {{lastNameIndian}} a/l {{firstNameMaleIndian}}',
'{{firstNameFemaleIndian}} {{lastNameIndian}} a/l {{firstNameMaleIndian}} {{lastNameIndian}}',
'{{firstNameFemaleIndian}} {{lastNameIndian}}',
);
/**
* @link https://en.wikipedia.org/wiki/List_of_Malay_people
* @link https://samttar.edu.my/senarai-nama-pelajar-2016/
* @link http://smkspkl.edu.my/senarai-nama-pelajar
*/
protected static $firstNameMaleMalay = array(
'A','A.r','A\'fif','A\'zizul','Ab','Abadi','Abas','Abd','Abd.','Abd.rahim','Abdel','Abdul','Abdull','Abdullah','Abdulloh','Abu','Adam','Adi','Adib','Adil','Adnan','Ady','Adzmin','Afandy','Afif','Afiq','Afza','Agus','Ahmad','Ahmat','Ahmed','Ahwali','Ahyer','Aidid','Aidil','Aiman','Aimman','Ainol','Ainuddin','Ainul','Aizad','Aizam','Aizat','Aizuddin','Ajis','Ajmal','Ajwad','Akhmal','Akid','Akif','Akmal','Al','Al-afnan','Al-muazrar','Alfian','Ali','Alias','Alif','Aliff','Alilah','Alin','Allif','Amaanullah','Amami','Aman','Amar','Ameershah','Amier','Amierul','Amil','Amin','Aminuddin','Amir','Amiruddin','Amirul','Ammar','Amran','Amri','Amru','Amrullah','Amsyar','Anas','Andri','Aniq','Anuar','Anuwar','Anwar','Aqeel','Aqif','Aqil','Arash','Arbani','Arefin','Arief','Arif','Arifen','Ariff','Ariffin','Arifin','Armi','Ashraf','Ashraff','Ashrof','Ashrul','Aslam','Asmawi','Asmin','Asmuri','Asraf','Asri','Asrialif','Asror','Asrul','Asymawi','Asyraaf','Asyraf','Atan','Athari','Awaludin','Awira','Azam','Azely','Azfar','Azhan','Azhar','Azhari','Azib','Azim','Aziz','Azizan','Azizul','Azizulhasni','Azlan','Azlee','Azli','Azman','Azmi','Azmie','Azmin','Aznan','Aznizam','Azraai','Azri','Azrie','Azrien','Azril','Azrin','Azrul','Azry','Azuan',
'Badri','Badrullesham','Baharin','Baharuddin','Bahrul','Bakri','Basaruddin','Basiran','Basirin','Basri','Basyir','Bazli','Borhan','Buang','Budi','Bukhari','Bukharudin','Bustaman','Buyung',
'Chailan',
'Dahalan','Dailami','Dan','Danial','Danie','Daniel','Danien','Danish','Darimin','Darul','Darus','Darwisy','Dhiyaulhaq','Diah','Djuhandie','Dolbahrin','Dolkefli','Dzikri','Dzul','Dzulfahmi','Dzulfikri','Dzulkarnaen',
'Eazriq','Effendi','Ehza','Eizkandar','Ekhsan','Elyas','Enidzullah','Ezam','Ezani',
'Fadhil','Fadly','Fadzil','Fadziruddin','Fadzli','Fahmi','Faiq','Fairuz','Faisal','Faiz','Faizal','Faizurrahman','Fakhrul','Fakhrullah','Farham','Farhan','Farid','Faris','Farisan','Fariz','Fasil','Fateh','Fathi','Fathuddin','Fathul','Fauzan','Fauzi','Fauzul','Fawwaz','Fazal','Fazly','Fazreen','Fazril','Fendi','Fikri','Fikrie','Fikrul','Firdaus','Fithri','Fitiri','Fitri','Fuad',
'Ghazali',
'Habib','Haddad','Hadi','Hadif','Hadzir','Haffize','Haffizi','Hafidzuddin','Hafis','Hafiy','Hafiz','Hafizan','Hafizhan','Hafizi','Hafizsyakirin','Hafizuddin','Haikal','Haiqal','Hairol','Hairollkahar','Hairuddin','Hairul','Hairun','Haisyraf','Haizan','Hakeem','Hakim','Hakimi','Hakimie','Halidan','Haliem','Halim','Hamdani','Hamidoon','Hamizan','Hamka','Hamzah','Hanafi','Hanif','Hanit','Hannan','Haqeem','Haqimie','Harez','Haris','Harith','Hariz','Harmaini','Harraz','Harun','Hasan','Hashim','Hasif','Hasnul','Hasrin','Hasrol','Hassan','Hasyim','Haszlan','Hayani','Hazim','Haziq','Haziqh','Hazrie','Hazrul','Hazwan','Hazzam','Helmy','Hermansah','Hidayat','Hidayatullah','Hilmi','Hisam','Hisammudin','Hisyam','Hj','Hoirussalam','Humadu','Hurmin','Husain','Husaini','Husnul','Hussein','Hussin','Huzaifi','Huzaimi','Huzzaini',
'Ibnu','Ibrahim','Idham','Idlan','Idris','Idrus','Idzwan','Ielman','Ighfar','Ihsan','Ikhmal','Ikhwan','Ikmal','Ilham','Ilhan','Illias','Ilman','Iman','Imran','Indra','Innamul','Iqbal','Iqwan','Iraman','Irfan','Irman','Irsyad','Isa','Ishak','Ishaq','Iskandar','Isma','Ismail','Ismaon','Isyraq','Iwan','Iyad','Izam','Izdihar','Izlan','Izuhail','Izwan','Izz','Izzan','Izzat','Izzikry','Izzuddin','Izzul',
'Ja\'afer','Jaf','Jaferi','Jafree','Jafri','Jahari','Jalani','Jamal','Jamali','Jamalludin','Jamaluddin','Jamekon','Jamil','Jamsare','Jani','Jasin','Jasni','Jebat','Jefrie','Johari','Joharudin','Jumat','Junaidi',
'Kamal','Kamaruddin','Kamarudin','Kamarul','Kamaruzain','Kamaruzaman','Kamaruzzaman','Kasim','Kasturi','Kemat','Khadzromi','Khairi','Khairil','Khairin','Khairiz','Khairol','Khairubi','Khairudin','Khairul','Khairulnizam','Khairun','Khairurrazi','Khalilul','Khasmadi','Khasri','Khatta','Khirul','Khoirul','Kholis','Khusaini','Khuzairey','Kutni',
'Latiff','Lazim','Lokman','Loqman','Lufty','Lukman','Luqman','Luqmanul','Luthfi','Luthfie',
'M.','Maamor','Madfaizal','Mahadhir','Mahatdir','Mahmusin','Mansor','Marlizam','Martonis','Mastura','Mat','Mazlan','Mazmin','Mazwan','Md','Md.','Megat','Meor','Midoon','Mie','Mikhail','Mirza','Misbun','Miskan','Misran','Miza','Mohlim','Mohmad','Mokhtar','Mokhzani','Moktar','Mu\'izzuddin','Muazzam','Mubarak','Muhaimen','Muhaimi','Muhammad','Muhd','Muid','Muizzuddin','Muizzudin','Mukhtar','Mukhriz','Mukminin','Murad','Murshid','Mus\'ab','Musa','Musiran','Muslim','Mustafa','Mustain','Mustaqim','Musyrif','Muszaphar','Muzami','Muzamil','Muzhafar','Muzzammil',
'Na\'imullah','Nabil','Naderi','Nadzeri','Naim','Najhan','Najib','Najmi','Nakimie','Naqib','Naqiuddin','Narul','Nasaruddin','Nashrul','Nasimuddin','Nasir','Nasiruddin','Nasri','Nasrizal','Nasruddin','Nasrul','Nasrullah','Naufal','Nawawi','Nazari','Nazaruddin','Nazarul','Nazeem','Nazeri','Nazhan','Nazim','Nazlan','Nazmi','Nazren','Nazri','Nazril','Nazrin','Nazrul','Nazzab','Ngadinin','Ngasiman','Ngatri','Nik','Nizam','Nizan','Nizar','Noor','Noordin','Noorizman','Nor','Norain','Norazman','Norazmi','Nordanish','Nordiarman','Nordin','Norfadli','Norfahmi','Norhakim','Norhan','Norhisham','Norsilan','Nur','Nur\'irfaan','Nurakmal','Nurhanafi','Nurhazrul','Nurul','Nuwair','Nuzrul','Nuzul',
'Omar','Omri','Osama','Osman','Othman',
'Pauzi','Puadi','Putra',
'Qairil','Qays','Qusyairi',
'R','Radin','Radzi','Radzuan','Rafael','Raffioddin','Rafiee','Rafiq','Rafizal','Rahim','Raihan','Raja','Rakmat','Ramdan','Ramlan','Ramli','Rash','Rashdan','Rashid','Rashidi','Rasid','Raulah','Rausyan','Razak','Razali','Razemi','Razif','Razlan','Razuan','Redzuan','Redzuawan','Redzwan','Rehan','Rehman','Rezal','Ridhuan','Ridwan','Ridza','Ridzuan','Ridzwan','Rifqi','Rizal','Rizli','Rohaizad','Rohaizal','Rohman','Roosmadi','Roseli','Roslan','Roslee','Rosli','Roslin','Rosman','Rosnan','Rossafizal','Rozi','Rukaini','Rukmanihakim','Ruknuddin','Ruslan','Rusli','Rusman',
'S.rozli','Sabana','Sabqi','Sabri','Sadili','Sadri','Saf\'han','Saffrin','Safie','Safiy','Safrizal','Safuan','Safwan','Sahamudin','Saharil','Said','Saidan','Saidin','Saif','Saiful','Saifullah','Saifullizan','Saipol','Sakri','Salamon','Salihin','Salimi','Salleh','Samad','Samani','Sameer','Samiun','Samsul','Samsur','Sanorhizam','Sardine','Sarudin','Sarwati','Saufishazwi','Sazali','Selamat','Senon','Shafarizal','Shafie','Shafiq','Shah','Shahamirul','Shaharudin','Shaheila','Shaheizy','Shahfiq','Shahmi','Shahnon','Shahquzaifi','Shahril','Shahrin','Shahrizal','Shahrol','Shahru','Shahrul','Shahrulnaim','Shahrun','Shahrunizam','Shahzwan','Shaiful','Shaikh','Shakif','Shakir','Sham','Shameer','Shamhazli','Shamil','Shamizan','Shamizul','Shamsuddin','Shamsudin','Shamsul','Shamsuri','Shamsuzlynn','Shapiein','Sharafuddin','Shari','Sharif','Sharifuddin','Sharifudin','Sharil','Sharizal','Sharsham','Sharudin','Sharul','Shaugi','Shauqi','Shawal','Shazwan','Sheikh','Shmsul','Shohaimi','Shukri','Sirajuddin','Sofian','Sohaini','Solehen','Solekhan','Solleh','Sualman','Subbahi','Subkhiddin','Sudarrahman','Sudirman','Suhaimi','Sukarni','Sukhairi','Sukri','Sukymi','Sulaiman','Sulhan','Suzaili','Suzaman','Syafiq','Syahaziq','Syahid','Syahir','Syahmi','Syahrial','Syahriman','Syahru','Syahzuan','Syakir','Syakirin','Syakirul','Syamirul','Syamsol','Syaqirin','Syarafuddin','Syawal','Syawalludin','Syazani','Syazwan','Syed','Syid','Syukri','Syuqeri',
'Tajuddin','Takiudin','Talha','Tarmizi','Tasripin','Taufek','Taufik','Tayib','Termizi','Thalahuddin','Thaqif','Tunan',
'Umair','Umar','Usman',
'W','Wafi','Wafiq','Wan','Wazir','Wazzirul','Wi',
'Yani','Yaqzan','Yazid','Yunos','Yusaini','Yusfaisal','Yushafiq','Yusni','Yusof','Yusoff','Yusri','Yussof','Yusuf',
'Zabayudin','Zabidi','Zahari','Zahid','Zahiruddin','Zahrul','Zaid','Zaidi','Zainal','Zaini','Zainodin','Zainordin','Zainuddin','Zainul','Zairy','Zaiyon','Zakaria','Zaki','Zakii','Zakri','Zakwan','Zambri','Zamre','Zamri','Zamrul','Zan','Zaqiyuddin','Zar\'ai','Zarif','Zariq','Zarith','Zarul','Zaukepli','Zawawi','Zharaubi','Zikri','Zikril','Zikry','Zizi','Zol','Zolkifle','Zubair','Zubir','Zufayri','Zufrie','Zuheeryrizal','Zuhri','Zuki','Zul','Zulfadhli','Zulfadli','Zulfahmi','Zulfaqar','Zulfaqqar','Zulfikar','Zulhaikal','Zulhakim','Zulhakimi','Zulhelmi','Zulhilmi','Zulkapli','Zulkarnain','Zulkefli','Zulkfli','Zulkifli','Zulkipli','Zulman','Zuri'
);
protected static $firstNameFemaleMalay = array(
'\'Abidah','\'Alyaa','\'Aqilah','\'Atiqah','\'Afiqah','\'Alia','\'Aqilah','A\'ishah','A\'in','A\'zizah','Abdah','Abiatul','Adani','Adawiyah','Adha','Adharina','Adhwa','Adibah','Adilah','Adilla','Adina','Adini','Adira','Adlina','Adlyna','Adriana','Adzlyana','Afifa','Afifah','Afina','Afiqah','Afiza','Afrina','Afzan','Ahda','Aida','Aidatul','Aidila','Aifa','Aiman','Aimi','Aimuni','Ain','Aina','Ainaa','Ainaanasuha','Aini','Ainin','Ainn','Ainnaziha','Ainul','Ainun','Ainur','Airin','Aishah','Aisya','Aisyah','Aiza','Akmal','Aleeya','Aleeza','Aleya','Aleza','Alia','Aliaa','Aliah','Aliffa','Aliffatus','Alina','Alis','Alisya','Aliya','Alkubra','Alleisya','Ally','Alya','Alyaa','Amalia','Amalien','Amalin','Amalina','Amani','Amanina','Amiera','Aminy','Amira','Amirah','Amisha','Amrina','Amylia','Amyra','An-nur','Anas','Andani','Andi','Anesha','Ani','Aninafishah','Anis','Anisah','Anisha','Anissa','Aniza','Anna','Anne','Antaza','Aqeem','Aqeera','Aqila','Aqilah','Arfahrina','Ariana','Ariena','Ariessa','Arifah','Arina','Ariqah','Arissa','Arisya','Armira','Arwina','Aryani','Ashika','Ashriyana','Asiah','Asma\'rauha','Asmaa\'','Asmaleana','Asniati','Asnie','Asniza','Aswana','Asy','Asyiqin','Asykin','Athirah','Atifa','Atifah','Atifahajar','Atikah','Atiqa','Atiqah','Atirah','Atyqah','Auni','Awatif','Awatiff','Ayesha','Ayu','Ayuni','Ayunie','Az','Azashahira','Aziah','Aziemah','Azika','Azira','Azizah','Azliah','Azliatul','Azlin','Azlina','Azmina','Azni','Azrah','Azrina','Azua','Azuin','Azwa','Azwani','Azyan','Azyyati',
'Badrina','Bahirah','Balqis','Basyirah','Batrisya','Batrisyia','Bilqis','Bismillah',
'Camelia','Cempaka',
'Dalila','Dalili','Damia','Dania','Danish','Darlina','Darwisyah','Deni','Dhani','\'Dhiya','Diana','Dianah','Dini','Diyana','Diyanah','Dylaila',
'Eizzah','Eliya','Ellynur','Elpiya','Elyana','Elysha','Ema','Emylia','Erika','Eva','Ezzatul',
'Faathihah','Fadhilah','Fadzliana','Fahda','Fahimah','Fahira','Fairuz','Faizah','Faiznur','Faizyani','Fakhira','Falah','Faqihah','Fara','Faradieba','Farah','Faraheira','Farahin','Farahiyah','Farahtasha','Farha','Farhah','Farhana','Faridatul','Fariha','Farina','Farisah','Farisha','Farrah','Fartinah','Farzana','Fasehah','Fasha','Fateha','Fatehah','Fathiah','Fathiha','Fathihah','Fathimah','Fatiha','Fatihah','Fatimatul','Fatin','Fatini','Fauziah','Faza','Fazlina','Fezrina','Filza','Filzah','Firzanah','Fitrah','Fitri','Fitriah','Fizra',
'Hadfina','Hadiyatul','Hafezah','Hafidzah','Hafieza','Hafizah','Hahizah','Hajar','Hakimah','Halimatul','Halimatussa\'diah','Halisah','Hamira','Hamizah','Hana','Hanaani','Hanani','Hani','Hanim','Hanini','Hanis','Hanisah','Hanna','Hannan','Hannani','Hanni','Hanun','Harma','Hasmalinda','Hasya','Hasyimah','Hayani','Hayati','Hayatul','Hayaty','Hazira','Hazirah','Hazmeera','Hazwani','Hazwanie','Herlina','Herliyana','Hidayah','Hidzwati','Huda','Humaira','Hureen','Husna','Husnina',
'Ida','Iffah','Iklil','Ili','Ilyana','Iman','Imelda','Insyira','Insyirah','Intan','\'Irdhina','Irdina','\'Irdina','Irsa','Iryani','\'Isdmah','Islamiah','Isnur','Izaiti','Izati','Izatie','Izatul','Izaty','Izlin','\'Izzah','Izzah','Izzani','Izzati','Izzatul','Izzaty','Izziani',
'Jaf','Jajuenne','Jani','Jannah','Jannatul','Jaslina','Jihan','Ju','Julia','Juliana','Juliya',
'Kamarlia','Kamelia','Kausthar','Kauthar','Khadijah','Khahirah','Khairina','Khairun','Khairunisa','Khairunnisa','Khairunnisak','Khaleeda','Khaleisya','Khaliesah','Khalisa','Khodijah',
'Laila','Liana','Lina','Lisa','Liyana',
'Madihah','Maheran','Mahfuzah','Mahirah','Maisara','Maisarah','Maizatul','Malihah','Mardhiah','Mariam','Marina','Mariska','Marlina','Marni','Maryam','Mas','Mashitah','Masitah','Mastura','Maswah','Masyikah','Masyitah','Maszlina','Mawaddah','Maya','Mazdiyana','Mazlyn','Melisa','Melissa','Mimi','Mira','Mirsha','Miskon','Miza','Muazzah','Mumtaz','Mursyidah','Muti\'ah','Muyassarah','Muzainah','Mysara','Mysarah',
'Nabihah','Nabila','Nabilah','Nabilla','Nabillah','Nadhilah','Nadhirah','Nadhrah','Nadia','Nadiah','Nadiatun','Nadilla','Nadira','Nadirah','Nadwah','Nadzirah','Nafisah','Nafizah','Najah','Najian','Najiha','Najihah','Najla','Najwa','Najwani','Naliny','Naqibahuda','Nashrah','Nashuha','Nasliha','Nasrin','Nasuha','Natasa','Natasha','Natasya','Nathasa','Natrah','Naurah','Nayli','Nazatul','Nazihah','Nazira','Nazirah','Nazura','Nazurah','Nikmah','Nina','Nisa','Nisak','Nisrina','Noorain','Noorazmiera','Noorfarzanah','Noornazratul','Norafizah','Norain','Noraisyah','Noralia','Noranisa','Noratasha','Nordhiya','Nordiana','Norelliana','Norerina','Norfaezah','Norfahanna','Norhafiza','Norhamiza','Norhidayah','Noridayu','Norliyana','Norsakinah','Norshaera','Norshahirah','Norshuhailah','Norsolehah','Norsuhana','Norsyafiqah','Norsyahirah','Norsyamimie','Norsyarah','Norsyazmira','Norsyazwani','Norsyuhada','Norul','Noryshah',
'Nuradilah','Nurafifah','Nurafrina','Nurain','Nuraina','Nuralia','Nuraliah','Nuralifah','Nuralya','Nurani','Nuranisya','Nuraqilah','Nurarisha','Nurasyikin','Nuratiqah','Nuraveena','Nureen','Nurfaatihah','Nurfadlhlin','Nurfaizah','Nurfarah','Nurfarahin','Nurfarhana','Nurfarrah','Nurfatehah','Nurfatiha','Nurfatin','Nurfirzanah','Nurfitrah','Nurfizatul','Nurhafizah','Nurhajar','Nurhani','Nurhanida','Nurhanis','Nurhanisah','Nurhanna','Nurhawa','Nurhazwani','Nurhazzimah','Nurhidayah','Nurhidayatul','Nurhuda','Nurilyani','Nurin','Nurjazriena','Nurmuzdalifah','Nurnajiha','Nurnatasha','Nurnazhimah','Nurnazhirah','Nurqurratuain','Nursabrina','Nursahira','Nursarah','Nursarwindah','Nursham','Nurshammeza','Nursofiah','Nursuhaila','Nursyaffira','Nursyafika','Nursyahindah','Nursyakirah','Nursyarina','Nursyazwani','Nursyazwina','Nursyuhadah','Nurulhuda','Nurulsyahida','Nurun','Nurwadiyah','Nurwahidah','Nurzafira','Nurzarith','Nurzulaika',
'Pesona','Puteri','Putri',
'Qairina','Qamarina','Qasrina','Qhistina','Qistina','Quintasya','Qurratu','Qurratuaini','Qurratul',
'Rabi\'atul','Rabiatul','Rafidah','Rahiemah','Rahmah','Raihah','Raihana','Raihanah','Raja','Rashmi','Rasyaratul','Rasyiqah','Rasyiqqah','Raudatul','Ridiatul','Rieni','Rifhan','Rihhadatul','Ros','Rosalinda','Rosyadah','Rusyda','Rusydina',
'Sa\'adah','Saadiah','Sabrina','Safi','Safiah','Safiyah','Sahira','Saidatul','Sakinah','Sakirah','Salwa','Sameera','Sarah','Sarwati','Sasya','Serene','Sha','Shabariah','Shafiah','Shafiera','Shafikah','Shafinaz','Shafiqa','Shafiqah','Shah','Shahida','Shahidah','Shahiera','Shahila','Shahira','Shahirah','Shahrazy','Shahrina','Shakilah','Shakinah','Shalina','Shameera','Shamila','Shamimie','Shamira','Shar\'fiera','Sharifah','Sharizah','Shauqina','Shayira','Shazana','Shazieda','Shazlien','Shazwana','Shazwani','Shonia','Shuhada','Siti','Siti','Siti','Siti','Siti','Siti','Sitti','Sofea','Sofeah','Soffia','Sofia','Sofiya','Sofiyah','Sofya','Solehah','Sopie','Suaidah','Suhada','Suhadah','Suhaida','Suhaila','Suhailah','Suhaina','Suhana','Suhani','Sulaiha','Sumayyah','Suraya','Suziyanis','Syaffea','Syafika','Syafikah','Syafina','Syafiqa','Syafiqah','Syafirah','Syafiyah','Syafiyana','Syahada','Syahadatullah','Syahera','Syaherah','Syahidah','Syahidatul','Syahiera','Syahira','Syahirah','Syahmimi','Syahmina','Syahzani','Syaidatul','Syairah','Syakila','Syakira','Syakirah','Syamien','Syamilah','Syamimi','Syamina','Syamirah','Syara','Syarafana','Syarafina','Syarah','Syarina','Syasyabila','Syauqina','Syaza','Syazana','Syazliya','Syazmin','Syazryana','Syazwana','Syazwani','Syazwanie','Syazwina','Syifa\'','Syuhada','Syuhada`','Syuhaida','Syuhaidah',
'Taqiah','Tasnim','Tengku','Tihany',
'Umairah','Umi','Umira','Ummi',
'Wadiha','Wafa','Waheeda','Wahida','Wahidah','Wan','Wardatul','Wardina','Wardinah','Wazira','Weni',
'Yasmeen','Yasmin','Yetri','Yunalis','Yusra','Yusrinaa','Yusyilaaida',
'Zaffan','Zafirah','Zaharah','Zahirah','Zahrah','Zahrak','Zaidalina','Zaidatulkhoiriyah','Zainab','Zainatul','Zakdatul','Zatalini','Zati','Zayani','Zeqafazri','Zilhaiza','Zubaidah','Zulaika','Zulaikha'
);
protected static $lastNameMalay = array(
'\'Aizat','A\'liyyuddin','Abas','Abdillah','Abdullah','Abidin','Adam','Adha','Adham','Adi','Adieka','Adip','Adli','Adnan','Adrus','Afandi','Afiq','Afizi','Afnan','Afsyal','Ahmad','Ahwali','Aidi','Aidil','Aiman','Aizad','Aizam','Aizat','Ajllin','Ajmal','Akashah','Akasyah','Akbar','Akhmal','Akid','Akif','Akmal','Al-amin','Al-hakim','Albukhary','Ali','Alias','Alif','Alimi','Aliuddin','Amaluddin','Amin','Aminnudin','Aminrullah','Aminuddin','Amiran','Amiruddin','Amirul','Amirullah','Ammar','Ammer','Amni','Amran','Amri','Amry','Amsyar','Amzah','Anam','Anaqi','Andalis','Anuar','Anwar','Apizan','Aqashah','Aqil','Arfan','Arfandi','Arias','Arief','Arif','Ariff','Ariffin','Arifin','Arifuddin','Arman','Arshad','Arziman','As','Asa','Ashraf','Ashraff','Asmadi','Asmar','Asmawi','Asri','Asyraf','Asyran','Asyrani','Aszahari','Awal','Awalluddin','Awaluddin','Awaludin','Awira','Ayyadi','Azahar','Azahari','Azam','Azhan','Azhar','Azhari','Azim','Aziz','Azizan','Azizi','Azizy','Azlan','Azlansyhah','Azli','Azlim','Azman','Azmee','Azmi','Azmin','Aznai','Azni','Azraai','Azrai','Azri','Azril','Azrin','Azriq','Azrul','Azuan',
'Badrulhisham','Baha','Bahaman','Bahari','Baharin','Baharruddin','Baharuddin','Baharudin','Bahri','Bahrin','Bahrodin','Bakar','Bakri','Bakry','Bakti','Basaruddin','Bashah','Basri','Basyir','Batisah','Bella','Berman','Borhan','Buhari','Bukhari',
'Chai',
'Dahalan','Dahari','Dahlan','Daiman','Daneal','Daniael','Danial','Daniel','Danish','Darmawi','Daryusman','Daud','Dazila','Din','Dini','Djuhandie','Dolkefli','Draman','Dzikri','Dzolkefli','Dzulkifli','Dzullutfi',
'Effendi','Effindi','Ekhsan','Elfin','Erfan',
'Fadhil','Fadhilah','Fadil','Fadillah','Fadlullah','Fadzil','Faez','Fahi','Fahim','Fahmi','Fahmie','Fairos','Fairuz','Faiser','Faiz','Faizal','Faizul','Faizun','Fakhri','Fakhrurrazi','Fareesnizra','Fareez','Farhan','Farid','Farihan','Faris','Farris','Fathi','Fatullah','Faudzi','Fauzi','Fauzy','Fayyad','Fazal','Fazil','Fazira','Fikri','Firdaus','Firdoz','Fiteri','Fitri','Fuad','Fuart','Fuzi',
'Garapar','Ghani','Ghazi',
'Haddi','Hadi','Hadzis','Haeizan','Hafandi','Hafiz','Hafizam','Hafizee','Hafizh','Hafizi','Hafizuddin','Haidie','Haikal','Haiqal','Hairizan','Hairuddin','Hairulnizam','Hairunnezam','Haizam','Haizan','Hajar','Hakam','Hakiem','Hakim','Hakimi','Hakimie','Halib','Halil','Halim','Halin','Hamdan','Hamdani','Hamid','Hamidi','Hamizie','Hamizuddin','Hamjah','Hammani','Hamzah','Hanafi','Hanafia','Hanief','Hanif','Hanifah','Haniff','Hanim','Hapani','Haqim','Haqimi','Haramaini','Hardinal','Hariff','Haris','Harith','Hariz','Harmaini','Harman','Haron','Harris','Haruddin','Harun','Hasadi','Hasan','Hasbi','Hasbullah','Hashan','Hasif','Hasim','Hasmawi','Hasnan','Hasri','Hassan','Hassim','Hassimon','Haszlan','Hazambi','Hazaril','Hazim','Hazimie','Haziq','Hazizan','Hazlin','Hazre','Hazrin','Hazrol','Helmi','Hi\'qal','Hikmee','Hilmi','Hisam','Hisham','Hishhram','Hizam','Husaini','Husin','Husna','Husni','Hussin','Huzaify','Huzain',
'Ibrahim','Idham','Idris','\'Iffat','Ifwat','Ikhmal','Ikhram','Ikhwan','Ikmal','Ikram','Ilman','Iman','Imran','Imtiyaz','Iqbal','Iqmal','Irfan','Irham','Irsyad','Is\'ad','Isa','Isfarhan','Ishak','Ishsyal','Iskandar','Ismadi','Ismail','Ismayudin','Isroman','Isyrafi','Izad','Izam','Izani','Izman','Izwan','Izzat','Izzuddin','Izzudin',
'Jainal','Jaini','Jamahari','Jamal','Jamaluddin','Jamaludin','Jaman','Jamri','Jani','Jasni','Jaya','Jeffri','Jefri','Jelani','Jemadin','Johan','Johari','Juhari','Jumat','Junaidi',
'Kahar','Kamal','Kamaruddin','Kamarudin','Kamarul','Kamaruzaman','Kamil','Kamslian','Karzin','Kasim','Kasturi','Khafiz','Khairani','Khairuddin','Khaleed','Khaliq','Khan','Kharmain','Khatta','Khilmi','Khir-ruddin','Khirulrezal','Khusaini',
'Latif','Latip','Lazim','Lukman',
'Maarof','Mahadi','Mahat','Mahathir','Mahmudin','Mahmusin','Mahyuddin','Mahyus','Majid','Malek','Malik','Maliki','Mamhuri','Man','Manaf','Manan','Manap','Mansor','Margono','Martunus','Maruzi','Marzuki','Maserun','Maskor','Maslan','Maswari','Maszuni','Mazalan','Mazlan','Midali','Mikhail','Mirza','Miskan','Miskoulan','Mislan','Misnan','Mizan','Mohhidin','Mohsin','Mokhtar','Moktar','Molkan','Mon','Montahar','Mossanif','Mu','Muaddib','Muain','Muhaimi','Muhaimin','Muhdi','Muiz','Mujamek','Mukmin','Mukromin','Muneer','Muqriz','Murad','Murshed','Murshidi','Musa','Muslim','Musliman','Mustafa','Mustapha','Mustaqim','Musyrif','Mutaali','Mutalib','Muti\'i','Muzamil','Muzammil',
'Na\'im','Nabil','Nadzri','Nafiz','Naim','Najhi','Najib','Najmi','Najmuddin','Naqiyuddin','Nasaruddin','Nashriq','Nasiman','Nasir','Nasrodin','Nasrullah','Naufal','Nawawi','Nazairi','Nazar','Nazarudin','Nazeri','Nazhan','Nazirin','Nazmi','Nazree','Nazri','Nazrin','Nazry','Ngadenan','Ngadun','Niszan','Nizam','Noh','Noor','Noordin','Noorhakim','Noorismadi','Noorizman','Nor','Noradhzmi','Noraffendi','Noraslan','Norazam','Norazim','Norazman','Norazmi','Nordin','Norhisam','Norhisham','Norizal','Norizan','Norlisam','Normansah','Norrizam','Norsilan','Norzamri','Nurfairuz','Nurhaliza','Nurnaim',
'Omar','Osman','Othman',
'Pa\'aing','Pauzi','Pisol','Putra','Putra',
'Qayum','Qayyum','Qayyuum','Qusyairi',
'Ra\'ais','Radzi','Raffioddin','Raffiq','Rafi','Rafizal','Rahamad','Rahim','Rahman','Rahmat','Rais','Raizal','Raman','Ramdan','Ramdzan','Ramlan','Ramlee','Ramli','Ramly','Rani','Ranjit','Raqi','Rashid','Rashidi','Rashidin','Rasid','Rassid','Rasyid','Razak','Razali','Raze','Razi','Razin','Razlan','Razman','Redha','Redzuan','Rembli','Remi','Ridduan','Ridhwan','Ridzuan','Ridzwan','Rifin','Rifqi','Rifqie','Rithwan','Rizal','Rizuan','Rizwan','Robani','Rohaizan','Rohem','Rohman','Ros','Rosdan','Roshman','Roslan','Roslee','Rosli','Rosly','Rosmawi','Rosnan','Rossaimi','Rostam','Rostan','Roszainal','Rozi','Rubi','Rusdi','Ruslan','Rusli','Rustam','Rusyaidi',
'Sa\'ari','Saad','Sabaruddin','Sabarudin','Sabki','Sabri','Sabrie','Safee','Saffuan','Safie','Safingi','Safrifarizal','Safrizal','Safwan','Sahidi','Sahril','Sahroni','Saifuddin','Saifudin','Saifulzakher','Saifuzin','Saihun','Saizol','Sakdon','Sakri','Salam','Saleh','Salehudin','Salim','Salleh','Salman','Sam','Samad','Samae','Samah','Saman','Samsani','Samsuddin','Samsul','Samsuri','Sandha','Sani','Sanorhizam','Sapuan','Sarim','Satar','Saudi','Sazali','Sedek','Selamat','Senon','Sha\'ril','Shabana','Shafei','Shafie','Shafiq','Shah','Shaharuddin','Shaharudin','Shahiman','Shahrazy','Shahrizan','Shaidi','Shaifuddin','Shaihuddin','Sham','Shameer','Shamizan','Shamsuddin','Shamsudin','Shamsul','Shapiein','Sharasan','Sharif','Sharifudin','Shariman','Sharin','Sharollizam','Sharum','Shazani','Shazman','Shmsul','Shobi','Shueib','Shukor','Shukri','Sidek','Sinuzulan','Soberi','Sobirin','Sofi','Solehin','Solekhan','Sonan','Suami','Subhi','Subzan','Sudirman','Sueib','Sufi','Sufian','Suhaimi','Suhiman','Sukarsek','Sulaiman','Sulong','Suraji','Surya','Sutrisno','Suz\'ian','Suzaimi','Syafiq','Syafrin','Syahir','Syahmi','Syahril','Syahrin','Syakir','Syamil','Syauqi','Syazwan','Syukran','Syukri','Syuraih',
'Tajudin','Takiudin','Talib','Taqiuddin','Tarjuddin','Tarmizi','Tarudin','Taufek','Thaqif','Tuah','Tukimin','Tumiran',
'Ubaidillah','Ulum','Umar','Usman','Usri','Uzair',
'Wafi','Wahab','Wahbillah','Wahid','Wahidan','Wahidin','Wardi','Wasil','Wazif','Wildani',
'Ya\'accob','Yaacob','Yaakob','Yaacup','Yacob','Yahaya','Yahya','Yajid','Yamani','Yanis','Yaqin','Yasin','Yazid','Yunus','Yusaini','Yusihasbi','Yusni','Yusof','Yusoff','Yusri','Yusrin','Yusseri','Yussof','Yusuf','Yuszelan','Yuzli',
'Zafran','Zahani','Zahar','Zahareman','Zahari','Zahin','Zaid','Zaidi','Zailan','Zailani','Zaimi','Zaiminuddin','Zain','Zainal','Zaini','Zainorazman','Zainordin','Zainuddin','Zainudin','Zainul-\'alam','Zainun','Zainuri','Zairi','Zairulaizam','Zakaria','Zaki','Zakir','Zakuan','Zakwan','Zam','Zamanhuri','Zamani','Zamhari','Zamran','Zamre','Zamree','Zamri','Zamzuri','Zani','Zar\'ai','Zawawi','Zawi','Zazlan','Zehnei','Zhafran','Zihni','Zikry','Zin','Zizi','Zol','Zolkafeli','Zolkifli','Zuanuar','Zubair','Zubir','Zufayri','Zuhaili','Zuki','Zukri','Zulamin','Zulfadhli','Zulfikar','Zulfikri','Zulhazril','Zulhelmi','Zulkafli','Zulkanine','Zulkarnaen','Zulkefle','Zulkefli','Zulkernain','Zulkhairie','Zulkifli','Zulqurnainin','Zumali','Zuraidi','Zuri','Zuwairi',
);
/**
* Note: The empty elements are for names without the title, chances increase by number of empty elements.
*
* @link https://en.wikipedia.org/wiki/Muhammad_(name)
*/
protected static $muhammadName = array('', '', '', '', 'Mohamad ','Mohamed ','Mohammad ','Mohammed ','Muhamad ','Muhamed ','Muhammad ','Muhammed ','Muhammet ','Mohd ');
/**
*
* @link https://en.wikipedia.org/wiki/Noor_(name)
*/
protected static $nurName = array('', '', '', '', 'Noor ', 'Nor ', 'Nur ', 'Nur ', 'Nur ', 'Nurul ','Nuur ');
/**
* @link https://en.wikipedia.org/wiki/Malaysian_names#Haji_or_Hajjah
*/
protected static $haji = array('', '', '', '', 'Haji ', 'Hj ');
protected static $hajjah = array('', '', '', '', 'Hajjah ', 'Hjh ');
/**
* @link https://en.wikipedia.org/wiki/Malay_styles_and_titles
*/
protected static $titleMaleMalay = array('', '', '', '', '', '', 'Syed ','Wan ','Nik ','Che ');
/**
* Chinese family name or surname
*
* @link https://en.wikipedia.org/wiki/List_of_common_Chinese_surnames
* @link https://en.wikipedia.org/wiki/Hundred_Family_Surnames
*
*/
protected static $lastNameChinese = array(
'An','Ang','Au','Au-Yong','Aun','Aw',
'Bai','Ban','Bok','Bong',
'Ch\'ng','Cha','Chai','Cham','Chan','Chang','Cheah','Cheam','Chee','Chen','Cheng','Cheok','Cheong','Chew','Chia','Chiam','Chiang',
'Chieng','Chiew','Chin','Ching','Chong','Choong','Chou','Chow','Choy','Chu','Chua','Chuah','Chung',
'Dee','Die','Ding',
'Ee','En','Eng','Er','Ewe',
'Fam','Fan','Fang','Feng','Foo','Foong',
'Gan','Gao','Gee','Gnai','Go','Goh','Gong','Guan','Gun',
'H\'ng','Hang','Hao','Haw','Hee','Heng','Hew','Hiew','Hii','Ho','Hoo','Hong','Hooi','Hui',
'Jong',
'Kam','Kang','Kar','Kee','Khoo','Khor','Khu','Kia','Kim','King','Ko','Koay','Koh','Kok','Kong','Kow','Kwok','Kwong','Ku','Kua','Kuan','Kum',
'Lah','Lai','Lam','Lau','Law','Leau','Lee','Leng','Leong','Leow','Leung','Lew','Li','Lian','Liang','Liao','Liew','Lim','Ling','Liong','Liow',
'Lo','Loh','Loi','Lok','Loke','Loo','Looi','Low','Lu','Luo','Lum','Lye',
'Ma','Mah','Mak','Meng','Mok',
'Neo','Neoh','New','Ng','Nga','Ngan','Ngeh','Ngeow','Ngo','Ngu','Nguei','Nii',
'Ong','Oo','Ooi','Oon','Oong','OuYang',
'P\'ng','Pang','Phang','Phoon','Phor','Phua','Phuah','Poh','Poon',
'Qian','Qu','Quah','Quak','Quan','Quek',
'Sam','Sau','Seah','See','Seetho','Seng','Seoh','Seow','Shee','Shi','Shum','Sia','Siah','Siao','Siauw','Siaw','Siew','Sim','Sin','Sio','Siong','Siow','Siu','Soh','Song','Soo','Soon','Su','Sum',
'T\'ng','Tai','Tam','Tan','Tay','Tang','Tea','Tee','Teh','Tek','Teng','Teo','Teoh','Tern','Tew','Tey','Thang','Thew','Thong','Thoo','Thum','Thun','Ting','Tiong','Toh','Tong','Tse','Tung',
'Vong',
'Wah','Waiy','Wan','Wee','Wen','Wong','Woo','Woon','Wu',
'Xia','Xiong','Xu',
'Yam','Yao','Yiaw','Ying','Yip','Yang','Yap','Yau','Yee','Yen','Yeo','Yeoh','Yeong','Yeow','Yep','Yew','Yong','Yow','You','Yu','Yuan','Yuen',
'Zhong','Zhang','Zheng','Zhu','Zu'
);
/**
* Chinese second character
*
* @link https://en.wikipedia.org/wiki/Chinese_given_name
* @link https://en.wikipedia.org/wiki/List_of_Malaysians_of_Chinese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Cantonese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_politicians_of_Chinese_descent
*/
protected static $firstNameChinese = array(
'Ah','Ai','Aik','An','Ann','Ang','Au','Aun','Aw',
'Bae','Bai','Bak','Ban','Bang','Bao','Bau','Bee','Beh','Bei','Ben','Beng','Bi','Bik','Bin','Bing','Bo','Bok','Bong','Boo','Boon','Bow','Bu','Bui','Buk','Bun','Bung',
'Cai','Car','Caw','Cee','Ceh','Cek','Cen','Cer',
'Cha','Chah','Chai','Chak','Cham','Chan','Chang','Chao','Chap','Char','Chat','Chau','Chaw',
'Chea','Cheah','Cheam','Chean','Cheang','Chee','Cheen','Chek','Chen','Cheng','Cheok','Cheong','Cher','Chet','Chew',
'Chi','Chia','Chih','Chik','Chin','Ching','Chio','Chit','Chiu',
'Cho','Choi','Chok','Chon','Chong','Choo','Chooi','Choon','Choong','Chor','Chou','Chow','Choy',
'Chu','Chua','Chuah','Chuan','Chua','Chui','Chuk','Chum','Chun','Chung','Chuo','Chye',
'Da','Dai','Dan','Dang','Dao','Dau','Dee','Deng','Di','Dim','Din','Ding','Diong','Do','Dong','Doo','Dou','Du','Dui','Duo',
'Ee','Eh','En','Enn','Er','Ern','Eu','Ew',
'Fa','Fah','Fai','Fam','Fan','Fang','Fat','Fatt','Fay','Faye','Fee','Fei','Fen','Feng','Fern','Fey','Fok','Fon','Fong','Foo','Foon','Foong','Fu','Fui','Fuk','Fun','Fung',
'Gai','Gak','Gam','Gan','Gao','Gau','Gee','Gek','Geng','Gi','Giap','Gin','Git','Go','Goh','Gok','Gon','Gong','Goo','Goon','Gu','Gui','Guk','Gun','Gung','Gunn',
'Ha','Haa','Hah','Hai','Han','Hang','Hao','Har','Haw','He','Hee','Hei','Hen','Heng','Heong','Her','Hew','Hi','Hii','Hin','Hing','Hiong','Hiu',
'Ho','Hoe','Hoi','Hok','Hom','Hon','Hong','Hoo','Hooi','Hook','Hoon','Hoong','Hor','Hou','How','Hoy','Hu','Hua','Huan','Huang','Hue','Hui','Hun','Hung','Huo','Hup',
'Jan','Jang','Jao','Jee','Jei','Jen','Jeng','Jeong','Jer','Jet','Jett','Jeu','Ji','Jia','Jian','Jiang','Jie','Jien','Jiet','Jim','Jin','Jing','Jio','Jiong','Jit','Jiu',
'Jo','Joe','Jong','Joo','Joon','Joong','Joy','Ju','Jun','Jung','Jye',
'Ka','Kaa','Kah','Kai','Kak','Kam','Kan','Kang','Kao','Kap','Kar','Kat','Kau','Kaw','Kay','Ke','Kean','Keang','Keat','Kee','Kei','Kek','Ken','Keng','Ker','Keu','Kew','Key',
'Kha','Khai','Khan','Khang','Khar','Khaw','Khay','Khean','Kheang','Khee','Khi','Khia','Khian','Khiang','Kho','Khoh','Khoi','Khoo','Khor','Khu','Khum','Khung',
'Ki','Kia','Kian','Kiang','Kiap','Kiat','Kien','Kiet','Kim','Kin','King','Kit','Ko','Koe','Koh','Koi','Kok','Kong','Koo','Koong','Koor','Kor','Kou','Kow','Koy',
'Ku','Kua','Kuang','Kui','Kum','Kun','Kung','Kuo','Kuong','Kuu',
'La','Lai','Lak','Lam','Lan','Lang','Lao','Lap','Lar','Lat','Lau','Law','Lay',
'Le','Lea','Lean','Leang','Leat','Lee','Leen','Leet','Lei','Lein','Leik','Leiu','Lek','Len','Leng','Leon','Leong','Leow','Ler','Leu','Leung','Lew','Lex','Ley',
'Li','Liah','Lian','Liang','Liao','Liat','Liau','Liaw','Lie','Liek','Liem','Lien','Liet','Lieu','Liew','Lih','Lik','Lim','Lin','Ling','Lio','Lion','Liong','Liow','Lip','Lit','Liu',
'Lo','Loh','Loi','Lok','Long','Loo','Looi','Look','Loon','Loong','Lor','Lou','Low','Loy',
'Lu','Lua','Lui','Luk','Lum','Lun','Lung','Luo','Lup','Luu',
'Ma','Mae','Mag','Mah','Mai','Mak','Man','Mang','Mao','Mar','Mat','Mau','Maw','May','Me','Mea','Mee','Meg','Meh','Mei','Mek','Mel','Men','Meu','Mew',
'Mi','Mie','Miin','Miing','Min','Ming','Miu','Mo','Moh','Moi','Mok','Mon','Mong','Moo','Moon','Moong','Mou','Mow','Moy','Mu','Mua','Mui','Mum','Mun','Muu',
'Na','Naa','Nah','Nai','Nam','Nan','Nao','Nau','Nee','Nei','Neng','Neo','Neu','New','Nga','Ngah','Ngai','Ngan','Ngao','Ngau','Ngaw','Ngo','Ngu','Ni','Nian','Niang','Niao','Niau','Nien','Nik','Nin','Niu','Nong','Nyet',
'Oh','Oi','Ong','Onn','Oo','Ooi',
'Pah','Pai','Pak','Pam','Pan','Pang','Pao','Pat','Pau','Paw','Pay','Peh','Pei','Peik','Pek','Pen','Peng','Pey',
'Phang','Pheng','Phong','Pik','Pin','Ping','Po','Poh','Pok','Pom','Pong','Pooi','Pou','Pow','Pu','Pua','Puah','Pui','Pun',
'Qi','Qin','Qing','Qiu','Qu','Quan','Quay','Quen','Qui','Quek','Quok',
'Rei','Ren','Rin','Ring','Rinn','Ron','Rong','Rou','Ru','Rui','Ruo',
'Sai','Sam','San','Sang','Say','Sha','Shak','Sham','Shan','Shang','Shao','Shar','Shau','Shaw','Shay','She','Shea','Shee','Shei','Shek','Shen','Sher','Shew','Shey','Shi','Shia','Shian','Shiang','Shiao','Shie','Shih','Shik','Shim','Shin','Shing','Shio','Shiu',
'Sho','Shok','Shong','Shoo','Shou','Show','Shu','Shui','Shuk','Shum','Shun','Shung','Shuo','Si','Sia','Siah','Siak','Siam','Sian','Siang','Siao','Siau','Siaw','Sien','Sieu','Siew','Sih','Sik','Sim','Sin','Sing','Sio','Siong','Siou','Siow','Sit','Siu',
'So','Soh','Soi','Sok','Son','Song','Soo','Soon','Soong','Sou','Sow','Su','Suan','Suang','Sue','Suen','Sui','Suk','Sum','Sun','Sung','Suo',
'Ta','Tai','Tak','Tam','Tan','Tang','Tao','Tar','Tat','Tatt','Tau','Tay','Tea','Teak','Tean','Tee','Teh','Tei','Tek','Ten','Teng','Teo','Teoh','Ter','Tet','Teu','Tew','Tey',
'Tha','Thai','Tham','Thang','Thau','Thay','Thee','Theo','Ther','Thew','They','Thia','Thian','Thien','Tho','Thok','Thong','Thoo','Thor','Thou','Thu','Thuk','Thum','Thung','Thur','Ti','Tia','Tiah','Tiak','Tiam','Tian','Tiang','Tiek','Tien','Tik','Tim','Tin','Ting','Tio','Tiong','Tiu',
'To','Toh','Tok','Tong','Too','Tor','Tou','Tow','Tu','Tuk','Tung',
'Ung',
'Vin','Von','Voon',
'Wa','Wah','Wai','Wan','Wang','Way','Wee','Wei','Wen','Weng','Wey','Whay','Whey','Wi','Win','Wing','Wo','Woh','Woi','Wok','Won','Wong','Woo','Woon','Wu','Wui',
'Xi','Xia','Xiah','Xian','Xiang','Xiao','Xiau','Xie','Xin','Xing','Xiong','Xiu','Xu','Xun',
'Yam','Yan','Yang','Yao','Yat','Yatt','Yau','Yaw','Ye','Yee','Yen','Yeng','Yeo','Yeoh','Yeong','Yep','Yet','Yeu','Yew','Yi','Yih','Yii','Yik','Yin','Ying','Yip','Yit','Yo','Yok','Yon','Yong','Yoo','You','Yow','Yu','Yuan','Yue','Yuen','Yuet','Yuk','Yun','Yung','Yup','Yut','Yutt',
'Za','Zai','Zang','Zao','Zau','Zea','Zeah','Zed','Zee','Zen','Zeng','Zeo','Zet',
'Zha','Zhai','Zhan','Zhang','Zhao','Zhau','Zhee','Zhen','Zheng','Zhet','Zhi','Zhong','Zhu','Zhung',
'Zi','Zia','Ziah','Ziak','Zian','Ziang','Ziao','Ziau','Zit','Zo','Zoe','Zou','Zu','Zui','Zuk','Zung',
);
/**
* Chinese male third character
*
* @link https://en.wikipedia.org/wiki/Chinese_given_name
* @link https://en.wikipedia.org/wiki/List_of_Malaysians_of_Chinese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Cantonese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_politicians_of_Chinese_descent
*/
protected static $firstNameMaleChinese = array(
'Aik','Ang','Au','Aun',
'Bak','Ban','Bang','Bao','Bau','Ben','Beng','Bing','Bok','Bong','Boo','Boon','Bow','Buk','Bun','Bung',
'Chai','Chak','Chan','Chang','Chao','Chap','Chat','Chau','Chaw',
'Cheah','Chee','Cheen','Chek','Chen','Cheong','Cher','Chet','Chew',
'Chia','Chih','Chik','Chin','Ching','Chit','Chiu',
'Cho','Choi','Chok','Chon','Chong','Choo','Chooi','Choon','Choong','Chor','Chou','Chow','Choy',
'Chua','Chuah','Chuan','Chua','Chui','Chuk','Chum','Chun','Chung','Chuo','Chye',
'Dan','Dao','Dau','Dee','Deng','Di','Dim','Din','Diong','Dong','Dou','Du','Dui','Duo',
'Eu','Ew',
'Fai','Fam','Fat','Fatt','Fee','Feng','Fok','Fon','Fong','Foo','Foon','Foong','Fu','Fui','Fuk',
'Gai','Gak','Gam','Gan','Gao','Gau','Gee','Gek','Geng','Giap','Gin','Git','Go','Goh','Gok','Gon','Gong','Gu','Guk','Gun','Gung','Gunn',
'Hai','Han','Hang','Har','Haw','Hei','Hen','Heng','Hing',
'Ho','Hoe','Hoi','Hok','Hom','Hon','Hong','Hoo','Hook','Hoon','Hoong','Hor','Hou','How','Hoy','Hu','Huan','Huang','Hun','Hung','Huo','Hup',
'Jeong','Jer','Jet','Jett','Jeu','Ji','Jian','Jiang','Jiet','Jim','Jin','Jio','Jiong','Jit','Jiu','Jo','Joe','Joong','Jung','Jye',
'Kai','Kan','Kang','Kao','Kap','Kau','Kaw','Kean','Keang','Keat','Kek','Ken','Keng','Ker','Keu','Kew',
'Khai','Khan','Khang','Khaw','Khean','Kheang','Khia','Khian','Khiang','Kho','Khoh','Khoi','Khoo','Khu','Khung',
'Kia','Kian','Kiang','Kiap','Kiat','Kien','Kiet','Kin','King','Kit','Ko','Koi','Kok','Kong','Koo','Koong','Koor','Kou','Kow','Koy',
'Ku','Kuang','Kui','Kun','Kung','Kuo','Kuong','Kuu',
'Lak','Lam','Lang','Lao','Lap','Lar','Lat','Lau','Law',
'Lean','Leang','Leat','Lee','Leet','Leik','Leiu','Lek','Len','Leon','Leong','Leow','Leung','Lew','Lex',
'Liang','Liao','Liat','Liau','Liaw','Liek','Liem','Liet','Lieu','Liew','Lih','Lik','Lim','Lio','Lion','Liong','Liow','Lip','Lit','Liu',
'Lo','Loh','Loi','Lok','Long','Loo','Looi','Look','Loon','Loong','Lor','Lou','Low','Loy',
'Lu','Luk','Lum','Lun','Lung','Lup',
'Man','Mang','Mao','Mar','Mat','Mau','Maw','Mek','Men',
'Mo','Mok','Mon','Mong','Moong','Mou','Mow','Mu',
'Nam','Nan','Nau','Neng','Neo','Neu','Ngai','Ngao','Ngau','Ngaw','Ngo','Niao','Niau','Nien','Nik','Niu','Nyet',
'Oh','Oi','Ong','Onn','Oo',
'Pah','Pai','Pak','Pang','Pao','Pat','Pau','Paw','Pen','Peng',
'Phang','Pheng','Phong','Pok','Pou','Pow','Pu','Pua','Puah',
'Quan','Quen','Quek','Quok',
'Ren','Ron',
'Sai','Sam','San','Sang','Shak','Sham','Shang','Shao','Shau','Shaw','Shek','Shen','Shiang','Shih','Shik','Shim','Shing','Shio','Shiu',
'Sho','Shong','Shoo','Shou','Show','Shun','Shung','Shuo','Siam','Siang','Siau','Siaw','Sieu','Sih','Sik','Sing','Sio','Siong','Siou','Siow','Sit',
'Son','Song','Soon','Soong','Sou','Sow','Suang','Sum','Sung','Suo',
'Ta','Tak','Tan','Tang','Tao','Tar','Tat','Tatt','Tau','Teak','Tean','Tee','Teh','Tei','Tek','Ten','Teng','Teo','Teoh','Ter','Tet','Teu','Tew',
'Tha','Thai','Tham','Thang','Thau','Thay','Thee','Theo','Ther','Thew','They','Thian','Thien','Tho','Thok','Thong','Thoo','Thor','Thou','Thu','Thuk','Thum','Thung','Thur','Tiak','Tiam','Tian','Tiang','Tiek','Tien','Tik','Tim','Tin','Tio','Tiong','Tiu',
'To','Toh','Tok','Tong','Too','Tor','Tou','Tow','Tu','Tuk','Tung',
'Ung',
'Vin','Von',
'Wa','Wah','Wai','Wang','Way','Wee','Wei','Weng','Whay','Win','Wing','Wo','Woh','Woi','Wok','Won','Wong','Woo','Wu','Wui',
'Xiang','Xiong',
'Yang','Yao','Yat','Yatt','Yau','Yaw','Ye','Yeng','Yeo','Yeoh','Yeong','Yet','Yih','Yii','Yik','Yip','Yit','Yo','Yok','Yon','Yong','Yoo','You','Yow','Yu','Yuen','Yuet','Yuk','Yut','Yutt',
'Za','Zai','Zang','Zao','Zau','Zea','Zeah','Zed','Zee','Zen','Zeng','Zeo','Zet',
'Zha','Zhai','Zhan','Zhang','Zhao','Zhau','Zhee','Zheng','Zhet','Zhong','Zhu','Zhung',
'Ziak','Zian','Ziang','Ziao','Ziau','Zit','Zuk','Zung',
);
/**
* Chinese female third character
*
* @link https://en.wikipedia.org/wiki/Chinese_given_name
* @link https://en.wikipedia.org/wiki/List_of_Malaysians_of_Chinese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Cantonese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_politicians_of_Chinese_descent
*/
protected static $firstNameFemaleChinese = array(
'Ai','An','Ann','Aw',
'Bae','Bai','Bee','Beh','Bei','Bi','Bik','Bin','Bui',
'Cai','Cee','Cen','Cham','Cheam','Chean','Cheang','Cheng','Cheok','Chi','Ching','Chio','Chu',
'Dai','Dang','Ding','Do','Doo',
'Ee','En','Enn','Er','Ern',
'Fah','Fan','Fang','Fay','Faye','Fei','Fen','Fern','Fey','Fong','Fun','Fung',
'Gi','Goo','Goon','Gui',
'Ha','Haa','Hah','Hao','He','Hee','Heong','Her','Hew','Hi','Hii','Hin','Hiong','Hiu','Hooi','Hua','Hue','Hui',
'Jan','Jang','Jao','Jee','Jei','Jen','Jeng','Jia','Jie','Jien','Jing','Jong','Joo','Joon','Joy','Ju','Jun',
'Ka','Kaa','Kah','Kak','Kam','Kar','Kat','Kay','Ke','Kee','Kei','Key',
'Kha','Khar','Khay','Khee','Khi','Khor','Khum',
'Ki','Kim','Koe','Koh','Kor','Kum','Kua',
'Lai','Lan','Lay',
'Le','Lea','Leen','Lei','Lein','Leng','Ler','Leu','Ley',
'Li','Liah','Lian','Lie','Lien','Lin','Ling',
'Lua','Lui','Luo','Luu',
'Ma','Mae','Mag','Mah','Mai','Mak','May','Me','Mea','Mee','Meg','Meh','Mei','Mel','Meu','Mew',
'Mi','Mie','Miin','Miing','Min','Ming','Miu','Moh','Moi','Moo','Moon','Moy','Mua','Mui','Mum','Mun','Muu',
'Na','Naa','Nah','Nai','Nao','Nee','Nei','New','Nga','Ngah','Ngan','Ngu','Ni','Nian','Niang','Nin','Nong',
'Ooi',
'Pam','Pan','Pay','Peh','Pei','Peik','Pek','Pey','Pik','Pin','Ping','Po','Poh','Pom','Pong','Pooi','Pui','Pun',
'Qi','Qin','Qing','Qiu','Qu','Quay','Qui',
'Rei','Rin','Ring','Rinn','Rong','Rou','Ru','Rui','Ruo',
'Say','Sha','Shan','Shar','Shay','She','Shea','Shee','Shei','Sher','Shew','Shey','Shi','Shia','Shian','Shiao','Shie','Shin',
'Shok','Shu','Shui','Shuk','Shum','Si','Sia','Siah','Siak','Sian','Siao','Sien','Siew','Sim','Sin','Siu',
'So','Soh','Soi','Sok','Soo','Su','Suan','Sue','Suen','Sui','Suk','Sun',
'Tai','Tam','Tay','Tea','Teng','Tey','Thia','Ti','Tia','Tiah','Ting',
'Voon',
'Wan','Wen','Wey','Whey','Wi','Woon',
'Xi','Xia','Xiah','Xian','Xiao','Xiau','Xie','Xin','Xing','Xiu','Xu','Xun',
'Yam','Yan','Yee','Yen','Yep','Yeu','Yew','Yi','Yin','Ying','Yong','Yuan','Yue','Yuen','Yun','Yung','Yup',
'Zhen','Zhi','Zi','Zia','Ziah','Zo','Zoe','Zou','Zu','Zui',
);
/**
* @link https://en.wikipedia.org/wiki/List_of_Malaysians_of_Chinese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Cantonese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Chaoshanese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Chinese_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_English_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Hakka_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Hockchew_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Hokkien_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_people_of_Peranakan_descent
* @link https://en.wikipedia.org/wiki/Category:Malaysian_politicians_of_Chinese_descent
*/
protected static $firstNameMaleChristian = array(
'Aaron','Addy','Adrian','Alex','Amos','Anthony',
'Bernard','Billy',
'Chris','Christopher','Colin',
'Danell','Daniel','Danny','David','Douglas',
'Eddie','Eddy','Edmund','Eric',
'Francis','Frankie',
'Gary','Gavin','George','Gregory',
'Henry',
'Isaac',
'James','Jason','Jeff','Jeffrey','Jimmy','John','Jonathan','Josiah','Julian',
'Kevin','Kris',
'Mark','Martin','Mavin','Melvin','Michael',
'Nathaniel','Nelson','Nicholas',
'Peter','Philip',
'Richard','Robert','Roger','Ronny','Rynn',
'Shaun','Simon','Stephen','Steven',
'Terry','Tony',
'Victor','Vince','Vincent',
'Welson','William','Willie',
);
protected static $firstNameFemaleChristian = array(
'Alice','Alyssa','Amber','Amy','Andrea','Angelica','Angie','Apple','Aslina',
'Bernice','Betty','Boey','Bonnie',
'Caemen','Carey','Carmen','Carrie','Cindy',
'Debbie',
'Elaine','Elena',
'Felixia','Fish','Freya',
'Genervie','Gin',
'Hannah','Heidi','Helena',
'Janet','Jemie','Jess','Jesseca','Jessie','Joanna','Jolene','Joyce','Juliana',
'Karen','Kathleen',
'Lilian','Linda','Lydia','Lyndel',
'Maria','Marilyn','Maya','Meeia','Melinda','Melissa','Michelle','Michele',
'Nadia','Natalie','Nicole',
'Penny',
'Phyllis',
'Quincy',
'Rachel','Rena','Rose',
'Samantha','Sarah','Sheena','Sherine','Shevon','Sonia','Stella',
'Teresa','Tiffany','Tracy','Tricia',
'Vera','Violet','Vivian','Vivien',
'Yvonne',
);
/**
* @link https://en.wikipedia.org/wiki/List_of_Malaysians_of_Indian_descent
* @link https://en.wikipedia.org/wiki/List_of_Malaysian_politicians_of_Indian_descent
* @link https://en.wikipedia.org/wiki/List_of_Malaysian_sportspeople_of_Indian_descent
* @link https://en.wikipedia.org/wiki/Tamil_Malaysians#Notable_people
*/
protected static $initialIndian = array(
'B. ','B. C. ',
'C. ',
'D. ','D. R. ','D. S. ',
'E. ',
'G. ',
'K. ','K. L. ','K. R.','K. S. ',
'M. ','M. G. ','M. G. G. ','M. K. ',
'N. ','N. K. ',
'P. ',
'R. ','R. G. ','R. S. ',
'S. ','S. A. ',
'T. ',
'V. ','V. T. ',
);
/**
* @link https://en.wikipedia.org/wiki/List_of_Malaysians_of_Indian_descent
* @link https://en.wikipedia.org/wiki/K._L._Devaser
* @link https://en.wikipedia.org/wiki/List_of_Malaysian_politicians_of_Indian_descent
* @link https://en.wikipedia.org/wiki/List_of_Malaysian_sportspeople_of_Indian_descent
* @link https://en.wikipedia.org/wiki/Tamil_Malaysians#Notable_people
*/
protected static $firstNameMaleIndian = array(
'Anbil','Ananda','Arasu','Arul','Arulraj','Arumugam','Ash',
'Babu','Balachandra','Balasubramaniam','Balden','Baljit','Baltej','Bishan',
'Canagasabai','Cecil','Chakra','Chanturu',
'Depan','Darma Raja','Devaki','Devamany','Devan','Devasagayam','Diljit','Doraisingam',
'Ganesh','Ganga','Gengadharan','Gobalakrishnan','Gobind','Gopinathan','Govindasamy','Gunasekaran','Gurmit',
'Haran','Harikrish','Hiresh','Huzir',
'Indi',
'Jagdeep','Janil','Jeevandran','Jegathesan','Jeyakumar','Jomo Kwame',
'Kamal','Kamalanathan','Kanagaraj','Kandasamy','Kandiah','Karamjit','Karnail','Karpal','Kasi','Kasinather','Kavi','Kavidhai','Kishor','Krishen','Krishnamoorthy','Krishnamurthi','Krishnasamy','Kulasegaran','Kumar','Kumutha','Kuhan','Kunanlan','Kundan Lal','Kunjiraman',
'Loganathan',
'Magendran','Maha','Mahadev','Mahaletchumy','Mahathir','Maniam','Manickavasagam','Manikavasagam','Manjit','Manogaran','Manoharan','Manrick','Marimuthu','Merican','Mogan','Mohanadas','Munshi','Murugayan','Murugesan','Mutahir',
'Nadarajan','Nandakumar','Nanthakumar','Naraina','Nethaji','Ninian',
'Padathan','Palanivel','Param','Paramjit','Pavandeep','Praboo','Pragash','Premnath','Prema','Pria','Puvaneswaran',
'Rabinder','Rajagobal','Rajesh','Rajeswary','Rajiv','Rakesh','Rama','Ramasamy','Ramesh','Ramkarpal','Ramon','Rattan','Ravichandran','Rehman','Renuga','Rohan','Rueben',
'Saarvindran','Samy','Sanisvara','Sanjay','Santhara','Santokh','Sarath','Saravanan','Sarjit','Sasikumar','Satwant','Selvakkumar','Selvaraju','Serbegeth','Shan','Shankar','Shanmugam','Sittampalam','Sivakumar','Sivarasa','Solamalay','Sothinathan','Subramaniam','Sukhjit','Sumisha','Surendran','Suresh','Suriaprakash',
'Tatparanandam','Tanasekharan','Thamboosamy','Thamil','Thayaparan','Thirumurugan','Thirunavuk',
'Uthayakumar',
'Varatharaju','Veenod','Veerappan','Veerappen','Veloo','Vasudevan','Vellu','Viatilingam','Vijandren','Vinod','Vishnu','Vivasvan',
'Waythamoorthy','Weeratunge',
'Yosri','Yugendran',
);
/**
* @link https://en.wikipedia.org/wiki/List_of_Malaysians_of_Indian_descent
* @link https://en.wikipedia.org/wiki/List_of_Malaysian_politicians_of_Indian_descent
* @link https://en.wikipedia.org/wiki/List_of_Malaysian_sportspeople_of_Indian_descent
* @link https://en.wikipedia.org/wiki/Tamil_Malaysians#Notable_people
*/
protected static $firstNameFemaleIndian = array(
'Ambiga','Anaika','Anand','Anita','Asha','Athi',
'Gheetha',
'Haanii',
'Janaky',
'Kasthuriraani','Kavita','Kiran',
'Melinder',
'Nithya',
'Prashanthini','Preeta','Priya','Pushpa',
'Ramya','Rani','Rasammah','Renuga',
'Sangeeta','Sannatasah','Saraswati','Shamini','Shanthi','Shanti','Shoba','Shuba','Siva','Sutheaswari','Swarna','Sybil',
'Thanuja','Theiviya','Thripura',
'Umasundari','Uthaya',
'Vijaya',
'Zabrina',
);
/**
* @link https://en.wikipedia.org/wiki/List_of_Malaysians_of_Indian_descent
* @link https://en.wikipedia.org/wiki/List_of_Malaysian_politicians_of_Indian_descent
* @link https://en.wikipedia.org/wiki/List_of_Malaysian_sportspeople_of_Indian_descent
* @link https://en.wikipedia.org/wiki/Tamil_Malaysians#Notable_people
*/
protected static $lastNameIndian = array(
'Alagaratnam','Ambumamee','Ammasee','Ampalavanar','Ananthan','Arivanathan','Arujunan','Arumugam','Asirvatham','Autherapady',
'Balakrishnan','Balan','Bamadhaj','Bastianpillai','Bhullar','Bhupalan',
'Chandran','Cumaraswamy','Chelvan','Chengara',
'Dairiam','Davies','Devaraj','Devandran','Devaser','Dhaliwal','Dharmalingam','Dhillon',
'Elavarasan',
'Fernandes','Fernandez',
'Ganapathy','Ganesan','Gnanalingam','Goundar','Govindasamy','Gunalan','Gurusamy',
'Haridas',
'Iyer',
'Jaidka','Jassal','Jayaram','Jayaseelan','Jayawardene','Jeevananthan',
'Kaliappan','Kamalesvaran','Kandasamy','Karathu','Kathigasu','Kathiripillai','Kaveri','Kayveas','Krishnan','Krishnasamy','Kumar','Kumaresan','Kumari','Kunalan','Kundargal','Kuppusamy',
'Lakshmi','Linggam','Lourdenadin',
'Madhavan','Mahathevan','Malayalam','Manicka','Manikavasagam','Marimuthu','Menon','Mohinder','Moorthy','Mudukasan','Muniandy','Munisamy','Munusamy','Murugan','Murugeson',
'Nadarajah','Nagapan','Nagappan','Nagaraj','Nagarajan','Nahappan','Naidu','Nair','Namasivayam','Narayan','Navaratnam','Navarednam','Nayar','Nijhar',
'Pakiam','Palaniappan','Palanisamy','Panchanathan','Pandithan','Parthiban','Pathmanaban','Patto','Pereira','Perera','Periasamy','Perumal','Pillai','Pillay','Ponnusamy','Prakash','Puaneswaran','Purushothaman','Puspanathan','Puthucheary',
'Raj Kaur','Rajakumar','Rajan','Rajannaidu','Rajendra','Rajendran','Rajhans','Raju','Ramachandra','Ramadas','Ramadass','Ramanathan','Ramani','Ramasamy','Raj','Rao','Rasiah','Ratnam','Ravindran','Rayer','Retinam','Rishyakaran','Robbat',
'Sachithanandan','Sakadivan','Sakwati','Samarasan','Sambanthan','Sandrakasi','Sangalimuthu','Saniru','Sankar','Saravanan','Sathasivam','Sathianathan','Saunthararajah','Seenivasagam','Sekhar','Sellan','Selvanayagam','Selvarajoo','Selvaratnam','Shanmuganathan','Shanmugaratnam','Shekhar','Shivraj','Shree','Sidhu','Sinnandavar','Sinnathamby','Sinnathuray','Sivanesan','Singh','Sivalingam','Sivanesan','Shankar','Sodhy','Somasundram','Sooryapparad','Soti','Sreenevasan','Subramaniam','Sundram','Suppiah','Surendran',
'Thajudeen','Thalalla','Thambu','Thanabalasingam','Thanenthiran','Theseira','Thevandran','Thiru','Thirunavukarasu','Thivy','Thuraisingham','Tikaram',
'Vadaketh','Vadiveloo','Vanajah','Varman','Vasudevan','Veeran','Veerasamy','Veerasenan','Veerathan','Veetil','Velappan','Vello','Vengatarakoo','Vethamuthu','Viswalingam',
'Xavier',
);
/**
* @link https://en.wikipedia.org/wiki/Malay_styles_and_titles
*/
protected static $titleMale = array('En.','Dr.','Prof.','Datuk','Dato\'','Datuk Seri','Dato\' Sri','Tan Sri','Tun');
protected static $titleFemale = array('Pn.','Cik','Dr.','Prof.','Datin','Datin Paduka','Datin Paduka Seri','Puan Sri','Toh Puan');
/**
* Return a Malay male first name
*
* @example 'Ahmad'
*
* @return string
*/
public static function firstNameMaleMalay()
{
return static::randomElement(static::$firstNameMaleMalay);
}
/**
* Return a Malay female first name
*
* @example 'Adibah'
*
* @return string
*/
public static function firstNameFemaleMalay()
{
return static::randomElement(static::$firstNameFemaleMalay);
}
/**
* Return a Malay last name
*
* @example 'Abdullah'
*
* @return string
*/
public function lastNameMalay()
{
return static::randomElement(static::$lastNameMalay);
}
/**
* Return a Malay male 'Muhammad' name
*
* @example 'Muhammad'
*
* @return string
*/
public static function muhammadName()
{
return static::randomElement(static::$muhammadName);
}
/**
* Return a Malay female 'Nur' name
*
* @example 'Nur'
*
* @return string
*/
public static function nurName()
{
return static::randomElement(static::$nurName);
}
/**
* Return a Malay male 'Haji' title
*
* @example 'Haji'
*
* @return string
*/
public static function haji()
{
return static::randomElement(static::$haji);
}
/**
* Return a Malay female 'Hajjah' title
*
* @example 'Hajjah'
*
* @return string
*/
public static function hajjah()
{
return static::randomElement(static::$hajjah);
}
/**
* Return a Malay title
*
* @example 'Syed'
*
* @return string
*/
public static function titleMaleMalay()
{
return static::randomElement(static::$titleMaleMalay);
}
/**
* Return a Chinese last name
*
* @example 'Lim'
*
* @return string
*/
public static function lastNameChinese()
{
return static::randomElement(static::$lastNameChinese);
}
/**
* Return a Chinese male first name
*
* @example 'Goh Tong'
*
* @return string
*/
public static function firstNameMaleChinese()
{
return static::randomElement(static::$firstNameChinese) . ' ' . static::randomElement(static::$firstNameMaleChinese);
}
/**
* Return a Chinese female first name
*
* @example 'Mew Choo'
*
* @return string
*/
public static function firstNameFemaleChinese()
{
return static::randomElement(static::$firstNameChinese) . ' ' . static::randomElement(static::$firstNameFemaleChinese);
}
/**
* Return a Christian male name
*
* @example 'Aaron'
*
* @return string
*/
public static function firstNameMaleChristian()
{
return static::randomElement(static::$firstNameMaleChristian);
}
/**
* Return a Christian female name
*
* @example 'Alice'
*
* @return string
*/
public static function firstNameFemaleChristian()
{
return static::randomElement(static::$firstNameFemaleChristian);
}
/**
* Return an Indian initial
*
* @example 'S. '
*
* @return string
*/
public static function initialIndian()
{
return static::randomElement(static::$initialIndian);
}
/**
* Return an Indian male first name
*
* @example 'Arumugam'
*
* @return string
*/
public static function firstNameMaleIndian()
{
return static::randomElement(static::$firstNameMaleIndian);
}
/**
* Return an Indian female first name
*
* @example 'Ambiga'
*
* @return string
*/
public static function firstNameFemaleIndian()
{
return static::randomElement(static::$firstNameFemaleIndian);
}
/**
* Return an Indian last name
*
* @example 'Subramaniam'
*
* @return string
*/
public static function lastNameIndian()
{
return static::randomElement(static::$lastNameIndian);
}
/**
* Return a random last name
*
* @example 'Lee'
*
* @return string
*/
public function lastName()
{
$formats = array(
'{{lastNameMalay}}',
'{{lastNameChinese}}',
'{{lastNameIndian}}',
);
return $this->generator->parse(static::randomElement($formats));
}
/**
* Return a Malaysian I.C. No.
*
* @example '890123-45-6789'
*
* @link https://en.wikipedia.org/wiki/Malaysian_identity_card#Structure_of_the_National_Registration_Identity_Card_Number_(NRIC)
*
* @param string|null $gender 'male', 'female' or null for any
* @param bool|string|null $hyphen true, false, or any separator characters
*
* @return string
*/
public static function myKadNumber($gender = null, $hyphen = false)
{
// year of birth
$yy = mt_rand(0, 99);
// month of birth
$mm = DateTime::month();
// day of birth
$dd = DateTime::dayOfMonth();
// place of birth (1-59 except 17-20)
while (in_array(($pb = mt_rand(1, 59)), array(17, 18, 19, 20))) {
};
// random number
$nnn = mt_rand(0, 999);
// gender digit. Odd = MALE, Even = FEMALE
$g = mt_rand(0, 9);
//Credit: https://gist.github.com/mauris/3629548
if ($gender === static::GENDER_MALE) {
$g = $g | 1;
} elseif ($gender === static::GENDER_FEMALE) {
$g = $g & ~1;
}
// formatting with hyphen
if ($hyphen === true) {
$hyphen = "-";
} else if ($hyphen === false) {
$hyphen = "";
}
return sprintf("%02d%02d%02d%s%02d%s%03d%01d", $yy, $mm, $dd, $hyphen, $pb, $hyphen, $nnn, $g);
}
}
<?php
namespace Faker\Provider\ms_MY;
class PhoneNumber extends \Faker\Provider\PhoneNumber
{
protected static $formats = array(
'{{mobileNumber}}',
'{{fixedLineNumber}}',
'{{voipNumber}}'
);
protected static $plusSymbol = array(
'+'
);
protected static $countryCodePrefix = array(
'6'
);
/**
* @link https://en.wikipedia.org/wiki/Telephone_numbers_in_Malaysia#Mobile_phone_codes_and_IP_telephony
*/
protected static $zeroOneOnePrefix = array('10','11','12','13','14','15','16','17','18','19','20','22','23','32');
protected static $zeroOneFourPrefix = array('2','3','4','5','6','7','8','9');
protected static $zeroOneFivePrefix = array('1','2','3','4','5','6','9');
/**
* @link https://en.wikipedia.org/wiki/Telephone_numbers_in_Malaysia#Mobile_phone_codes_and_IP_telephony
*/
protected static $mobileNumberFormatsWithFormatting = array(
'010-### ####',
'011-{{zeroOneOnePrefix}}## ####',
'012-### ####',
'013-### ####',
'014-{{zeroOneFourPrefix}}## ####',
'016-### ####',
'017-### ####',
'018-### ####',
'019-### ####',
);
protected static $mobileNumberFormats = array(
'010#######',
'011{{zeroOneOnePrefix}}######',
'012#######',
'013#######',
'014{{zeroOneFourPrefix}}######',
'016#######',
'017#######',
'018#######',
'019#######',
);
/**
* @link https://en.wikipedia.org/wiki/Telephone_numbers_in_Malaysia#Geographic_area_codes
*/
protected static $fixedLineNumberFormatsWithFormatting = array(
'03-#### ####',
'04-### ####',
'05-### ####',
'06-### ####',
'07-### ####',
'08#-## ####',
'09-### ####',
);
protected static $fixedLineNumberFormats = array(
'03########',
'04#######',
'05#######',
'06#######',
'07#######',
'08#######',
'09#######',
);
/**
* @link https://en.wikipedia.org/wiki/Telephone_numbers_in_Malaysia#Mobile_phone_codes_and_IP_telephony
*/
protected static $voipNumberWithFormatting = array(
'015-{{zeroOneFivePrefix}}## ####'
);
protected static $voipNumber = array(
'015{{zeroOneFivePrefix}}######'
);
/**
* Return a Malaysian Mobile Phone Number.
*
* @example '+6012-345-6789'
*
* @param bool $countryCodePrefix true, false
* @param bool $formatting true, false
*
* @return string
*/
public function mobileNumber($countryCodePrefix = true, $formatting = true)
{
if ($formatting) {
$format = static::randomElement(static::$mobileNumberFormatsWithFormatting);
} else {
$format = static::randomElement(static::$mobileNumberFormats);
}
if ($countryCodePrefix) {
return static::countryCodePrefix($formatting) . static::numerify($this->generator->parse($format));
} else {
return static::numerify($this->generator->parse($format));
}
}
/**
* Return prefix digits for 011 numbers
*
* @example '10'
*
* @return string
*/
public static function zeroOneOnePrefix()
{
return static::numerify(static::randomElement(static::$zeroOneOnePrefix));
}
/**
* Return prefix digits for 014 numbers
*
* @example '2'
*
* @return string
*/
public static function zeroOneFourPrefix()
{
return static::numerify(static::randomElement(static::$zeroOneFourPrefix));
}
/**
* Return prefix digits for 015 numbers
*
* @example '1'
*
* @return string
*/
public static function zeroOneFivePrefix()
{
return static::numerify(static::randomElement(static::$zeroOneFivePrefix));
}
/**
* Return a Malaysian Fixed Line Phone Number.
*
* @example '+603-4567-8912'
*
* @param bool $countryCodePrefix true, false
* @param bool $formatting true, false
*
* @return string
*/
public function fixedLineNumber($countryCodePrefix = true, $formatting = true)
{
if ($formatting) {
$format = static::randomElement(static::$fixedLineNumberFormatsWithFormatting);
} else {
$format = static::randomElement(static::$fixedLineNumberFormats);
}
if ($countryCodePrefix) {
return static::countryCodePrefix($formatting) . static::numerify($this->generator->parse($format));
} else {
return static::numerify($this->generator->parse($format));
}
}
/**
* Return a Malaysian VoIP Phone Number.
*
* @example '+6015-678-9234'
*
* @param bool $countryCodePrefix true, false
* @param bool $formatting true, false
*
* @return string
*/
public function voipNumber($countryCodePrefix = true, $formatting = true)
{
if ($formatting) {
$format = static::randomElement(static::$voipNumberWithFormatting);
} else {
$format = static::randomElement(static::$voipNumber);
}
if ($countryCodePrefix) {
return static::countryCodePrefix($formatting) . static::numerify($this->generator->parse($format));
} else {
return static::numerify($this->generator->parse($format));
}
}
/**
* Return a Malaysian Country Code Prefix.
*
* @example '+6'
*
* @param bool $formatting true, false
*
* @return string
*/
public static function countryCodePrefix($formatting = true)
{
if ($formatting) {
return static::randomElement(static::$plusSymbol) . static::randomElement(static::$countryCodePrefix);
} else {
return static::randomElement(static::$countryCodePrefix);
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
<?php
namespace Faker\Provider\sl_SI;
class Company extends \Faker\Provider\Company
{
protected static $formats = array(
'{{firstName}} {{lastName}} s.p.',
'{{lastName}} {{companySuffix}}',
'{{lastName}}, {{lastName}} in {{lastName}} {{companySuffix}}',
);
protected static $companySuffix = array('d.o.o.', 'd.d.', 'k.d.', 'k.d.d.','d.n.o.','so.p.');
}
<?php
namespace Faker\Provider\th_TH;
class Color extends \Faker\Provider\Color
{
protected static $safeColorNames = array(
'ขาว','ชมพู','ดำ','น้ำตาล','น้ำเงิน','ฟ้า','ม่วง','ส้ม','เขียว','เขียวอ่อน','เหลือง','แดง'
);
protected static $allColorNames = array(
'กากี','ขาว','คราม','ชมพู','ดำ','ทอง','นาค','น้ำตาล',
'น้ำเงิน','ฟ้า','ม่วง','ส้ม','เขียว','เขียวอ่อน',
'เงิน','เทา','เหลือง','เหลืองอ่อน','แดง','่ขี้ม้า'
);
}
<?php
namespace Faker\Provider\tr_TR;
class Company extends \Faker\Provider\Company
{
protected static $formats = array(
'{{lastName}} {{companySuffix}}',
'{{lastName}}oğlu {{companySuffix}}',
'{{lastName}} {{lastName}} {{companySuffix}}',
'{{lastName}} {{companyField}} {{companySuffix}}',
'{{lastName}} {{companyField}} {{companySuffix}}',
'{{lastName}} {{companyField}} {{companySuffix}}',
'{{lastName}} {{lastName}} {{companyField}} {{companySuffix}}',
);
protected static $companySuffix = array('A.Ş.', 'Ltd. Şti.');
protected static $companyField = array(
'Akaryakıt', 'Beyaz Eşya', 'Bilgi İşlem', 'Bilgisayar', 'Bilişim Hizmetleri',
'Biracılık ve Malt Sanayii', 'Cam Sanayii', 'Çimento', 'Demir ve Çelik',
'Dış Ticaret', 'Eczacılık', 'Elektrik İletim', 'Elektrik Üretim', 'Elektronik',
'Emlak', 'Enerji', 'Giyim', 'Gıda', 'Holding', 'Isıtma ve Soğutma Sistemleri',
'İletişim Hizmetleri', 'İnşaat ve Sanayi', 'İthalat ve İhracat', 'Kimya',
'Kurumsal Hizmetler', 'Lojistik', 'Madencilik', 'Makina', 'Mağazalar', 'Nakliyat',
'Otomotiv', 'Pazarlama', 'Perakende Ticaret', 'Petrol', 'Petrolcülük', 'Sanayi',
'Sağlık Hizmetleri', 'Servis ve Ticaret', 'Süt Ürünleri', 'Tarım Sanayi',
'Tavukçuluk', 'Tekstil', 'Telekomunikasyon', 'Tersane ve Ulaşım Sanayi',
'Ticaret', 'Ticaret ve Sanayi', 'Ticaret ve Taahhüt', 'Turizm', 'Yatırım'
);
/**
* @link https://tr.wikipedia.org/wiki/Meslekler_listesi
* @note Randomly took 300 from this list
*/
protected static $jobTitleFormat = array(
'Acil tıp teknisyeni', 'Agronomist', 'Aile hekimi', 'Aktar', 'Aktör', 'Aktüer',
'Akustikçi', 'Albay', 'Ambarcı', 'Ambulans şoförü', 'Amiral', 'Analist',
'Antika satıcısı', 'Araba tamircisi', 'Arabacı', 'Araştırmacı', 'Armatör', 'Artist',
'Asker', 'Astrofizikçi', 'Astrolog', 'Astronom', 'Astronot', 'Atlet', 'Avukat',
'Ayakkabı boyacısı', 'Ayakkabı tamircisi', 'Ayakçı', 'Ağ yöneticisi', 'Aşçıbaşı',
'Bacacı', 'Badanacı', 'Baharatçı', 'Bahçe bitkileri uzmanı', 'Bakkal', 'Bakteriyolog',
'Balon pilotu', 'Bankacı', 'Banker', 'Barmeyd', 'Başdümenci', 'Başpiskopos',
'Başçavuş', 'Bebek Bakıcısı', 'Belediye başkanı', 'Belediye meclisi üyesi', 'Besteci',
'Biletçi', 'Bilgi İşlemci', 'Bilgisayar mühendisi', 'Binicilik', 'Biyografi yazarı',
'Bobinajcı', 'Borsacı', 'Boyacı', 'Bulaşıkçı', 'Börekç', 'Çamaşırcı', 'Çantacı',
'Çevik Kuvvet', 'Çevirmen', 'Çevre Mühendisi', 'Çevrebilimci', 'Çeyizci',
'Çiftlik işletici', 'Çiftçi', 'Çinici', 'Çoban', 'Çırak', 'Dadı', 'Daktilograf',
'Dalgıç', 'Dansöz', 'Dedektif', 'Derici', 'Değirmen işçisi', 'Değirmenci', 'Dilci',
'Diplomat', 'Doktor', 'Dokumacı', 'Dondurmacı', 'Doğramacı', 'Dövizci', 'Döşemeci',
'Elektrik mühendisi', 'Elektronik mühendisi', 'Elektronik ve Haberleşme mühendisi',
'Embriyolog', 'Emniyet amiri', 'Emniyet genel müdürü', 'Ergonomist', 'Eskici', 'Fahişe',
'Fizikçi', 'Fizyoterapist', 'Fotoğrafçı', 'Fıçıcı', 'Galerici', 'Garson',
'Gazete dağıtıcısı', 'Gazete satıcısı', 'Gazeteci', 'Gelir uzman yardımcısı', 'General',
'Genetik mühendisi', 'Gezici vaiz', 'Gondolcu', 'Guru', 'Gökbilimci', 'Gözlükçü',
'Güfteci', 'Gümrük uzmanı', 'Haham', 'Hakem', 'Halkbilimci', 'Hamal', 'Hamurkâr',
'Hareket memuru', 'Hava trafikçisi', 'Havacı', 'Hayvan terbiyecisi', 'Hesap uzmanı',
'Heykeltıraş', 'Hokkabaz', 'Irgat', 'İcra memuru', 'İllüzyonist', 'İmam',
'İnsan kaynakları uzmanı', 'İplikçi', 'İthalatçı', 'İş ve uğraşı terapisti', 'İşaretçi',
'Jimnastikçi', 'Jokey', 'Kabin görevlisi', 'Kabuk soyucusu', 'Kadın berberi', 'Kahveci',
'Kalaycı', 'Kaplamacı', 'Kapı satıcısı', 'Kardinal', 'Kardiyolog', 'Karikatürist',
'Kat görevlisi', 'Kaymakam', 'Kayıkçı', 'Kazıcı', 'Klarnetçi', 'Konserveci',
'Konveyör operatörü', 'Koramiral', 'Korgeneral', 'Kozmolog', 'Kuaför', 'Kumaşçı', 'Kumcu',
'Kuruyemişçi', 'Kurye', 'Kuyumcu', 'Kâğıtçı', 'Köpek eğiticisi', 'Köşe yazarı', 'Kürkçü',
'Kırtasiyeci', 'Laborant', 'Laboratuar işçisi', 'Lahmacuncu', 'Lehimci', 'Levazımcı',
'Lobici', 'Lokantacı', 'Lokman', 'Lostracı', 'Madenci', 'Makastar', 'Makine mühendisi',
'Makine zabiti', 'Makyajcı', 'Mali hizmetler uzmanı', 'Manastır baş rahibesi',
'Manifaturacı', 'Manikürcü', 'Masör', 'Matematikçi', 'Memur', 'Mermerci',
'Meteoroloji uzmanı', 'Misyoner', 'Model', 'Modelci', 'Modelist', 'Montajcı', 'Montör',
'Muallim', 'Muhafız', 'Mumyalayıcı', 'Müzik yönetmeni', 'Müşavir', 'Nalbant', 'Nalbur',
'Oduncu', 'Orgcu', 'Ornitolog', 'Oto elektrikçisi', 'Oto lastik tamircisi', 'Oyuncakçı',
'Oyuncu', 'Ön muhasebe yardımcı elemanı', 'Ön muhasebeci', 'Öğretim elemanı',
'Öğretim görevlisi', 'Öğretim üyesi', 'Papaz', 'Paramedik', 'Pastörizör', 'Pencereci',
'Perukçu', 'Peyzaj teknikeri', 'Peçeteci', 'Pideci', 'Pilot', 'Piyanist', 'Politikacı',
'Pompacı', 'Psikolog', 'Radyolog', 'Radyoloji teknisyeni/teknikeri', 'Rejisör',
'Reklamcı', 'Rektör', 'Rot balansçı', 'Saat tamircisi', 'Sanat yönetmeni', 'Saraç',
'Saz şairi', 'Sekreter', 'Ses teknisyeni', 'Sicil memuru', 'Sihirbaz', 'Sistem mühendisi',
'Sosyal hizmet uzmanı', 'Sosyolog', 'Soğuk demirci', 'Stenograf', 'Stilist', 'Striptizci',
'Sucu', 'Sunucu', 'Susuz araç yıkama', 'Sünnetçi', 'Sürveyan', 'Şapel papazı',
'Şarkı sözü yazarı', 'Şehir Plancısı', 'Şekerci', 'Şimşirci', 'Şoför', 'Tahsildar',
'Tarihçi', 'Tasarımcı', 'Taşlayıcı', 'Taşçı', 'Tekniker', 'Teknisyen', 'Teknoloji uzmani',
'Televizyon tamircisi', 'Terapist', 'Tesisatçı', 'Teşrifatçı', 'Tornacı', 'Tuğgeneral',
'Ulaşım sorumlusu', 'Ustabaşı', 'Uydu antenci', 'Üst Düzey Yönetici', 'Ütücü',
'Uzay bilimcisi', 'Vali', 'Veri hazırlama ve kontrol işletmeni', 'Veteriner hekim',
'Veteriner sağlık teknikeri', 'Veznedar', 'Vinç operatörü', 'Vitrinci', 'Yarbay',
'Yardımcı pilot', 'Yargıç', 'Yazar', 'Yazı işleri müdürü', 'Yazılım mühendisi',
'Yer gösterici', 'Yol bekçisi', 'Yorgancı', 'Yoğurtçu', 'Yıkıcı', 'Zabıta', 'Zoolog'
);
/**
* Returns a random company field.
*
* @return string
*/
public static function companyField()
{
return static::randomElement(static::$companyField);
}
}
<?php
namespace Faker\Test\Calculator;
use Faker\Calculator\TCNo;
use PHPUnit\Framework\TestCase;
class TCNoTest extends TestCase
{
public function checksumProvider()
{
return array(
array('553006348', '82'),
array('350630743', '78'),
array('550600932', '88'),
array('487932947', '70'),
array('168113862', '40')
);
}
/**
* @dataProvider checksumProvider
* @param $tcNo
* @param $checksum
*/
public function testChecksum($tcNo, $checksum)
{
$this->assertEquals($checksum, TCNo::checksum($tcNo), $tcNo);
}
public function validatorProvider()
{
return array(
array('22978160678', true),
array('26480045324', true),
array('47278360658', true),
array('34285002510', true),
array('19874561012', true),
array('11111111111', false),
array('11234567899', false),
);
}
/**
* @dataProvider validatorProvider
* @param $tcNo
* @param $isValid
*/
public function testIsValid($tcNo, $isValid)
{
$this->assertEquals($isValid, TCNo::isValid($tcNo), $tcNo);
}
}
<?php
namespace Faker\Test\Provider\el_GR;
use PHPUnit\Framework\TestCase;
class TextTest extends TestCase
{
private $textClass;
public function setUp()
{
$this->textClass = new \ReflectionClass('Faker\Provider\el_GR\Text');
}
protected function getMethod($name) {
$method = $this->textClass->getMethod($name);
$method->setAccessible(true);
return $method;
}
/** @test */
function testItShouldAppendEndPunctToTheEndOfString()
{
$this->assertSame(
'Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ '))
);
$this->assertSame(
'Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ—'))
);
$this->assertSame(
'Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ,'))
);
$this->assertSame(
'Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ!.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ! '))
);
$this->assertSame(
'Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ; '))
);
$this->assertSame(
'Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Και δεν άκουσες το κλοπακλόπ, κλοπακλόπ, κλοπακλόπ: '))
);
}
}
<?php
namespace Faker\Test\Provider\en_US;
use Faker\Provider\en_US\Company;
use Faker\Generator;
class CompanyTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Company($faker));
$this->faker = $faker;
}
/**
* @link https://stackoverflow.com/questions/4242433/regex-for-ein-number-and-ssn-number-format-in-jquery/35471665#35471665
*/
public function testEin()
{
$number = $this->faker->ein;
// should be in the format ##-#######, with a valid prefix
$this->assertRegExp('/^(0[1-6]||1[0-6]|2[0-7]|[35]\d|[468][0-8]|7[1-7]|9[0-58-9])-\d{7}$/', $number);
}
}
<?php
/**
* Created by Domingo Oropeza <dioh_@hotmail.com> for Faker
* Date: 01/09/2017
* Time: 09:45 PM
*/
namespace Faker\Test\Provider\es_VE;
use Faker\Generator;
use Faker\Provider\es_VE\Company;
use PHPUnit\Framework\TestCase;
class CompanyTest extends TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->seed(1);
$faker->addProvider(new Company($faker));
$this->faker = $faker;
}
/**
* national Id format validator
*/
public function testNationalId()
{
$pattern = '/^[VJGECP]-?\d{8}-?\d$/';
$rif = $this->faker->taxpayerIdentificationNumber;
$this->assertRegExp($pattern, $rif);
$rif = $this->faker->taxpayerIdentificationNumber('-');
$this->assertRegExp($pattern, $rif);
}
}
<?php
/**
* Created by Domingo Oropeza for Faker
* Date: 01/09/2017
* Time: 11:02 PM
*/
namespace Faker\Test\Provider\es_VE;
use Faker\Generator;
use Faker\Provider\es_VE\Person;
use PHPUnit\Framework\TestCase;
class PersonTest extends TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->seed(1);
$faker->addProvider(new Person($faker));
$this->faker = $faker;
}
/**
* national Id format validator
*/
public function testNationalId()
{
$pattern = '/(?:^V-?\d{5,9}$)|(?:^E-?\d{8,9}$)/';
$cedula = $this->faker->nationalId;
$this->assertRegExp($pattern, $cedula);
$cedula = $this->faker->nationalId('-');
$this->assertRegExp($pattern, $cedula);
}
}
<?php
namespace Faker\Test\Provider\fr_FR;
use Faker\Generator;
use Faker\Provider\fr_FR\Address;
use PHPUnit\Framework\TestCase;
class AddressTest extends TestCase
{
/**
* @var Faker\Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Address($faker));
$this->faker = $faker;
}
/**
* @test
*/
public function testSecondaryAddress()
{
$secondaryAdress = $this->faker->secondaryAddress();
$this->assertNotEmpty($secondaryAdress);
$this->assertInternalType('string', $secondaryAdress);
}
}
<?php
namespace Faker\Test\Provider\fr_FR;
use Faker\Calculator\Luhn;
use Faker\Generator;
use Faker\Provider\fr_FR\Payment;
use PHPUnit\Framework\TestCase;
class PaymentTest extends TestCase
{
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Payment($faker));
$this->faker = $faker;
}
public function testFormattedVat()
{
$vat = $this->faker->vat(true);
$this->assertRegExp("/^FR\s\w{2}\s\d{3}\s\d{3}\s\d{3}$/", $vat);
$vat = str_replace(' ', '', $vat);
$siren = substr($vat, 4, 12);
$this->assertTrue(Luhn::isValid($siren));
$key = (int) substr($siren, 2, 2);
if ($key === 0) {
$this->assertEqual($key, (12 + 3 * ($siren % 97)) % 97);
}
}
public function testUnformattedVat()
{
$vat = $this->faker->vat(false);
$this->assertRegExp("/^FR\w{2}\d{9}$/", $vat);
$siren = substr($vat, 4, 12);
$this->assertTrue(Luhn::isValid($siren));
$key = (int) substr($siren, 2, 2);
if ($key === 0) {
$this->assertEqual($key, (12 + 3 * ($siren % 97)) % 97);
}
}
}
\ No newline at end of file
<?php
namespace Faker\Test\Provider\fr_FR;
use Faker\Generator;
use Faker\Provider\fr_FR\PhoneNumber;
class PhoneNumberTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new PhoneNumber($faker));
$this->faker = $faker;
}
public function testMobileNumber()
{
$mobileNumber = $this->faker->mobileNumber();
$this->assertRegExp('/^(\+33 |\+33 \(0\)|0)(6|7)(?:(\s{1})?\d{2}){4}$/', $mobileNumber);
}
public function testMobileNumber07Format()
{
$mobileNumberFormat = $this->faker->phoneNumber07();
$this->assertRegExp('/^([3-9]{1})\d(\d{2}){3}$/', $mobileNumberFormat);
}
public function testMobileNumber07WithSeparatorFormat()
{
$mobileNumberFormat = $this->faker->phoneNumber07WithSeparator();
$this->assertRegExp('/^([3-9]{1})\d( \d{2}){3}$/', $mobileNumberFormat);
}
public function testServiceNumber()
{
$serviceNumber = $this->faker->serviceNumber();
$this->assertRegExp('/^(\+33 |\+33 \(0\)|0)8(?:(\s{1})?\d{2}){4}$/', $serviceNumber);
}
public function testServiceNumberFormat()
{
$serviceNumberFormat = $this->faker->phoneNumber08();
$this->assertRegExp('/^((0|1|2)\d{1}|9[^46])\d{6}$/', $serviceNumberFormat);
}
public function testServiceNumberWithSeparatorFormat()
{
$serviceNumberFormat = $this->faker->phoneNumber08WithSeparator();
$this->assertRegExp('/^((0|1|2)\d{1}|9[^46])( \d{2}){3}$/', $serviceNumberFormat);
}
}
<?php
namespace Faker\Test\Provider\fr_FR;
use PHPUnit\Framework\TestCase;
class TextTest extends TestCase
{
private $textClass;
public function setUp()
{
$this->textClass = new \ReflectionClass('Faker\Provider\fr_FR\Text');
}
protected function getMethod($name) {
$method = $this->textClass->getMethod($name);
$method->setAccessible(true);
return $method;
}
/** @test */
function testItShouldAppendEndPunctToTheEndOfString()
{
$this->assertSame(
'Que faisaient-elles maintenant? À.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Que faisaient-elles maintenant? À '))
);
$this->assertSame(
'Que faisaient-elles maintenant? À.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Que faisaient-elles maintenant? À— '))
);
$this->assertSame(
'Que faisaient-elles maintenant? À.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Que faisaient-elles maintenant? À,'))
);
$this->assertSame(
'Que faisaient-elles maintenant? À!.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Que faisaient-elles maintenant? À! '))
);
$this->assertSame(
'Que faisaient-elles maintenant? À.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Que faisaient-elles maintenant? À: '))
);
$this->assertSame(
'Que faisaient-elles maintenant? À.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Que faisaient-elles maintenant? À; '))
);
}
}
<?php
namespace Faker\Test\Provider\ka_GE;
use PHPUnit\Framework\TestCase;
class TextTest extends TestCase
{
private $textClass;
public function setUp()
{
$this->textClass = new \ReflectionClass('Faker\Provider\el_GR\Text');
}
protected function getMethod($name) {
$method = $this->textClass->getMethod($name);
$method->setAccessible(true);
return $method;
}
/** @test */
function testItShouldAppendEndPunctToTheEndOfString()
{
$this->assertSame(
'ჭეშმარიტია. ჩვენც ისე.',
$this->getMethod('appendEnd')->invokeArgs(null, array('ჭეშმარიტია. ჩვენც ისე '))
);
$this->assertSame(
'ჭეშმარიტია. ჩვენც ისე.',
$this->getMethod('appendEnd')->invokeArgs(null, array('ჭეშმარიტია. ჩვენც ისე— '))
);
$this->assertSame(
'ჭეშმარიტია. ჩვენც ისე.',
$this->getMethod('appendEnd')->invokeArgs(null, array('ჭეშმარიტია. ჩვენც ისე, '))
);
$this->assertSame(
'ჭეშმარიტია. ჩვენც ისე!.',
$this->getMethod('appendEnd')->invokeArgs(null, array('ჭეშმარიტია. ჩვენც ისე! '))
);
$this->assertSame(
'ჭეშმარიტია. ჩვენც ისე.',
$this->getMethod('appendEnd')->invokeArgs(null, array('ჭეშმარიტია. ჩვენც ისე; '))
);
$this->assertSame(
'ჭეშმარიტია. ჩვენც ისე.',
$this->getMethod('appendEnd')->invokeArgs(null, array('ჭეშმარიტია. ჩვენც ისე: '))
);
}
}
<?php
namespace Faker\Test\Provider\kk_KZ;
use PHPUnit\Framework\TestCase;
class TextTest extends TestCase
{
private $textClass;
public function setUp()
{
$this->textClass = new \ReflectionClass('Faker\Provider\kk_KZ\Text');
}
protected function getMethod($name) {
$method = $this->textClass->getMethod($name);
$method->setAccessible(true);
return $method;
}
/** @test */
function testItShouldAppendEndPunctToTheEndOfString()
{
$this->assertSame(
'Арыстан баб кесенесі - көне Отырар.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Арыстан баб кесенесі - көне Отырар '))
);
$this->assertSame(
'Арыстан баб кесенесі - көне Отырар.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Арыстан баб кесенесі - көне Отырар— '))
);
$this->assertSame(
'Арыстан баб кесенесі - көне Отырар.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Арыстан баб кесенесі - көне Отырар, '))
);
$this->assertSame(
'Арыстан баб кесенесі - көне Отырар!.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Арыстан баб кесенесі - көне Отырар! '))
);
$this->assertSame(
'Арыстан баб кесенесі - көне Отырар.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Арыстан баб кесенесі - көне Отырар: '))
);
$this->assertSame(
'Арыстан баб кесенесі - көне Отырар.',
$this->getMethod('appendEnd')->invokeArgs(null, array('Арыстан баб кесенесі - көне Отырар; '))
);
}
}
<?php
namespace Faker\Test\Provider\ko_KR;
use PHPUnit\Framework\TestCase;
class TextTest extends TestCase
{
private $textClass;
public function setUp()
{
$this->textClass = new \ReflectionClass('Faker\Provider\el_GR\Text');
}
protected function getMethod($name) {
$method = $this->textClass->getMethod($name);
$method->setAccessible(true);
return $method;
}
/** @test */
function testItShouldAppendEndPunctToTheEndOfString()
{
$this->assertSame(
'최석(崔晳)으로부터 최후의 편지가.',
$this->getMethod('appendEnd')->invokeArgs(null, array('최석(崔晳)으로부터 최후의 편지가 '))
);
$this->assertSame(
'최석(崔晳)으로부터 최후의 편지가.',
$this->getMethod('appendEnd')->invokeArgs(null, array('최석(崔晳)으로부터 최후의 편지가—'))
);
$this->assertSame(
'최석(崔晳)으로부터 최후의 편지가.',
$this->getMethod('appendEnd')->invokeArgs(null, array('최석(崔晳)으로부터 최후의 편지가,'))
);
$this->assertSame(
'최석(崔晳)으로부터 최후의 편지가!.',
$this->getMethod('appendEnd')->invokeArgs(null, array('최석(崔晳)으로부터 최후의 편지가! '))
);
$this->assertSame(
'최석(崔晳)으로부터 최후의 편지가.',
$this->getMethod('appendEnd')->invokeArgs(null, array('최석(崔晳)으로부터 최후의 편지가: '))
);
$this->assertSame(
'최석(崔晳)으로부터 최후의 편지가.',
$this->getMethod('appendEnd')->invokeArgs(null, array('최석(崔晳)으로부터 최후의 편지가; '))
);
}
}
<?php
namespace Faker\Test\Provider\ms_MY;
use Faker\Generator;
use Faker\Provider\ms_MY\Person;
use PHPUnit\Framework\TestCase;
class PersonTest extends TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Person($faker));
$this->faker = $faker;
}
/**
* @link https://en.wikipedia.org/wiki/Malaysian_identity_card#Structure_of_the_National_Registration_Identity_Card_Number_(NRIC)
*/
public function testPersonalIdentityCardNumber()
{
$myKadNumber = $this->faker->myKadNumber;
$yy = substr($myKadNumber, 0, 2);
//match any year from 00-99
$this->assertRegExp("/^[0-9]{2}$/", $yy);
$mm = substr($myKadNumber, 2, 2);
//match any month from 01-12
$this->assertRegExp("/^0[1-9]|1[0-2]$/", $mm);
$dd = substr($myKadNumber, 4, 2);
//match any date from 01-31
$this->assertRegExp("/^0[1-9]|1[0-9]|2[0-9]|3[0-1]$/", $dd);
$pb = substr($myKadNumber, 6, 2);
//match any valid place of birth code from 01-59 except 17-20
$this->assertRegExp("/^(0[1-9]|1[0-6])|(2[1-9]|3[0-9]|4[0-9]|5[0-9])$/", $pb);
$nnnn = substr($myKadNumber, 8, 4);
//match any number from 0000-9999
$this->assertRegExp("/^[0-9]{4}$/", $nnnn);
}
}
<?php
namespace Faker\Test\Provider\nl_BE;
use Faker\Generator;
use Faker\Provider\nl_BE\Person;
use PHPUnit\Framework\TestCase;
use Datetime;
/**
* @group Person
*/
class PersonTest extends TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Person($faker));
$this->faker = $faker;
}
public function testRrnIsValid()
{
$rrn = $this->faker->rrn();
$this->assertEquals(11, strlen($rrn));
$ctrlNumber = substr($rrn, 9, 2);
$calcCtrl = 97 - (substr($rrn, 0, 9) % 97);
$altcalcCtrl = 97 - ((2 . substr($rrn, 0, 9)) % 97);
$this->assertContains($ctrlNumber, array($calcCtrl, $altcalcCtrl));
$middle = substr($rrn, 6, 3);
$this->assertGreaterThan(1, $middle);
$this->assertLessThan(997, $middle);
}
public function testRrnIsMale()
{
$rrn = $this->faker->rrn('male');
$this->assertEquals(substr($rrn, 6, 3) % 2, 1);
}
public function testRrnIsFemale()
{
$rrn = $this->faker->rrn('female');
$this->assertEquals(substr($rrn, 6, 3) % 2, 0);
}
}
<?php
namespace Faker\Test\Provider\nl_NL;
use Faker\Generator;
use Faker\Provider\nl_NL\Person;
use PHPUnit\Framework\TestCase;
class PersonTest extends TestCase
{
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Person($faker));
$this->faker = $faker;
}
public function testGenerateValidIdNumber()
{
$idNumber = $this->faker->idNumber();
$this->assertEquals(9, strlen($idNumber));
$sum = -1 * $idNumber % 10;
for ($multiplier = 2; $idNumber > 0; $multiplier++) {
$val = ($idNumber /= 10) % 10;
$sum += $multiplier * $val;
}
$this->assertTrue($sum != 0 && $sum % 11 == 0);
}
}
<?php
namespace Faker\Provider\pl_PL;
use DateTime;
use Faker\Generator;
use PHPUnit\Framework\TestCase;
class PersonTest extends TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Person($faker));
$this->faker = $faker;
}
public function testPeselLenght()
{
$pesel = $this->faker->pesel();
$this->assertEquals(11, strlen($pesel));
}
public function testPeselDate()
{
$date = new DateTime('1990-01-01');
$pesel = $this->faker->pesel($date);
$this->assertEquals('90', substr($pesel, 0, 2));
$this->assertEquals('01', substr($pesel, 2, 2));
$this->assertEquals('01', substr($pesel, 4, 2));
}
public function testPeselDateWithYearAfter2000()
{
$date = new DateTime('2001-01-01');
$pesel = $this->faker->pesel($date);
$this->assertEquals('01', substr($pesel, 0, 2));
$this->assertEquals('21', substr($pesel, 2, 2));
$this->assertEquals('01', substr($pesel, 4, 2));
}
public function testPeselDateWithYearAfter2100()
{
$date = new DateTime('2101-01-01');
$pesel = $this->faker->pesel($date);
$this->assertEquals('01', substr($pesel, 0, 2));
$this->assertEquals('41', substr($pesel, 2, 2));
$this->assertEquals('01', substr($pesel, 4, 2));
}
public function testPeselDateWithYearAfter2200()
{
$date = new DateTime('2201-01-01');
$pesel = $this->faker->pesel($date);
$this->assertEquals('01', substr($pesel, 0, 2));
$this->assertEquals('61', substr($pesel, 2, 2));
$this->assertEquals('01', substr($pesel, 4, 2));
}
public function testPeselDateWithYearBefore1900()
{
$date = new DateTime('1801-01-01');
$pesel = $this->faker->pesel($date);
$this->assertEquals('01', substr($pesel, 0, 2));
$this->assertEquals('81', substr($pesel, 2, 2));
$this->assertEquals('01', substr($pesel, 4, 2));
}
public function testPeselSex()
{
$male = $this->faker->pesel(null, 'M');
$female = $this->faker->pesel(null, 'F');
$this->assertEquals(1, $male[9] % 2);
$this->assertEquals(0, $female[9] % 2);
}
public function testPeselCheckSum()
{
$pesel = $this->faker->pesel();
$weights = array(1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1);
$sum = 0;
foreach ($weights as $key => $weight) {
$sum += $pesel[$key] * $weight;
}
$this->assertEquals(0, $sum % 10);
}
}
<?php
namespace Faker\Test\Provider\ru_RU;
use PHPUnit\Framework\TestCase;
class TextTest extends TestCase
{
private $textClass;
public function setUp()
{
$this->textClass = new \ReflectionClass('Faker\Provider\ru_RU\Text');
}
protected function getMethod($name) {
$method = $this->textClass->getMethod($name);
$method->setAccessible(true);
return $method;
}
/** @test */
function testItShouldAppendEndPunctToTheEndOfString()
{
$this->assertSame(
'На другой день Чичиков отправился на обед и вечер.',
$this->getMethod('appendEnd')->invokeArgs(null, array('На другой день Чичиков отправился на обед и вечер '))
);
$this->assertSame(
'На другой день Чичиков отправился на обед и вечер.',
$this->getMethod('appendEnd')->invokeArgs(null, array('На другой день Чичиков отправился на обед и вечер—'))
);
$this->assertSame(
'На другой день Чичиков отправился на обед и вечер.',
$this->getMethod('appendEnd')->invokeArgs(null, array('На другой день Чичиков отправился на обед и вечер,'))
);
$this->assertSame(
'На другой день Чичиков отправился на обед и вечер!.',
$this->getMethod('appendEnd')->invokeArgs(null, array('На другой день Чичиков отправился на обед и вечер! '))
);
$this->assertSame(
'На другой день Чичиков отправился на обед и вечер.',
$this->getMethod('appendEnd')->invokeArgs(null, array('На другой день Чичиков отправился на обед и вечер; '))
);
$this->assertSame(
'На другой день Чичиков отправился на обед и вечер.',
$this->getMethod('appendEnd')->invokeArgs(null, array('На другой день Чичиков отправился на обед и вечер: '))
);
}
}
<?php
namespace Faker\Test\Provider\tr_TR;
use Faker\Provider\tr_TR\Company;
use Faker\Generator;
class CompanyTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Company($faker));
$this->faker = $faker;
}
public function testCompany()
{
$company = $this->faker->companyField;
$this->assertNotNull($company);
}
}
<?php
namespace Faker\Provider\tr_TR;
use Faker\Generator;
use PHPUnit\Framework\TestCase;
class PaymentTest extends TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Payment($faker));
$this->faker = $faker;
}
public function testBankAccountNumber()
{
$accNo = $this->faker->bankAccountNumber;
$this->assertEquals(substr($accNo, 0, 2), 'TR');
$this->assertEquals(26, strlen($accNo));
}
}
<?php
namespace Faker\Test\Provider\tr_TR;
use Faker\Calculator\TCNo;
use Faker\Provider\tr_TR\Person;
use Faker\Generator;
use PHPUnit\Framework\TestCase;
class PersonTest extends TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new Person($faker));
$this->faker = $faker;
}
public function testTCNo()
{
for ($i = 0; $i < 100; $i++) {
$number = $this->faker->tcNo;
$this->assertEquals(11, strlen($number));
$this->assertTrue(TCNo::isValid($number));
}
}
}
<?php
namespace Faker\Test\Provider\tr_TR;
use Faker\Generator;
use Faker\Provider\tr_TR\PhoneNumber;
use PHPUnit\Framework\TestCase;
class PhoneNumberTest extends TestCase
{
/**
* @var Generator
*/
private $faker;
public function setUp()
{
$faker = new Generator();
$faker->addProvider(new PhoneNumber($faker));
$this->faker = $faker;
}
public function testPhoneNumber()
{
for ($i = 0; $i < 100; $i++) {
$number = $this->faker->phoneNumber;
$baseNumber = preg_replace('/ *x.*$/', '', $number); // Remove possible extension
$digits = array_values(array_filter(str_split($baseNumber), 'ctype_digit'));
$this->assertGreaterThan(10, count($digits));
}
}
}
<?php
/**
* Message translations.
*
* This file is automatically generated by 'yii message' command.
* It contains the localizable messages extracted from source code.
* You may modify this file by translating the extracted messages.
*
* Each array element represents the translation (value) of a message (key).
* If the value is empty, the message is considered as not translated.
* Messages that no longer need translation will have their translations
* enclosed between a pair of '@@' marks.
*
* Message string can be used with plural forms format. Check i18n section
* of the guide for details.
*
* NOTE: this file must be saved in UTF-8 encoding.
*/
return [
'Select date' => 'Күнін таңдау',
'Clear field' => 'Тазарту',
];
<?php
/**
* Message translations.
*
* This file is automatically generated by 'yii message' command.
* It contains the localizable messages extracted from source code.
* You may modify this file by translating the extracted messages.
*
* Each array element represents the translation (value) of a message (key).
* If the value is empty, the message is considered as not translated.
* Messages that no longer need translation will have their translations
* enclosed between a pair of '@@' marks.
*
* Message string can be used with plural forms format. Check i18n section
* of the guide for details.
*
* NOTE: this file must be saved in UTF-8 encoding.
*/
return [
'Clear field' => 'Tyhjennä kenttä',
'Select date' => 'Valitse päivämäärä',
];
\ No newline at end of file
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Email address encoder.
*
* @author Christian Schmidt
*/
interface Swift_AddressEncoder
{
/**
* Encodes an email address.
*
* @throws Swift_AddressEncoderException If the email cannot be represented in
* the encoding implemented by this class.
*/
public function encodeString(string $address): string;
}
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An IDN email address encoder.
*
* Encodes the domain part of an address using IDN. This is compatible will all
* SMTP servers.
*
* This encoder does not support email addresses with non-ASCII characters in
* local-part (the substring before @). To send to such addresses, use
* Swift_AddressEncoder_Utf8AddressEncoder together with
* Swift_Transport_Esmtp_SmtpUtf8Handler. Your outbound SMTP server must support
* the SMTPUTF8 extension.
*
* @author Christian Schmidt
*/
class Swift_AddressEncoder_IdnAddressEncoder implements Swift_AddressEncoder
{
/**
* Encodes the domain part of an address using IDN.
*
* @throws Swift_AddressEncoderException If local-part contains non-ASCII characters,
* or if no suitable IDN encoder is installed.
*/
public function encodeString(string $address): string
{
$i = strrpos($address, '@');
if (false !== $i) {
$local = substr($address, 0, $i);
$domain = substr($address, $i + 1);
if (preg_match('/[^\x00-\x7F]/', $local)) {
throw new Swift_AddressEncoderException('Non-ASCII characters not supported in local-part', $address);
}
if (preg_match('/[^\x00-\x7F]/', $domain)) {
$address = sprintf('%s@%s', $local, $this->idnToAscii($domain));
}
}
return $address;
}
/**
* IDN-encodes a UTF-8 string to ASCII.
*/
protected function idnToAscii(string $string): string
{
if (function_exists('idn_to_ascii')) {
return idn_to_ascii($string, 0, INTL_IDNA_VARIANT_UTS46);
}
if (class_exists('TrueBV\Punycode')) {
$punycode = new \TrueBV\Punycode();
return $punycode->encode($string);
}
throw new Swift_AddressEncoderException('Non-ASCII characters in address, but no IDN encoder found (install the intl extension or the true/punycode package)', $address);
}
}
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A UTF-8 email address encoder.
*
* Returns the email address verbatimly in UTF-8 as permitted by RFC 6531 and
* RFC 6532. It supports addresses containing non-ASCII characters in both
* local-part and domain (i.e. on both sides of @).
*
* This encoder must be used together with Swift_Transport_Esmtp_SmtpUtf8Handler
* and requires that the outbound SMTP server supports the SMTPUTF8 extension.
*
* If your outbound SMTP server does not support SMTPUTF8, use
* Swift_AddressEncoder_IdnAddressEncoder instead. This allows sending to email
* addresses with non-ASCII characters in the domain, but not in local-part.
*
* @author Christian Schmidt
*/
class Swift_AddressEncoder_Utf8AddressEncoder implements Swift_AddressEncoder
{
/**
* Returns the address verbatimly.
*/
public function encodeString(string $address): string
{
return $address;
}
}
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* AddressEncoderException when the specified email address is in a format that
* cannot be encoded by a given address encoder.
*
* @author Christian Schmidt
*/
class Swift_AddressEncoderException extends Swift_RfcComplianceException
{
protected $address;
public function __construct(string $message, string $address)
{
parent::__construct($message);
$this->address = $address;
}
public function getAddress(): string
{
return $this->address;
}
}
<?php
/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Handles the case where the email body is already encoded and you just need specify the correct
* encoding without actually changing the encoding of the body.
*
* @author Jan Flora <jf@penneo.com>
*/
class Swift_Mime_ContentEncoder_NullContentEncoder implements Swift_Mime_ContentEncoder
{
/**
* The name of this encoding scheme (probably 7bit or 8bit).
*
* @var string
*/
private $_name;
/**
* Creates a new NullContentEncoder with $name (probably 7bit or 8bit).
*
* @param string $name
*/
public function __construct($name)
{
$this->_name = $name;
}
/**
* Encode a given string to produce an encoded string.
*
* @param string $string
* @param int $firstLineOffset ignored
* @param int $maxLineLength ignored
*
* @return string
*/
public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
{
return $string;
}
/**
* Encode stream $in to stream $out.
*
* @param int $firstLineOffset ignored
* @param int $maxLineLength ignored
*/
public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
{
while (false !== ($bytes = $os->read(8192))) {
$is->write($bytes);
}
}
/**
* Get the name of this encoding scheme.
*
* @return string
*/
public function getName()
{
return $this->_name;
}
/**
* Not used.
*/
public function charsetChanged($charset)
{
}
}
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An ESMTP handler for 8BITMIME support (RFC 6152).
*
* 8BITMIME is required when sending 8-bit content to over SMTP, e.g. when using
* Swift_Mime_ContentEncoder_PlainContentEncoder in "8bit" mode.
*
* 8BITMIME mode is enabled unconditionally, even when sending ASCII-only
* messages, so it should only be used with an outbound SMTP server that will
* convert the message to 7-bit MIME if the next hop does not support 8BITMIME.
*
* @author Christian Schmidt
*/
class Swift_Transport_Esmtp_EightBitMimeHandler implements Swift_Transport_EsmtpHandler
{
protected $encoding;
/**
* @param string $encoding The parameter so send with the MAIL FROM command;
* either "8BITMIME" or "7BIT"
*/
public function __construct(string $encoding = '8BITMIME')
{
$this->encoding = $encoding;
}
/**
* Get the name of the ESMTP extension this handles.
*
* @return string
*/
public function getHandledKeyword()
{
return '8BITMIME';
}
/**
* Not used.
*/
public function setKeywordParams(array $parameters)
{
}
/**
* Not used.
*/
public function afterEhlo(Swift_Transport_SmtpAgent $agent)
{
}
/**
* Get params which are appended to MAIL FROM:<>.
*
* @return string[]
*/
public function getMailParams()
{
return ['BODY='.$this->encoding];
}
/**
* Not used.
*/
public function getRcptParams()
{
return [];
}
/**
* Not used.
*/
public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false)
{
}
/**
* Returns +1, -1 or 0 according to the rules for usort().
*
* This method is called to ensure extensions can be execute in an appropriate order.
*
* @param string $esmtpKeyword to compare with
*
* @return int
*/
public function getPriorityOver($esmtpKeyword)
{
return 0;
}
/**
* Not used.
*/
public function exposeMixinMethods()
{
return [];
}
/**
* Not used.
*/
public function resetState()
{
}
}
<?php
/*
* This file is part of SwiftMailer.
* (c) 2018 Christian Schmidt
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* An ESMTP handler for SMTPUTF8 support (RFC 6531).
*
* SMTPUTF8 is required when sending to email addresses containing non-ASCII
* characters in local-part (the substring before @). This handler should be
* used together with Swift_AddressEncoder_Utf8AddressEncoder.
*
* SMTPUTF8 mode is enabled unconditionally, even when sending to ASCII-only
* addresses, so it should only be used with an outbound SMTP server that will
* deliver ASCII-only messages even if the next hop does not support SMTPUTF8.
*
* @author Christian Schmidt
*/
class Swift_Transport_Esmtp_SmtpUtf8Handler implements Swift_Transport_EsmtpHandler
{
public function __construct()
{
}
/**
* Get the name of the ESMTP extension this handles.
*
* @return string
*/
public function getHandledKeyword()
{
return 'SMTPUTF8';
}
/**
* Not used.
*/
public function setKeywordParams(array $parameters)
{
}
/**
* Not used.
*/
public function afterEhlo(Swift_Transport_SmtpAgent $agent)
{
}
/**
* Get params which are appended to MAIL FROM:<>.
*
* @return string[]
*/
public function getMailParams()
{
return ['SMTPUTF8'];
}
/**
* Not used.
*/
public function getRcptParams()
{
return [];
}
/**
* Not used.
*/
public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = [], &$failedRecipients = null, &$stop = false)
{
}
/**
* Returns +1, -1 or 0 according to the rules for usort().
*
* This method is called to ensure extensions can be execute in an appropriate order.
*
* @param string $esmtpKeyword to compare with
*
* @return int
*/
public function getPriorityOver($esmtpKeyword)
{
return 0;
}
/**
* Not used.
*/
public function exposeMixinMethods()
{
return [];
}
/**
* Not used.
*/
public function resetState()
{
}
}
vendor/
composer.lock
phpunit.xml
CHANGELOG
=========
4.1.0
-----
* added the `Process::isTtySupported()` method that allows to check for TTY support
* made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary
* added the `ProcessSignaledException` class to properly catch signaled process errors
4.0.0
-----
* environment variables will always be inherited
* added a second `array $env = array()` argument to the `start()`, `run()`,
`mustRun()`, and `restart()` methods of the `Process` class
* added a second `array $env = array()` argument to the `start()` method of the
`PhpProcess` class
* the `ProcessUtils::escapeArgument()` method has been removed
* the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()`
methods of the `Process` class have been removed
* support for passing `proc_open()` options has been removed
* removed the `ProcessBuilder` class, use the `Process` class instead
* removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class
* passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not
supported anymore
3.4.0
-----
* deprecated the ProcessBuilder class
* deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor)
3.3.0
-----
* added command line arrays in the `Process` class
* added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods
* deprecated the `ProcessUtils::escapeArgument()` method
* deprecated not inheriting environment variables
* deprecated configuring `proc_open()` options
* deprecated configuring enhanced Windows compatibility
* deprecated configuring enhanced sigchild compatibility
2.5.0
-----
* added support for PTY mode
* added the convenience method "mustRun"
* deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
* deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
* deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
2.4.0
-----
* added the ability to define an idle timeout
2.3.0
-----
* added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
* added Process::signal()
* added Process::getPid()
* added support for a TTY mode
2.2.0
-----
* added ProcessBuilder::setArguments() to reset the arguments on a builder
* added a way to retrieve the standard and error output incrementally
* added Process:restart()
2.1.0
-----
* added support for non-blocking processes (start(), wait(), isRunning(), stop())
* enhanced Windows compatibility
* added Process::getExitCodeText() that returns a string representation for
the exit code returned by the process
* added ProcessBuilder
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* Marker Interface for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* InvalidArgumentException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* LogicException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception for failed processes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessFailedException extends RuntimeException
{
private $process;
public function __construct(Process $process)
{
if ($process->isSuccessful()) {
throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
}
$error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
$process->getCommandLine(),
$process->getExitCode(),
$process->getExitCodeText(),
$process->getWorkingDirectory()
);
if (!$process->isOutputDisabled()) {
$error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
$process->getOutput(),
$process->getErrorOutput()
);
}
parent::__construct($error);
$this->process = $process;
}
public function getProcess()
{
return $this->process;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception that is thrown when a process has been signaled.
*
* @author Sullivan Senechal <soullivaneuh@gmail.com>
*/
final class ProcessSignaledException extends RuntimeException
{
private $process;
public function __construct(Process $process)
{
$this->process = $process;
parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
}
public function getProcess(): Process
{
return $this->process;
}
public function getSignal(): int
{
return $this->getProcess()->getTermSignal();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception that is thrown when a process times out.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessTimedOutException extends RuntimeException
{
const TYPE_GENERAL = 1;
const TYPE_IDLE = 2;
private $process;
private $timeoutType;
public function __construct(Process $process, int $timeoutType)
{
$this->process = $process;
$this->timeoutType = $timeoutType;
parent::__construct(sprintf(
'The process "%s" exceeded the timeout of %s seconds.',
$process->getCommandLine(),
$this->getExceededTimeout()
));
}
public function getProcess()
{
return $this->process;
}
public function isGeneralTimeout()
{
return self::TYPE_GENERAL === $this->timeoutType;
}
public function isIdleTimeout()
{
return self::TYPE_IDLE === $this->timeoutType;
}
public function getExceededTimeout()
{
switch ($this->timeoutType) {
case self::TYPE_GENERAL:
return $this->process->getTimeout();
case self::TYPE_IDLE:
return $this->process->getIdleTimeout();
default:
throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* RuntimeException for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* Generic executable finder.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ExecutableFinder
{
private $suffixes = array('.exe', '.bat', '.cmd', '.com');
/**
* Replaces default suffixes of executable.
*/
public function setSuffixes(array $suffixes)
{
$this->suffixes = $suffixes;
}
/**
* Adds new possible suffix to check for executable.
*
* @param string $suffix
*/
public function addSuffix($suffix)
{
$this->suffixes[] = $suffix;
}
/**
* Finds an executable by name.
*
* @param string $name The executable name (without the extension)
* @param string $default The default to return if no executable is found
* @param array $extraDirs Additional dirs to check into
*
* @return string The executable path or default value
*/
public function find($name, $default = null, array $extraDirs = array())
{
if (ini_get('open_basedir')) {
$searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir'));
$dirs = array();
foreach ($searchPath as $path) {
// Silencing against https://bugs.php.net/69240
if (@is_dir($path)) {
$dirs[] = $path;
} else {
if (basename($path) == $name && @is_executable($path)) {
return $path;
}
}
}
} else {
$dirs = array_merge(
explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs
);
}
$suffixes = array('');
if ('\\' === DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
}
foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) {
if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || @is_executable($file))) {
return $file;
}
}
}
return $default;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* Provides a way to continuously write to the input of a Process until the InputStream is closed.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class InputStream implements \IteratorAggregate
{
/** @var null|callable */
private $onEmpty = null;
private $input = array();
private $open = true;
/**
* Sets a callback that is called when the write buffer becomes empty.
*/
public function onEmpty(callable $onEmpty = null)
{
$this->onEmpty = $onEmpty;
}
/**
* Appends an input to the write buffer.
*
* @param resource|string|int|float|bool|\Traversable|null The input to append as scalar,
* stream resource or \Traversable
*/
public function write($input)
{
if (null === $input) {
return;
}
if ($this->isClosed()) {
throw new RuntimeException(sprintf('%s is closed', static::class));
}
$this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
}
/**
* Closes the write buffer.
*/
public function close()
{
$this->open = false;
}
/**
* Tells whether the write buffer is closed or not.
*/
public function isClosed()
{
return !$this->open;
}
public function getIterator()
{
$this->open = true;
while ($this->open || $this->input) {
if (!$this->input) {
yield '';
continue;
}
$current = array_shift($this->input);
if ($current instanceof \Iterator) {
foreach ($current as $cur) {
yield $cur;
}
} else {
yield $current;
}
if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
$this->write($onEmpty($this));
}
}
}
}
Copyright (c) 2004-2018 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* An executable finder specifically designed for the PHP executable.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PhpExecutableFinder
{
private $executableFinder;
public function __construct()
{
$this->executableFinder = new ExecutableFinder();
}
/**
* Finds The PHP executable.
*
* @param bool $includeArgs Whether or not include command arguments
*
* @return string|false The PHP executable path or false if it cannot be found
*/
public function find($includeArgs = true)
{
if ($php = getenv('PHP_BINARY')) {
if (!is_executable($php)) {
return false;
}
return $php;
}
$args = $this->findArguments();
$args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
// PHP_BINARY return the current sapi executable
if (PHP_BINARY && \in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg'), true)) {
return PHP_BINARY.$args;
}
if ($php = getenv('PHP_PATH')) {
if (!@is_executable($php)) {
return false;
}
return $php;
}
if ($php = getenv('PHP_PEAR_PHP_BIN')) {
if (@is_executable($php)) {
return $php;
}
}
if (@is_executable($php = PHP_BINDIR.('\\' === DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
return $php;
}
$dirs = array(PHP_BINDIR);
if ('\\' === DIRECTORY_SEPARATOR) {
$dirs[] = 'C:\xampp\php\\';
}
return $this->executableFinder->find('php', false, $dirs);
}
/**
* Finds the PHP executable arguments.
*
* @return array The PHP executable arguments
*/
public function findArguments()
{
$arguments = array();
if ('phpdbg' === PHP_SAPI) {
$arguments[] = '-qrr';
}
return $arguments;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* PhpProcess runs a PHP script in an independent process.
*
* $p = new PhpProcess('<?php echo "foo"; ?>');
* $p->run();
* print $p->getOutput()."\n";
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PhpProcess extends Process
{
/**
* @param string $script The PHP script to run (as a string)
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param int $timeout The timeout in seconds
*/
public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60)
{
$executableFinder = new PhpExecutableFinder();
if (false === $php = $executableFinder->find(false)) {
$php = null;
} else {
$php = array_merge(array($php), $executableFinder->findArguments());
}
if ('phpdbg' === PHP_SAPI) {
$file = tempnam(sys_get_temp_dir(), 'dbg');
file_put_contents($file, $script);
register_shutdown_function('unlink', $file);
$php[] = $file;
$script = null;
}
parent::__construct($php, $cwd, $env, $script, $timeout);
}
/**
* Sets the path to the PHP binary to use.
*/
public function setPhpBinary($php)
{
$this->setCommandLine($php);
}
/**
* {@inheritdoc}
*/
public function start(callable $callback = null, array $env = array())
{
if (null === $this->getCommandLine()) {
throw new RuntimeException('Unable to find the PHP executable.');
}
parent::start($callback, $env);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
abstract class AbstractPipes implements PipesInterface
{
public $pipes = array();
private $inputBuffer = '';
private $input;
private $blocked = true;
private $lastError;
/**
* @param resource|string|int|float|bool|\Iterator|null $input
*/
public function __construct($input)
{
if (is_resource($input) || $input instanceof \Iterator) {
$this->input = $input;
} elseif (is_string($input)) {
$this->inputBuffer = $input;
} else {
$this->inputBuffer = (string) $input;
}
}
/**
* {@inheritdoc}
*/
public function close()
{
foreach ($this->pipes as $pipe) {
fclose($pipe);
}
$this->pipes = array();
}
/**
* Returns true if a system call has been interrupted.
*
* @return bool
*/
protected function hasSystemCallBeenInterrupted()
{
$lastError = $this->lastError;
$this->lastError = null;
// stream_select returns false when the `select` system call is interrupted by an incoming signal
return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
}
/**
* Unblocks streams.
*/
protected function unblock()
{
if (!$this->blocked) {
return;
}
foreach ($this->pipes as $pipe) {
stream_set_blocking($pipe, 0);
}
if (is_resource($this->input)) {
stream_set_blocking($this->input, 0);
}
$this->blocked = false;
}
/**
* Writes input to stdin.
*
* @throws InvalidArgumentException When an input iterator yields a non supported value
*/
protected function write()
{
if (!isset($this->pipes[0])) {
return;
}
$input = $this->input;
if ($input instanceof \Iterator) {
if (!$input->valid()) {
$input = null;
} elseif (is_resource($input = $input->current())) {
stream_set_blocking($input, 0);
} elseif (!isset($this->inputBuffer[0])) {
if (!is_string($input)) {
if (!is_scalar($input)) {
throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', get_class($this->input), gettype($input)));
}
$input = (string) $input;
}
$this->inputBuffer = $input;
$this->input->next();
$input = null;
} else {
$input = null;
}
}
$r = $e = array();
$w = array($this->pipes[0]);
// let's have a look if something changed in streams
if (false === @stream_select($r, $w, $e, 0, 0)) {
return;
}
foreach ($w as $stdin) {
if (isset($this->inputBuffer[0])) {
$written = fwrite($stdin, $this->inputBuffer);
$this->inputBuffer = substr($this->inputBuffer, $written);
if (isset($this->inputBuffer[0])) {
return array($this->pipes[0]);
}
}
if ($input) {
for (;;) {
$data = fread($input, self::CHUNK_SIZE);
if (!isset($data[0])) {
break;
}
$written = fwrite($stdin, $data);
$data = substr($data, $written);
if (isset($data[0])) {
$this->inputBuffer = $data;
return array($this->pipes[0]);
}
}
if (feof($input)) {
if ($this->input instanceof \Iterator) {
$this->input->next();
} else {
$this->input = null;
}
}
}
}
// no input to read on resource, buffer is empty
if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
$this->input = null;
fclose($this->pipes[0]);
unset($this->pipes[0]);
} elseif (!$w) {
return array($this->pipes[0]);
}
}
/**
* @internal
*/
public function handleError($type, $msg)
{
$this->lastError = $msg;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
/**
* PipesInterface manages descriptors and pipes for the use of proc_open.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
interface PipesInterface
{
const CHUNK_SIZE = 16384;
/**
* Returns an array of descriptors for the use of proc_open.
*
* @return array
*/
public function getDescriptors();
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
*
* @return string[]
*/
public function getFiles();
/**
* Reads data in file handles and pipes.
*
* @param bool $blocking Whether to use blocking calls or not
* @param bool $close Whether to close pipes if they've reached EOF
*
* @return string[] An array of read data indexed by their fd
*/
public function readAndWrite($blocking, $close = false);
/**
* Returns if the current state has open file handles or pipes.
*
* @return bool
*/
public function areOpen();
/**
* Returns if pipes are able to read output.
*
* @return bool
*/
public function haveReadSupport();
/**
* Closes file handles and pipes.
*/
public function close();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Process;
/**
* UnixPipes implementation uses unix pipes as handles.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class UnixPipes extends AbstractPipes
{
private $ttyMode;
private $ptyMode;
private $haveReadSupport;
public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
{
$this->ttyMode = $ttyMode;
$this->ptyMode = $ptyMode;
$this->haveReadSupport = $haveReadSupport;
parent::__construct($input);
}
public function __destruct()
{
$this->close();
}
/**
* {@inheritdoc}
*/
public function getDescriptors()
{
if (!$this->haveReadSupport) {
$nullstream = fopen('/dev/null', 'c');
return array(
array('pipe', 'r'),
$nullstream,
$nullstream,
);
}
if ($this->ttyMode) {
return array(
array('file', '/dev/tty', 'r'),
array('file', '/dev/tty', 'w'),
array('file', '/dev/tty', 'w'),
);
}
if ($this->ptyMode && Process::isPtySupported()) {
return array(
array('pty'),
array('pty'),
array('pty'),
);
}
return array(
array('pipe', 'r'),
array('pipe', 'w'), // stdout
array('pipe', 'w'), // stderr
);
}
/**
* {@inheritdoc}
*/
public function getFiles()
{
return array();
}
/**
* {@inheritdoc}
*/
public function readAndWrite($blocking, $close = false)
{
$this->unblock();
$w = $this->write();
$read = $e = array();
$r = $this->pipes;
unset($r[0]);
// let's have a look if something changed in streams
set_error_handler(array($this, 'handleError'));
if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
restore_error_handler();
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = array();
}
return $read;
}
restore_error_handler();
foreach ($r as $pipe) {
// prior PHP 5.4 the array passed to stream_select is modified and
// lose key association, we have to find back the key
$read[$type = array_search($pipe, $this->pipes, true)] = '';
do {
$data = fread($pipe, self::CHUNK_SIZE);
$read[$type] .= $data;
} while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
if (!isset($read[$type][0])) {
unset($read[$type]);
}
if ($close && feof($pipe)) {
fclose($pipe);
unset($this->pipes[$type]);
}
}
return $read;
}
/**
* {@inheritdoc}
*/
public function haveReadSupport()
{
return $this->haveReadSupport;
}
/**
* {@inheritdoc}
*/
public function areOpen()
{
return (bool) $this->pipes;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* WindowsPipes implementation uses temporary files as handles.
*
* @see https://bugs.php.net/bug.php?id=51800
* @see https://bugs.php.net/bug.php?id=65650
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class WindowsPipes extends AbstractPipes
{
private $files = array();
private $fileHandles = array();
private $readBytes = array(
Process::STDOUT => 0,
Process::STDERR => 0,
);
private $haveReadSupport;
public function __construct($input, bool $haveReadSupport)
{
$this->haveReadSupport = $haveReadSupport;
if ($this->haveReadSupport) {
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//
// @see https://bugs.php.net/bug.php?id=51800
$pipes = array(
Process::STDOUT => Process::OUT,
Process::STDERR => Process::ERR,
);
$tmpCheck = false;
$tmpDir = sys_get_temp_dir();
$lastError = 'unknown reason';
set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
for ($i = 0;; ++$i) {
foreach ($pipes as $pipe => $name) {
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
if (file_exists($file) && !unlink($file)) {
continue 2;
}
$h = fopen($file, 'xb');
if (!$h) {
$error = $lastError;
if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) {
continue;
}
restore_error_handler();
throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error));
}
if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) {
continue 2;
}
if (isset($this->files[$pipe])) {
unlink($this->files[$pipe]);
}
$this->files[$pipe] = $file;
}
break;
}
restore_error_handler();
}
parent::__construct($input);
}
public function __destruct()
{
$this->close();
$this->removeFiles();
}
/**
* {@inheritdoc}
*/
public function getDescriptors()
{
if (!$this->haveReadSupport) {
$nullstream = fopen('NUL', 'c');
return array(
array('pipe', 'r'),
$nullstream,
$nullstream,
);
}
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
// So we redirect output within the commandline and pass the nul device to the process
return array(
array('pipe', 'r'),
array('file', 'NUL', 'w'),
array('file', 'NUL', 'w'),
);
}
/**
* {@inheritdoc}
*/
public function getFiles()
{
return $this->files;
}
/**
* {@inheritdoc}
*/
public function readAndWrite($blocking, $close = false)
{
$this->unblock();
$w = $this->write();
$read = $r = $e = array();
if ($blocking) {
if ($w) {
@stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
} elseif ($this->fileHandles) {
usleep(Process::TIMEOUT_PRECISION * 1E6);
}
}
foreach ($this->fileHandles as $type => $fileHandle) {
$data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
if (isset($data[0])) {
$this->readBytes[$type] += strlen($data);
$read[$type] = $data;
}
if ($close) {
fclose($fileHandle);
unset($this->fileHandles[$type]);
}
}
return $read;
}
/**
* {@inheritdoc}
*/
public function haveReadSupport()
{
return $this->haveReadSupport;
}
/**
* {@inheritdoc}
*/
public function areOpen()
{
return $this->pipes && $this->fileHandles;
}
/**
* {@inheritdoc}
*/
public function close()
{
parent::close();
foreach ($this->fileHandles as $handle) {
fclose($handle);
}
$this->fileHandles = array();
}
/**
* Removes temporary files.
*/
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = array();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Pipes\UnixPipes;
use Symfony\Component\Process\Pipes\WindowsPipes;
/**
* Process is a thin wrapper around proc_* functions to easily
* start independent PHP processes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Romain Neutron <imprec@gmail.com>
*/
class Process implements \IteratorAggregate
{
const ERR = 'err';
const OUT = 'out';
const STATUS_READY = 'ready';
const STATUS_STARTED = 'started';
const STATUS_TERMINATED = 'terminated';
const STDIN = 0;
const STDOUT = 1;
const STDERR = 2;
// Timeout Precision in seconds.
const TIMEOUT_PRECISION = 0.2;
const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
private $callback;
private $hasCallback = false;
private $commandline;
private $cwd;
private $env;
private $input;
private $starttime;
private $lastOutputTime;
private $timeout;
private $idleTimeout;
private $exitcode;
private $fallbackStatus = array();
private $processInformation;
private $outputDisabled = false;
private $stdout;
private $stderr;
private $process;
private $status = self::STATUS_READY;
private $incrementalOutputOffset = 0;
private $incrementalErrorOutputOffset = 0;
private $tty;
private $pty;
private $useFileHandles = false;
/** @var PipesInterface */
private $processPipes;
private $latestSignal;
private static $sigchild;
/**
* Exit codes translation table.
*
* User-defined errors must use exit codes in the 64-113 range.
*/
public static $exitCodes = array(
0 => 'OK',
1 => 'General error',
2 => 'Misuse of shell builtins',
126 => 'Invoked command cannot execute',
127 => 'Command not found',
128 => 'Invalid exit argument',
// signals
129 => 'Hangup',
130 => 'Interrupt',
131 => 'Quit and dump core',
132 => 'Illegal instruction',
133 => 'Trace/breakpoint trap',
134 => 'Process aborted',
135 => 'Bus error: "access to undefined portion of memory object"',
136 => 'Floating point exception: "erroneous arithmetic operation"',
137 => 'Kill (terminate immediately)',
138 => 'User-defined 1',
139 => 'Segmentation violation',
140 => 'User-defined 2',
141 => 'Write to pipe with no one reading',
142 => 'Signal raised by alarm',
143 => 'Termination (request to terminate)',
// 144 - not defined
145 => 'Child process terminated, stopped (or continued*)',
146 => 'Continue if stopped',
147 => 'Stop executing temporarily',
148 => 'Terminal stop signal',
149 => 'Background process attempting to read from tty ("in")',
150 => 'Background process attempting to write to tty ("out")',
151 => 'Urgent data available on socket',
152 => 'CPU time limit exceeded',
153 => 'File size limit exceeded',
154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
155 => 'Profiling timer expired',
// 156 - not defined
157 => 'Pollable event',
// 158 - not defined
159 => 'Bad syscall',
);
/**
* @param string|array $commandline The command line to run
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
*
* @throws RuntimeException When proc_open is not installed
*/
public function __construct($commandline, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
if (!function_exists('proc_open')) {
throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
}
$this->commandline = $commandline;
$this->cwd = $cwd;
// on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
// on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
// @see : https://bugs.php.net/bug.php?id=51800
// @see : https://bugs.php.net/bug.php?id=50524
if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) {
$this->cwd = getcwd();
}
if (null !== $env) {
$this->setEnv($env);
}
$this->setInput($input);
$this->setTimeout($timeout);
$this->useFileHandles = '\\' === DIRECTORY_SEPARATOR;
$this->pty = false;
}
public function __destruct()
{
$this->stop(0);
}
public function __clone()
{
$this->resetProcessData();
}
/**
* Runs the process.
*
* The callback receives the type of output (out or err) and
* some bytes from the output in real-time. It allows to have feedback
* from the independent process during execution.
*
* The STDOUT and STDERR are also available after the process is finished
* via the getOutput() and getErrorOutput() methods.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param array $env An array of additional env vars to set when running the process
*
* @return int The exit status code
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
*
* @final
*/
public function run(callable $callback = null, array $env = array()): int
{
$this->start($callback, $env);
return $this->wait();
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param callable|null $callback
* @param array $env An array of additional env vars to set when running the process
*
* @return self
*
* @throws ProcessFailedException if the process didn't terminate successfully
*
* @final
*/
public function mustRun(callable $callback = null, array $env = array())
{
if (0 !== $this->run($callback, $env)) {
throw new ProcessFailedException($this);
}
return $this;
}
/**
* Starts the process and returns after writing the input to STDIN.
*
* This method blocks until all STDIN data is sent to the process then it
* returns while the process runs in the background.
*
* The termination of the process can be awaited with wait().
*
* The callback receives the type of output (out or err) and some bytes from
* the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param array $env An array of additional env vars to set when running the process
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws LogicException In case a callback is provided and output has been disabled
*/
public function start(callable $callback = null, array $env = array())
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running');
}
$this->resetProcessData();
$this->starttime = $this->lastOutputTime = microtime(true);
$this->callback = $this->buildCallback($callback);
$this->hasCallback = null !== $callback;
$descriptors = $this->getDescriptors();
if (is_array($commandline = $this->commandline)) {
$commandline = implode(' ', array_map(array($this, 'escapeArgument'), $commandline));
if ('\\' !== DIRECTORY_SEPARATOR) {
// exec is mandatory to deal with sending a signal to the process
$commandline = 'exec '.$commandline;
}
}
if ($this->env) {
$env += $this->env;
}
$env += $this->getDefaultEnv();
$options = array('suppress_errors' => true);
if ('\\' === DIRECTORY_SEPARATOR) {
$options['bypass_shell'] = true;
$commandline = $this->prepareWindowsCommandLine($commandline, $env);
} elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
$descriptors[3] = array('pipe', 'w');
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
$commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
$commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
// Workaround for the bug, when PTS functionality is enabled.
// @see : https://bugs.php.net/69442
$ptsWorkaround = fopen(__FILE__, 'r');
}
$envPairs = array();
foreach ($env as $k => $v) {
if (false !== $v) {
$envPairs[] = $k.'='.$v;
}
}
if (!is_dir($this->cwd)) {
throw new RuntimeException('The provided cwd does not exist.');
}
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
if (!is_resource($this->process)) {
throw new RuntimeException('Unable to launch a new process.');
}
$this->status = self::STATUS_STARTED;
if (isset($descriptors[3])) {
$this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
}
if ($this->tty) {
return;
}
$this->updateStatus(false);
$this->checkTimeout();
}
/**
* Restarts the process.
*
* Be warned that the process is cloned before being started.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param array $env An array of additional env vars to set when running the process
*
* @return $this
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
*
* @see start()
*
* @final
*/
public function restart(callable $callback = null, array $env = array())
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running');
}
$process = clone $this;
$process->start($callback, $env);
return $process;
}
/**
* Waits for the process to terminate.
*
* The callback receives the type of output (out or err) and some bytes
* from the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable|null $callback A valid PHP callback
*
* @return int The exitcode of the process
*
* @throws RuntimeException When process timed out
* @throws RuntimeException When process stopped after receiving signal
* @throws LogicException When process is not yet started
*/
public function wait(callable $callback = null)
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (null !== $callback) {
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait');
}
$this->callback = $this->buildCallback($callback);
}
do {
$this->checkTimeout();
$running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running);
} while ($running);
while ($this->isRunning()) {
usleep(1000);
}
if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
throw new ProcessSignaledException($this);
}
return $this->exitcode;
}
/**
* Returns the Pid (process identifier), if applicable.
*
* @return int|null The process id if running, null otherwise
*/
public function getPid()
{
return $this->isRunning() ? $this->processInformation['pid'] : null;
}
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
*
* @return $this
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
public function signal($signal)
{
$this->doSignal($signal, true);
return $this;
}
/**
* Disables fetching output and error output from the underlying process.
*
* @return $this
*
* @throws RuntimeException In case the process is already running
* @throws LogicException if an idle timeout is set
*/
public function disableOutput()
{
if ($this->isRunning()) {
throw new RuntimeException('Disabling output while the process is running is not possible.');
}
if (null !== $this->idleTimeout) {
throw new LogicException('Output can not be disabled while an idle timeout is set.');
}
$this->outputDisabled = true;
return $this;
}
/**
* Enables fetching output and error output from the underlying process.
*
* @return $this
*
* @throws RuntimeException In case the process is already running
*/
public function enableOutput()
{
if ($this->isRunning()) {
throw new RuntimeException('Enabling output while the process is running is not possible.');
}
$this->outputDisabled = false;
return $this;
}
/**
* Returns true in case the output is disabled, false otherwise.
*
* @return bool
*/
public function isOutputDisabled()
{
return $this->outputDisabled;
}
/**
* Returns the current output of the process (STDOUT).
*
* @return string The process output
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getOutput()
{
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
return '';
}
return $ret;
}
/**
* Returns the output incrementally.
*
* In comparison with the getOutput method which always return the whole
* output, this one returns the new output since the last call.
*
* @return string The process output since the last call
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getIncrementalOutput()
{
$this->readPipesForOutput(__FUNCTION__);
$latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
$this->incrementalOutputOffset = ftell($this->stdout);
if (false === $latest) {
return '';
}
return $latest;
}
/**
* Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
*
* @param int $flags A bit field of Process::ITER_* flags
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*
* @return \Generator
*/
public function getIterator($flags = 0)
{
$this->readPipesForOutput(__FUNCTION__, false);
$clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
$blocking = !(self::ITER_NON_BLOCKING & $flags);
$yieldOut = !(self::ITER_SKIP_OUT & $flags);
$yieldErr = !(self::ITER_SKIP_ERR & $flags);
while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
if ($yieldOut) {
$out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
if (isset($out[0])) {
if ($clearOutput) {
$this->clearOutput();
} else {
$this->incrementalOutputOffset = ftell($this->stdout);
}
yield self::OUT => $out;
}
}
if ($yieldErr) {
$err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
if (isset($err[0])) {
if ($clearOutput) {
$this->clearErrorOutput();
} else {
$this->incrementalErrorOutputOffset = ftell($this->stderr);
}
yield self::ERR => $err;
}
}
if (!$blocking && !isset($out[0]) && !isset($err[0])) {
yield self::OUT => '';
}
$this->checkTimeout();
$this->readPipesForOutput(__FUNCTION__, $blocking);
}
}
/**
* Clears the process output.
*
* @return $this
*/
public function clearOutput()
{
ftruncate($this->stdout, 0);
fseek($this->stdout, 0);
$this->incrementalOutputOffset = 0;
return $this;
}
/**
* Returns the current error output of the process (STDERR).
*
* @return string The process error output
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getErrorOutput()
{
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
return '';
}
return $ret;
}
/**
* Returns the errorOutput incrementally.
*
* In comparison with the getErrorOutput method which always return the
* whole error output, this one returns the new error output since the last
* call.
*
* @return string The process error output since the last call
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getIncrementalErrorOutput()
{
$this->readPipesForOutput(__FUNCTION__);
$latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
$this->incrementalErrorOutputOffset = ftell($this->stderr);
if (false === $latest) {
return '';
}
return $latest;
}
/**
* Clears the process output.
*
* @return $this
*/
public function clearErrorOutput()
{
ftruncate($this->stderr, 0);
fseek($this->stderr, 0);
$this->incrementalErrorOutputOffset = 0;
return $this;
}
/**
* Returns the exit code returned by the process.
*
* @return null|int The exit status code, null if the Process is not terminated
*/
public function getExitCode()
{
$this->updateStatus(false);
return $this->exitcode;
}
/**
* Returns a string representation for the exit code returned by the process.
*
* This method relies on the Unix exit code status standardization
* and might not be relevant for other operating systems.
*
* @return null|string A string representation for the exit status code, null if the Process is not terminated
*
* @see http://tldp.org/LDP/abs/html/exitcodes.html
* @see http://en.wikipedia.org/wiki/Unix_signal
*/
public function getExitCodeText()
{
if (null === $exitcode = $this->getExitCode()) {
return;
}
return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
}
/**
* Checks if the process ended successfully.
*
* @return bool true if the process ended successfully, false otherwise
*/
public function isSuccessful()
{
return 0 === $this->getExitCode();
}
/**
* Returns true if the child process has been terminated by an uncaught signal.
*
* It always returns false on Windows.
*
* @return bool
*
* @throws LogicException In case the process is not terminated
*/
public function hasBeenSignaled()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['signaled'];
}
/**
* Returns the number of the signal that caused the child process to terminate its execution.
*
* It is only meaningful if hasBeenSignaled() returns true.
*
* @return int
*
* @throws RuntimeException In case --enable-sigchild is activated
* @throws LogicException In case the process is not terminated
*/
public function getTermSignal()
{
$this->requireProcessIsTerminated(__FUNCTION__);
if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
}
return $this->processInformation['termsig'];
}
/**
* Returns true if the child process has been stopped by a signal.
*
* It always returns false on Windows.
*
* @return bool
*
* @throws LogicException In case the process is not terminated
*/
public function hasBeenStopped()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['stopped'];
}
/**
* Returns the number of the signal that caused the child process to stop its execution.
*
* It is only meaningful if hasBeenStopped() returns true.
*
* @return int
*
* @throws LogicException In case the process is not terminated
*/
public function getStopSignal()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['stopsig'];
}
/**
* Checks if the process is currently running.
*
* @return bool true if the process is currently running, false otherwise
*/
public function isRunning()
{
if (self::STATUS_STARTED !== $this->status) {
return false;
}
$this->updateStatus(false);
return $this->processInformation['running'];
}
/**
* Checks if the process has been started with no regard to the current state.
*
* @return bool true if status is ready, false otherwise
*/
public function isStarted()
{
return self::STATUS_READY != $this->status;
}
/**
* Checks if the process is terminated.
*
* @return bool true if process is terminated, false otherwise
*/
public function isTerminated()
{
$this->updateStatus(false);
return self::STATUS_TERMINATED == $this->status;
}
/**
* Gets the process status.
*
* The status is one of: ready, started, terminated.
*
* @return string The current process status
*/
public function getStatus()
{
$this->updateStatus(false);
return $this->status;
}
/**
* Stops the process.
*
* @param int|float $timeout The timeout in seconds
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
*
* @return int The exit-code of the process
*/
public function stop($timeout = 10, $signal = null)
{
$timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) {
// given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
$this->doSignal(15, false);
do {
usleep(1000);
} while ($this->isRunning() && microtime(true) < $timeoutMicro);
if ($this->isRunning()) {
// Avoid exception here: process is supposed to be running, but it might have stopped just
// after this line. In any case, let's silently discard the error, we cannot do anything.
$this->doSignal($signal ?: 9, false);
}
}
if ($this->isRunning()) {
if (isset($this->fallbackStatus['pid'])) {
unset($this->fallbackStatus['pid']);
return $this->stop(0, $signal);
}
$this->close();
}
return $this->exitcode;
}
/**
* Adds a line to the STDOUT stream.
*
* @internal
*/
public function addOutput(string $line)
{
$this->lastOutputTime = microtime(true);
fseek($this->stdout, 0, SEEK_END);
fwrite($this->stdout, $line);
fseek($this->stdout, $this->incrementalOutputOffset);
}
/**
* Adds a line to the STDERR stream.
*
* @internal
*/
public function addErrorOutput(string $line)
{
$this->lastOutputTime = microtime(true);
fseek($this->stderr, 0, SEEK_END);
fwrite($this->stderr, $line);
fseek($this->stderr, $this->incrementalErrorOutputOffset);
}
/**
* Gets the command line to be executed.
*
* @return string The command to execute
*/
public function getCommandLine()
{
return is_array($this->commandline) ? implode(' ', array_map(array($this, 'escapeArgument'), $this->commandline)) : $this->commandline;
}
/**
* Sets the command line to be executed.
*
* @param string|array $commandline The command to execute
*
* @return self The current Process instance
*/
public function setCommandLine($commandline)
{
$this->commandline = $commandline;
return $this;
}
/**
* Gets the process timeout (max. runtime).
*
* @return float|null The timeout in seconds or null if it's disabled
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* Gets the process idle timeout (max. time since last output).
*
* @return float|null The timeout in seconds or null if it's disabled
*/
public function getIdleTimeout()
{
return $this->idleTimeout;
}
/**
* Sets the process timeout (max. runtime).
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
*
* @throws InvalidArgumentException if the timeout is negative
*/
public function setTimeout($timeout)
{
$this->timeout = $this->validateTimeout($timeout);
return $this;
}
/**
* Sets the process idle timeout (max. time since last output).
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
*
* @throws LogicException if the output is disabled
* @throws InvalidArgumentException if the timeout is negative
*/
public function setIdleTimeout($timeout)
{
if (null !== $timeout && $this->outputDisabled) {
throw new LogicException('Idle timeout can not be set while the output is disabled.');
}
$this->idleTimeout = $this->validateTimeout($timeout);
return $this;
}
/**
* Enables or disables the TTY mode.
*
* @param bool $tty True to enabled and false to disable
*
* @return self The current Process instance
*
* @throws RuntimeException In case the TTY mode is not supported
*/
public function setTty($tty)
{
if ('\\' === DIRECTORY_SEPARATOR && $tty) {
throw new RuntimeException('TTY mode is not supported on Windows platform.');
}
if ($tty && !self::isTtySupported()) {
throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
}
$this->tty = (bool) $tty;
return $this;
}
/**
* Checks if the TTY mode is enabled.
*
* @return bool true if the TTY mode is enabled, false otherwise
*/
public function isTty()
{
return $this->tty;
}
/**
* Sets PTY mode.
*
* @param bool $bool
*
* @return self
*/
public function setPty($bool)
{
$this->pty = (bool) $bool;
return $this;
}
/**
* Returns PTY state.
*
* @return bool
*/
public function isPty()
{
return $this->pty;
}
/**
* Gets the working directory.
*
* @return string|null The current working directory or null on failure
*/
public function getWorkingDirectory()
{
if (null === $this->cwd) {
// getcwd() will return false if any one of the parent directories does not have
// the readable or search mode set, even if the current directory does
return getcwd() ?: null;
}
return $this->cwd;
}
/**
* Sets the current working directory.
*
* @param string $cwd The new working directory
*
* @return self The current Process instance
*/
public function setWorkingDirectory($cwd)
{
$this->cwd = $cwd;
return $this;
}
/**
* Gets the environment variables.
*
* @return array The current environment variables
*/
public function getEnv()
{
return $this->env;
}
/**
* Sets the environment variables.
*
* Each environment variable value should be a string.
* If it is an array, the variable is ignored.
* If it is false or null, it will be removed when
* env vars are otherwise inherited.
*
* That happens in PHP when 'argv' is registered into
* the $_ENV array for instance.
*
* @param array $env The new environment variables
*
* @return self The current Process instance
*/
public function setEnv(array $env)
{
// Process can not handle env values that are arrays
$env = array_filter($env, function ($value) {
return !is_array($value);
});
$this->env = $env;
return $this;
}
/**
* Gets the Process input.
*
* @return resource|string|\Iterator|null The Process input
*/
public function getInput()
{
return $this->input;
}
/**
* Sets the input.
*
* This content will be passed to the underlying process standard input.
*
* @param string|int|float|bool|resource|\Traversable|null $input The content
*
* @return self The current Process instance
*
* @throws LogicException In case the process is running
*/
public function setInput($input)
{
if ($this->isRunning()) {
throw new LogicException('Input can not be set while the process is running.');
}
$this->input = ProcessUtils::validateInput(__METHOD__, $input);
return $this;
}
/**
* Sets whether environment variables will be inherited or not.
*
* @param bool $inheritEnv
*
* @return self The current Process instance
*/
public function inheritEnvironmentVariables($inheritEnv = true)
{
if (!$inheritEnv) {
throw new InvalidArgumentException('Not inheriting environment variables is not supported.');
}
return $this;
}
/**
* Performs a check between the timeout definition and the time the process started.
*
* In case you run a background process (with the start method), you should
* trigger this method regularly to ensure the process timeout
*
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function checkTimeout()
{
if (self::STATUS_STARTED !== $this->status) {
return;
}
if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
$this->stop(0);
throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
}
if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
$this->stop(0);
throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
}
}
/**
* Returns whether TTY is supported on the current operating system.
*/
public static function isTtySupported(): bool
{
static $isTtySupported;
if (null === $isTtySupported) {
$isTtySupported = (bool) @proc_open('echo 1 >/dev/null', array(array('file', '/dev/tty', 'r'), array('file', '/dev/tty', 'w'), array('file', '/dev/tty', 'w')), $pipes);
}
return $isTtySupported;
}
/**
* Returns whether PTY is supported on the current operating system.
*
* @return bool
*/
public static function isPtySupported()
{
static $result;
if (null !== $result) {
return $result;
}
if ('\\' === DIRECTORY_SEPARATOR) {
return $result = false;
}
return $result = (bool) @proc_open('echo 1 >/dev/null', array(array('pty'), array('pty'), array('pty')), $pipes);
}
/**
* Creates the descriptors needed by the proc_open.
*/
private function getDescriptors(): array
{
if ($this->input instanceof \Iterator) {
$this->input->rewind();
}
if ('\\' === DIRECTORY_SEPARATOR) {
$this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
} else {
$this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
}
return $this->processPipes->getDescriptors();
}
/**
* Builds up the callback used by wait().
*
* The callbacks adds all occurred output to the specific buffer and calls
* the user callback (if present) with the received output.
*
* @param callable|null $callback The user defined PHP callback
*
* @return \Closure A PHP closure
*/
protected function buildCallback(callable $callback = null)
{
if ($this->outputDisabled) {
return function ($type, $data) use ($callback) {
if (null !== $callback) {
call_user_func($callback, $type, $data);
}
};
}
$out = self::OUT;
return function ($type, $data) use ($callback, $out) {
if ($out == $type) {
$this->addOutput($data);
} else {
$this->addErrorOutput($data);
}
if (null !== $callback) {
call_user_func($callback, $type, $data);
}
};
}
/**
* Updates the status of the process, reads pipes.
*
* @param bool $blocking Whether to use a blocking read call
*/
protected function updateStatus($blocking)
{
if (self::STATUS_STARTED !== $this->status) {
return;
}
$this->processInformation = proc_get_status($this->process);
$running = $this->processInformation['running'];
$this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running);
if ($this->fallbackStatus && $this->isSigchildEnabled()) {
$this->processInformation = $this->fallbackStatus + $this->processInformation;
}
if (!$running) {
$this->close();
}
}
/**
* Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
*
* @return bool
*/
protected function isSigchildEnabled()
{
if (null !== self::$sigchild) {
return self::$sigchild;
}
if (!function_exists('phpinfo')) {
return self::$sigchild = false;
}
ob_start();
phpinfo(INFO_GENERAL);
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
}
/**
* Reads pipes for the freshest output.
*
* @param string $caller The name of the method that needs fresh outputs
* @param bool $blocking Whether to use blocking calls or not
*
* @throws LogicException in case output has been disabled or process is not started
*/
private function readPipesForOutput(string $caller, bool $blocking = false)
{
if ($this->outputDisabled) {
throw new LogicException('Output has been disabled.');
}
$this->requireProcessIsStarted($caller);
$this->updateStatus($blocking);
}
/**
* Validates and returns the filtered timeout.
*
* @throws InvalidArgumentException if the given timeout is a negative number
*/
private function validateTimeout(?float $timeout): ?float
{
$timeout = (float) $timeout;
if (0.0 === $timeout) {
$timeout = null;
} elseif ($timeout < 0) {
throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
}
return $timeout;
}
/**
* Reads pipes, executes callback.
*
* @param bool $blocking Whether to use blocking calls or not
* @param bool $close Whether to close file handles or not
*/
private function readPipes(bool $blocking, bool $close)
{
$result = $this->processPipes->readAndWrite($blocking, $close);
$callback = $this->callback;
foreach ($result as $type => $data) {
if (3 !== $type) {
$callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
}
/**
* Closes process resource, closes file handles, sets the exitcode.
*
* @return int The exitcode
*/
private function close(): int
{
$this->processPipes->close();
if (is_resource($this->process)) {
proc_close($this->process);
}
$this->exitcode = $this->processInformation['exitcode'];
$this->status = self::STATUS_TERMINATED;
if (-1 === $this->exitcode) {
if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
$this->exitcode = 128 + $this->processInformation['termsig'];
} elseif ($this->isSigchildEnabled()) {
$this->processInformation['signaled'] = true;
$this->processInformation['termsig'] = -1;
}
}
// Free memory from self-reference callback created by buildCallback
// Doing so in other contexts like __destruct or by garbage collector is ineffective
// Now pipes are closed, so the callback is no longer necessary
$this->callback = null;
return $this->exitcode;
}
/**
* Resets data related to the latest run of the process.
*/
private function resetProcessData()
{
$this->starttime = null;
$this->callback = null;
$this->exitcode = null;
$this->fallbackStatus = array();
$this->processInformation = null;
$this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
$this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
$this->process = null;
$this->latestSignal = null;
$this->status = self::STATUS_READY;
$this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0;
}
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @param bool $throwException Whether to throw exception in case signal failed
*
* @return bool True if the signal was sent successfully, false otherwise
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
private function doSignal(int $signal, bool $throwException): bool
{
if (null === $pid = $this->getPid()) {
if ($throwException) {
throw new LogicException('Can not send signal on a non running process.');
}
return false;
}
if ('\\' === DIRECTORY_SEPARATOR) {
exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
if ($exitCode && $this->isRunning()) {
if ($throwException) {
throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
}
return false;
}
} else {
if (!$this->isSigchildEnabled()) {
$ok = @proc_terminate($this->process, $signal);
} elseif (function_exists('posix_kill')) {
$ok = @posix_kill($pid, $signal);
} elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) {
$ok = false === fgets($pipes[2]);
}
if (!$ok) {
if ($throwException) {
throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
}
return false;
}
}
$this->latestSignal = $signal;
$this->fallbackStatus['signaled'] = true;
$this->fallbackStatus['exitcode'] = -1;
$this->fallbackStatus['termsig'] = $this->latestSignal;
return true;
}
private function prepareWindowsCommandLine(string $cmd, array &$env)
{
$uid = uniqid('', true);
$varCount = 0;
$varCache = array();
$cmd = preg_replace_callback(
'/"(?:(
[^"%!^]*+
(?:
(?: !LF! | "(?:\^[%!^])?+" )
[^"%!^]*+
)++
) | [^"]*+ )"/x',
function ($m) use (&$env, &$varCache, &$varCount, $uid) {
if (!isset($m[1])) {
return $m[0];
}
if (isset($varCache[$m[0]])) {
return $varCache[$m[0]];
}
if (false !== strpos($value = $m[1], "\0")) {
$value = str_replace("\0", '?', $value);
}
if (false === strpbrk($value, "\"%!\n")) {
return '"'.$value.'"';
}
$value = str_replace(array('!LF!', '"^!"', '"^%"', '"^^"', '""'), array("\n", '!', '%', '^', '"'), $value);
$value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
$var = $uid.++$varCount;
$env[$var] = $value;
return $varCache[$m[0]] = '!'.$var.'!';
},
$cmd
);
$cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
foreach ($this->processPipes->getFiles() as $offset => $filename) {
$cmd .= ' '.$offset.'>"'.$filename.'"';
}
return $cmd;
}
/**
* Ensures the process is running or terminated, throws a LogicException if the process has a not started.
*
* @throws LogicException if the process has not run
*/
private function requireProcessIsStarted(string $functionName)
{
if (!$this->isStarted()) {
throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
}
}
/**
* Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
*
* @throws LogicException if the process is not yet terminated
*/
private function requireProcessIsTerminated(string $functionName)
{
if (!$this->isTerminated()) {
throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
}
}
/**
* Escapes a string to be used as a shell argument.
*/
private function escapeArgument(string $argument): string
{
if ('\\' !== DIRECTORY_SEPARATOR) {
return "'".str_replace("'", "'\\''", $argument)."'";
}
if ('' === $argument = (string) $argument) {
return '""';
}
if (false !== strpos($argument, "\0")) {
$argument = str_replace("\0", '?', $argument);
}
if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
return $argument;
}
$argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
return '"'.str_replace(array('"', '^', '%', '!', "\n"), array('""', '"^^"', '"^%"', '"^!"', '!LF!'), $argument).'"';
}
private function getDefaultEnv()
{
$env = array();
foreach ($_SERVER as $k => $v) {
if (is_string($v) && false !== $v = getenv($k)) {
$env[$k] = $v;
}
}
foreach ($_ENV as $k => $v) {
if (is_string($v)) {
$env[$k] = $v;
}
}
return $env;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* ProcessUtils is a bunch of utility methods.
*
* This class contains static methods only and is not meant to be instantiated.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class ProcessUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Validates and normalizes a Process input.
*
* @param string $caller The name of method call that validates the input
* @param mixed $input The input to validate
*
* @return mixed The validated input
*
* @throws InvalidArgumentException In case the input is not valid
*/
public static function validateInput($caller, $input)
{
if (null !== $input) {
if (is_resource($input)) {
return $input;
}
if (is_string($input)) {
return $input;
}
if (is_scalar($input)) {
return (string) $input;
}
if ($input instanceof Process) {
return $input->getIterator($input::ITER_SKIP_ERR);
}
if ($input instanceof \Iterator) {
return $input;
}
if ($input instanceof \Traversable) {
return new \IteratorIterator($input);
}
throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
}
return $input;
}
}
Process Component
=================
The Process component executes commands in sub-processes.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/process.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\ExecutableFinder;
/**
* @author Chris Smith <chris@cs278.org>
*/
class ExecutableFinderTest extends TestCase
{
private $path;
protected function tearDown()
{
if ($this->path) {
// Restore path if it was changed.
putenv('PATH='.$this->path);
}
}
private function setPath($path)
{
$this->path = getenv('PATH');
putenv('PATH='.$path);
}
public function testFind()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$this->setPath(dirname(PHP_BINARY));
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName());
$this->assertSamePath(PHP_BINARY, $result);
}
public function testFindWithDefault()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$expected = 'defaultValue';
$this->setPath('');
$finder = new ExecutableFinder();
$result = $finder->find('foo', $expected);
$this->assertEquals($expected, $result);
}
public function testFindWithExtraDirs()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$this->setPath('');
$extraDirs = array(dirname(PHP_BINARY));
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);
$this->assertSamePath(PHP_BINARY, $result);
}
public function testFindWithOpenBaseDir()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Cannot run test on windows');
}
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$this->iniSet('open_basedir', dirname(PHP_BINARY).PATH_SEPARATOR.'/');
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName());
$this->assertSamePath(PHP_BINARY, $result);
}
public function testFindProcessInOpenBasedir()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Cannot run test on windows');
}
$this->setPath('');
$this->iniSet('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/');
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName(), false);
$this->assertSamePath(PHP_BINARY, $result);
}
/**
* @requires PHP 5.4
*/
public function testFindBatchExecutableOnWindows()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
if ('\\' !== DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Can be only tested on windows');
}
$target = tempnam(sys_get_temp_dir(), 'example-windows-executable');
touch($target);
touch($target.'.BAT');
$this->assertFalse(is_executable($target));
$this->setPath(sys_get_temp_dir());
$finder = new ExecutableFinder();
$result = $finder->find(basename($target), false);
unlink($target);
unlink($target.'.BAT');
$this->assertSamePath($target.'.BAT', $result);
}
private function assertSamePath($expected, $tested)
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->assertEquals(strtolower($expected), strtolower($tested));
} else {
$this->assertEquals($expected, $tested);
}
}
private function getPhpBinaryName()
{
return basename(PHP_BINARY, '\\' === DIRECTORY_SEPARATOR ? '.exe' : '');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds.
*
* @args duration Run this script with a custom duration
*
* @example `php NonStopableProcess.php 42` will run the script for 42 seconds
*/
function handleSignal($signal)
{
switch ($signal) {
case SIGTERM:
$name = 'SIGTERM';
break;
case SIGINT:
$name = 'SIGINT';
break;
default:
$name = $signal.' (unknown)';
break;
}
echo "signal $name\n";
}
pcntl_signal(SIGTERM, 'handleSignal');
pcntl_signal(SIGINT, 'handleSignal');
echo 'received ';
$duration = isset($argv[1]) ? (int) $argv[1] : 3;
$start = microtime(true);
while ($duration > (microtime(true) - $start)) {
usleep(10000);
pcntl_signal_dispatch();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\PhpExecutableFinder;
/**
* @author Robert Schönthal <seroscho@googlemail.com>
*/
class PhpExecutableFinderTest extends TestCase
{
/**
* tests find() with the constant PHP_BINARY.
*/
public function testFind()
{
$f = new PhpExecutableFinder();
$current = PHP_BINARY;
$args = 'phpdbg' === PHP_SAPI ? ' -qrr' : '';
$this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP');
$this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
}
/**
* tests find() with the env var PHP_PATH.
*/
public function testFindArguments()
{
$f = new PhpExecutableFinder();
if ('phpdbg' === PHP_SAPI) {
$this->assertEquals($f->findArguments(), array('-qrr'), '::findArguments() returns phpdbg arguments');
} else {
$this->assertEquals($f->findArguments(), array(), '::findArguments() returns no arguments');
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\PhpProcess;
class PhpProcessTest extends TestCase
{
public function testNonBlockingWorks()
{
$expected = 'hello world!';
$process = new PhpProcess(<<<PHP
<?php echo '$expected';
PHP
);
$process->start();
$process->wait();
$this->assertEquals($expected, $process->getOutput());
}
public function testCommandLine()
{
$process = new PhpProcess(<<<'PHP'
<?php echo phpversion().PHP_SAPI;
PHP
);
$commandLine = $process->getCommandLine();
$process->start();
$this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
$process->wait();
$this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
$this->assertSame(PHP_VERSION.PHP_SAPI, $process->getOutput());
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
define('ERR_SELECT_FAILED', 1);
define('ERR_TIMEOUT', 2);
define('ERR_READ_FAILED', 3);
define('ERR_WRITE_FAILED', 4);
$read = array(STDIN);
$write = array(STDOUT, STDERR);
stream_set_blocking(STDIN, 0);
stream_set_blocking(STDOUT, 0);
stream_set_blocking(STDERR, 0);
$out = $err = '';
while ($read || $write) {
$r = $read;
$w = $write;
$e = null;
$n = stream_select($r, $w, $e, 5);
if (false === $n) {
die(ERR_SELECT_FAILED);
} elseif ($n < 1) {
die(ERR_TIMEOUT);
}
if (in_array(STDOUT, $w) && strlen($out) > 0) {
$written = fwrite(STDOUT, (binary) $out, 32768);
if (false === $written) {
die(ERR_WRITE_FAILED);
}
$out = (binary) substr($out, $written);
}
if (null === $read && '' === $out) {
$write = array_diff($write, array(STDOUT));
}
if (in_array(STDERR, $w) && strlen($err) > 0) {
$written = fwrite(STDERR, (binary) $err, 32768);
if (false === $written) {
die(ERR_WRITE_FAILED);
}
$err = (binary) substr($err, $written);
}
if (null === $read && '' === $err) {
$write = array_diff($write, array(STDERR));
}
if ($r) {
$str = fread(STDIN, 32768);
if (false !== $str) {
$out .= $str;
$err .= $str;
}
if (false === $str || feof(STDIN)) {
$read = null;
if (!feof(STDIN)) {
die(ERR_READ_FAILED);
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Exception\ProcessFailedException;
/**
* @author Sebastian Marek <proofek@gmail.com>
*/
class ProcessFailedExceptionTest extends TestCase
{
/**
* tests ProcessFailedException throws exception if the process was successful.
*/
public function testProcessFailedExceptionThrowsException()
{
$process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(array('isSuccessful'))->setConstructorArgs(array('php'))->getMock();
$process->expects($this->once())
->method('isSuccessful')
->will($this->returnValue(true));
if (method_exists($this, 'expectException')) {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Expected a failed process, but the given process was successful.');
} else {
$this->setExpectedException(\InvalidArgumentException::class, 'Expected a failed process, but the given process was successful.');
}
new ProcessFailedException($process);
}
/**
* tests ProcessFailedException uses information from process output
* to generate exception message.
*/
public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()
{
$cmd = 'php';
$exitCode = 1;
$exitText = 'General error';
$output = 'Command output';
$errorOutput = 'FATAL: Unexpected error';
$workingDirectory = getcwd();
$process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(array('isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'))->setConstructorArgs(array($cmd))->getMock();
$process->expects($this->once())
->method('isSuccessful')
->will($this->returnValue(false));
$process->expects($this->once())
->method('getOutput')
->will($this->returnValue($output));
$process->expects($this->once())
->method('getErrorOutput')
->will($this->returnValue($errorOutput));
$process->expects($this->once())
->method('getExitCode')
->will($this->returnValue($exitCode));
$process->expects($this->once())
->method('getExitCodeText')
->will($this->returnValue($exitText));
$process->expects($this->once())
->method('isOutputDisabled')
->will($this->returnValue(false));
$process->expects($this->once())
->method('getWorkingDirectory')
->will($this->returnValue($workingDirectory));
$exception = new ProcessFailedException($process);
$this->assertEquals(
"The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}",
$exception->getMessage()
);
}
/**
* Tests that ProcessFailedException does not extract information from
* process output if it was previously disabled.
*/
public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput()
{
$cmd = 'php';
$exitCode = 1;
$exitText = 'General error';
$workingDirectory = getcwd();
$process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(array('isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'))->setConstructorArgs(array($cmd))->getMock();
$process->expects($this->once())
->method('isSuccessful')
->will($this->returnValue(false));
$process->expects($this->never())
->method('getOutput');
$process->expects($this->never())
->method('getErrorOutput');
$process->expects($this->once())
->method('getExitCode')
->will($this->returnValue($exitCode));
$process->expects($this->once())
->method('getExitCodeText')
->will($this->returnValue($exitText));
$process->expects($this->once())
->method('isOutputDisabled')
->will($this->returnValue(true));
$process->expects($this->once())
->method('getWorkingDirectory')
->will($this->returnValue($workingDirectory));
$exception = new ProcessFailedException($process);
$this->assertEquals(
"The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}",
$exception->getMessage()
);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\InputStream;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Process;
/**
* @author Robert Schönthal <seroscho@googlemail.com>
*/
class ProcessTest extends TestCase
{
private static $phpBin;
private static $process;
private static $sigchild;
public static function setUpBeforeClass()
{
$phpBin = new PhpExecutableFinder();
self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === PHP_SAPI ? 'php' : $phpBin->find());
ob_start();
phpinfo(INFO_GENERAL);
self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
}
protected function tearDown()
{
if (self::$process) {
self::$process->stop(0);
self::$process = null;
}
}
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
* @expectedExceptionMessage The provided cwd does not exist.
*/
public function testInvalidCwd()
{
try {
// Check that it works fine if the CWD exists
$cmd = new Process('echo test', __DIR__);
$cmd->run();
} catch (\Exception $e) {
$this->fail($e);
}
$cmd = new Process('echo test', __DIR__.'/notfound/');
$cmd->run();
}
public function testThatProcessDoesNotThrowWarningDuringRun()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('This test is transient on Windows');
}
@trigger_error('Test Error', E_USER_NOTICE);
$process = $this->getProcessForCode('sleep(3)');
$process->run();
$actualError = error_get_last();
$this->assertEquals('Test Error', $actualError['message']);
$this->assertEquals(E_USER_NOTICE, $actualError['type']);
}
/**
* @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
*/
public function testNegativeTimeoutFromConstructor()
{
$this->getProcess('', null, null, null, -1);
}
/**
* @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
*/
public function testNegativeTimeoutFromSetter()
{
$p = $this->getProcess('');
$p->setTimeout(-1);
}
public function testFloatAndNullTimeout()
{
$p = $this->getProcess('');
$p->setTimeout(10);
$this->assertSame(10.0, $p->getTimeout());
$p->setTimeout(null);
$this->assertNull($p->getTimeout());
$p->setTimeout(0.0);
$this->assertNull($p->getTimeout());
}
/**
* @requires extension pcntl
*/
public function testStopWithTimeoutIsActuallyWorking()
{
$p = $this->getProcess(array(self::$phpBin, __DIR__.'/NonStopableProcess.php', 30));
$p->start();
while ($p->isRunning() && false === strpos($p->getOutput(), 'received')) {
usleep(1000);
}
if (!$p->isRunning()) {
throw new \LogicException('Process is not running: '.$p->getErrorOutput());
}
$start = microtime(true);
$p->stop(0.1);
$p->wait();
$this->assertLessThan(15, microtime(true) - $start);
}
public function testAllOutputIsActuallyReadOnTermination()
{
// this code will result in a maximum of 2 reads of 8192 bytes by calling
// start() and isRunning(). by the time getOutput() is called the process
// has terminated so the internal pipes array is already empty. normally
// the call to start() will not read any data as the process will not have
// generated output, but this is non-deterministic so we must count it as
// a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
// another byte which will never be read.
$expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
$code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
$p = $this->getProcessForCode($code);
$p->start();
// Don't call Process::run nor Process::wait to avoid any read of pipes
$h = new \ReflectionProperty($p, 'process');
$h->setAccessible(true);
$h = $h->getValue($p);
$s = @proc_get_status($h);
while (!empty($s['running'])) {
usleep(1000);
$s = proc_get_status($h);
}
$o = $p->getOutput();
$this->assertEquals($expectedOutputSize, strlen($o));
}
public function testCallbacksAreExecutedWithStart()
{
$process = $this->getProcess('echo foo');
$process->start(function ($type, $buffer) use (&$data) {
$data .= $buffer;
});
$process->wait();
$this->assertSame('foo'.PHP_EOL, $data);
}
/**
* tests results from sub processes.
*
* @dataProvider responsesCodeProvider
*/
public function testProcessResponses($expected, $getter, $code)
{
$p = $this->getProcessForCode($code);
$p->run();
$this->assertSame($expected, $p->$getter());
}
/**
* tests results from sub processes.
*
* @dataProvider pipesCodeProvider
*/
public function testProcessPipes($code, $size)
{
$expected = str_repeat(str_repeat('*', 1024), $size).'!';
$expectedLength = (1024 * $size) + 1;
$p = $this->getProcessForCode($code);
$p->setInput($expected);
$p->run();
$this->assertEquals($expectedLength, strlen($p->getOutput()));
$this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
}
/**
* @dataProvider pipesCodeProvider
*/
public function testSetStreamAsInput($code, $size)
{
$expected = str_repeat(str_repeat('*', 1024), $size).'!';
$expectedLength = (1024 * $size) + 1;
$stream = fopen('php://temporary', 'w+');
fwrite($stream, $expected);
rewind($stream);
$p = $this->getProcessForCode($code);
$p->setInput($stream);
$p->run();
fclose($stream);
$this->assertEquals($expectedLength, strlen($p->getOutput()));
$this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
}
public function testLiveStreamAsInput()
{
$stream = fopen('php://memory', 'r+');
fwrite($stream, 'hello');
rewind($stream);
$p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
$p->setInput($stream);
$p->start(function ($type, $data) use ($stream) {
if ('hello' === $data) {
fclose($stream);
}
});
$p->wait();
$this->assertSame('hello', $p->getOutput());
}
/**
* @expectedException \Symfony\Component\Process\Exception\LogicException
* @expectedExceptionMessage Input can not be set while the process is running.
*/
public function testSetInputWhileRunningThrowsAnException()
{
$process = $this->getProcessForCode('sleep(30);');
$process->start();
try {
$process->setInput('foobar');
$process->stop();
$this->fail('A LogicException should have been raised.');
} catch (LogicException $e) {
}
$process->stop();
throw $e;
}
/**
* @dataProvider provideInvalidInputValues
* @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
* @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources.
*/
public function testInvalidInput($value)
{
$process = $this->getProcess('foo');
$process->setInput($value);
}
public function provideInvalidInputValues()
{
return array(
array(array()),
array(new NonStringifiable()),
);
}
/**
* @dataProvider provideInputValues
*/
public function testValidInput($expected, $value)
{
$process = $this->getProcess('foo');
$process->setInput($value);
$this->assertSame($expected, $process->getInput());
}
public function provideInputValues()
{
return array(
array(null, null),
array('24.5', 24.5),
array('input data', 'input data'),
);
}
public function chainedCommandsOutputProvider()
{
if ('\\' === DIRECTORY_SEPARATOR) {
return array(
array("2 \r\n2\r\n", '&&', '2'),
);
}
return array(
array("1\n1\n", ';', '1'),
array("2\n2\n", '&&', '2'),
);
}
/**
* @dataProvider chainedCommandsOutputProvider
*/
public function testChainedCommandsOutput($expected, $operator, $input)
{
$process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
$process->run();
$this->assertEquals($expected, $process->getOutput());
}
public function testCallbackIsExecutedForOutput()
{
$p = $this->getProcessForCode('echo \'foo\';');
$called = false;
$p->run(function ($type, $buffer) use (&$called) {
$called = 'foo' === $buffer;
});
$this->assertTrue($called, 'The callback should be executed with the output');
}
public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
{
$p = $this->getProcessForCode('echo \'foo\';');
$p->disableOutput();
$called = false;
$p->run(function ($type, $buffer) use (&$called) {
$called = 'foo' === $buffer;
});
$this->assertTrue($called, 'The callback should be executed with the output');
}
public function testGetErrorOutput()
{
$p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
$p->run();
$this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
}
public function testFlushErrorOutput()
{
$p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
$p->run();
$p->clearErrorOutput();
$this->assertEmpty($p->getErrorOutput());
}
/**
* @dataProvider provideIncrementalOutput
*/
public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
{
$lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
$p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');');
$h = fopen($lock, 'w');
flock($h, LOCK_EX);
$p->start();
foreach (array('foo', 'bar') as $s) {
while (false === strpos($p->$getOutput(), $s)) {
usleep(1000);
}
$this->assertSame($s, $p->$getIncrementalOutput());
$this->assertSame('', $p->$getIncrementalOutput());
flock($h, LOCK_UN);
}
fclose($h);
}
public function provideIncrementalOutput()
{
return array(
array('getOutput', 'getIncrementalOutput', 'php://stdout'),
array('getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'),
);
}
public function testGetOutput()
{
$p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
$p->run();
$this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
}
public function testFlushOutput()
{
$p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
$p->run();
$p->clearOutput();
$this->assertEmpty($p->getOutput());
}
public function testZeroAsOutput()
{
if ('\\' === DIRECTORY_SEPARATOR) {
// see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
$p = $this->getProcess('echo | set /p dummyName=0');
} else {
$p = $this->getProcess('printf 0');
}
$p->run();
$this->assertSame('0', $p->getOutput());
}
public function testExitCodeCommandFailed()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Windows does not support POSIX exit code');
}
// such command run in bash return an exitcode 127
$process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
$process->run();
$this->assertGreaterThan(0, $process->getExitCode());
}
/**
* @group tty
*/
public function testTTYCommand()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Windows does not have /dev/tty support');
}
$process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
$process->setTty(true);
$process->start();
$this->assertTrue($process->isRunning());
$process->wait();
$this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
}
/**
* @group tty
*/
public function testTTYCommandExitCode()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Windows does have /dev/tty support');
}
$process = $this->getProcess('echo "foo" >> /dev/null');
$process->setTty(true);
$process->run();
$this->assertTrue($process->isSuccessful());
}
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
* @expectedExceptionMessage TTY mode is not supported on Windows platform.
*/
public function testTTYInWindowsEnvironment()
{
if ('\\' !== DIRECTORY_SEPARATOR) {
$this->markTestSkipped('This test is for Windows platform only');
}
$process = $this->getProcess('echo "foo" >> /dev/null');
$process->setTty(false);
$process->setTty(true);
}
public function testExitCodeTextIsNullWhenExitCodeIsNull()
{
$process = $this->getProcess('');
$this->assertNull($process->getExitCodeText());
}
public function testPTYCommand()
{
if (!Process::isPtySupported()) {
$this->markTestSkipped('PTY is not supported on this operating system.');
}
$process = $this->getProcess('echo "foo"');
$process->setPty(true);
$process->run();
$this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
$this->assertEquals("foo\r\n", $process->getOutput());
}
public function testMustRun()
{
$process = $this->getProcess('echo foo');
$this->assertSame($process, $process->mustRun());
$this->assertEquals('foo'.PHP_EOL, $process->getOutput());
}
public function testSuccessfulMustRunHasCorrectExitCode()
{
$process = $this->getProcess('echo foo')->mustRun();
$this->assertEquals(0, $process->getExitCode());
}
/**
* @expectedException \Symfony\Component\Process\Exception\ProcessFailedException
*/
public function testMustRunThrowsException()
{
$process = $this->getProcess('exit 1');
$process->mustRun();
}
public function testExitCodeText()
{
$process = $this->getProcess('');
$r = new \ReflectionObject($process);
$p = $r->getProperty('exitcode');
$p->setAccessible(true);
$p->setValue($process, 2);
$this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
}
public function testStartIsNonBlocking()
{
$process = $this->getProcessForCode('usleep(500000);');
$start = microtime(true);
$process->start();
$end = microtime(true);
$this->assertLessThan(0.4, $end - $start);
$process->stop();
}
public function testUpdateStatus()
{
$process = $this->getProcess('echo foo');
$process->run();
$this->assertGreaterThan(0, strlen($process->getOutput()));
}
public function testGetExitCodeIsNullOnStart()
{
$process = $this->getProcessForCode('usleep(100000);');
$this->assertNull($process->getExitCode());
$process->start();
$this->assertNull($process->getExitCode());
$process->wait();
$this->assertEquals(0, $process->getExitCode());
}
public function testGetExitCodeIsNullOnWhenStartingAgain()
{
$process = $this->getProcessForCode('usleep(100000);');
$process->run();
$this->assertEquals(0, $process->getExitCode());
$process->start();
$this->assertNull($process->getExitCode());
$process->wait();
$this->assertEquals(0, $process->getExitCode());
}
public function testGetExitCode()
{
$process = $this->getProcess('echo foo');
$process->run();
$this->assertSame(0, $process->getExitCode());
}
public function testStatus()
{
$process = $this->getProcessForCode('usleep(100000);');
$this->assertFalse($process->isRunning());
$this->assertFalse($process->isStarted());
$this->assertFalse($process->isTerminated());
$this->assertSame(Process::STATUS_READY, $process->getStatus());
$process->start();
$this->assertTrue($process->isRunning());
$this->assertTrue($process->isStarted());
$this->assertFalse($process->isTerminated());
$this->assertSame(Process::STATUS_STARTED, $process->getStatus());
$process->wait();
$this->assertFalse($process->isRunning());
$this->assertTrue($process->isStarted());
$this->assertTrue($process->isTerminated());
$this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
}
public function testStop()
{
$process = $this->getProcessForCode('sleep(31);');
$process->start();
$this->assertTrue($process->isRunning());
$process->stop();
$this->assertFalse($process->isRunning());
}
public function testIsSuccessful()
{
$process = $this->getProcess('echo foo');
$process->run();
$this->assertTrue($process->isSuccessful());
}
public function testIsSuccessfulOnlyAfterTerminated()
{
$process = $this->getProcessForCode('usleep(100000);');
$process->start();
$this->assertFalse($process->isSuccessful());
$process->wait();
$this->assertTrue($process->isSuccessful());
}
public function testIsNotSuccessful()
{
$process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
$process->run();
$this->assertFalse($process->isSuccessful());
}
public function testProcessIsNotSignaled()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Windows does not support POSIX signals');
}
$process = $this->getProcess('echo foo');
$process->run();
$this->assertFalse($process->hasBeenSignaled());
}
public function testProcessWithoutTermSignal()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Windows does not support POSIX signals');
}
$process = $this->getProcess('echo foo');
$process->run();
$this->assertEquals(0, $process->getTermSignal());
}
public function testProcessIsSignaledIfStopped()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Windows does not support POSIX signals');
}
$process = $this->getProcessForCode('sleep(32);');
$process->start();
$process->stop();
$this->assertTrue($process->hasBeenSignaled());
$this->assertEquals(15, $process->getTermSignal()); // SIGTERM
}
/**
* @expectedException \Symfony\Component\Process\Exception\ProcessSignaledException
* @expectedExceptionMessage The process has been signaled with signal "9".
*/
public function testProcessThrowsExceptionWhenExternallySignaled()
{
if (!function_exists('posix_kill')) {
$this->markTestSkipped('Function posix_kill is required.');
}
if (self::$sigchild) {
$this->markTestSkipped('PHP is compiled with --enable-sigchild.');
}
$process = $this->getProcessForCode('sleep(32.1);');
$process->start();
posix_kill($process->getPid(), 9); // SIGKILL
$process->wait();
}
public function testRestart()
{
$process1 = $this->getProcessForCode('echo getmypid();');
$process1->run();
$process2 = $process1->restart();
$process2->wait(); // wait for output
// Ensure that both processed finished and the output is numeric
$this->assertFalse($process1->isRunning());
$this->assertFalse($process2->isRunning());
$this->assertInternalType('numeric', $process1->getOutput());
$this->assertInternalType('numeric', $process2->getOutput());
// Ensure that restart returned a new process by check that the output is different
$this->assertNotEquals($process1->getOutput(), $process2->getOutput());
}
/**
* @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
* @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
*/
public function testRunProcessWithTimeout()
{
$process = $this->getProcessForCode('sleep(30);');
$process->setTimeout(0.1);
$start = microtime(true);
try {
$process->run();
$this->fail('A RuntimeException should have been raised');
} catch (RuntimeException $e) {
}
$this->assertLessThan(15, microtime(true) - $start);
throw $e;
}
/**
* @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
* @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
*/
public function testIterateOverProcessWithTimeout()
{
$process = $this->getProcessForCode('sleep(30);');
$process->setTimeout(0.1);
$start = microtime(true);
try {
$process->start();
foreach ($process as $buffer);
$this->fail('A RuntimeException should have been raised');
} catch (RuntimeException $e) {
}
$this->assertLessThan(15, microtime(true) - $start);
throw $e;
}
public function testCheckTimeoutOnNonStartedProcess()
{
$process = $this->getProcess('echo foo');
$this->assertNull($process->checkTimeout());
}
public function testCheckTimeoutOnTerminatedProcess()
{
$process = $this->getProcess('echo foo');
$process->run();
$this->assertNull($process->checkTimeout());
}
/**
* @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
* @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
*/
public function testCheckTimeoutOnStartedProcess()
{
$process = $this->getProcessForCode('sleep(33);');
$process->setTimeout(0.1);
$process->start();
$start = microtime(true);
try {
while ($process->isRunning()) {
$process->checkTimeout();
usleep(100000);
}
$this->fail('A ProcessTimedOutException should have been raised');
} catch (ProcessTimedOutException $e) {
}
$this->assertLessThan(15, microtime(true) - $start);
throw $e;
}
public function testIdleTimeout()
{
$process = $this->getProcessForCode('sleep(34);');
$process->setTimeout(60);
$process->setIdleTimeout(0.1);
try {
$process->run();
$this->fail('A timeout exception was expected.');
} catch (ProcessTimedOutException $e) {
$this->assertTrue($e->isIdleTimeout());
$this->assertFalse($e->isGeneralTimeout());
$this->assertEquals(0.1, $e->getExceededTimeout());
}
}
public function testIdleTimeoutNotExceededWhenOutputIsSent()
{
$process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
$process->setTimeout(1);
$process->start();
while (false === strpos($process->getOutput(), 'foo')) {
usleep(1000);
}
$process->setIdleTimeout(0.5);
try {
$process->wait();
$this->fail('A timeout exception was expected.');
} catch (ProcessTimedOutException $e) {
$this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
$this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
$this->assertEquals(1, $e->getExceededTimeout());
}
}
/**
* @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
* @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
*/
public function testStartAfterATimeout()
{
$process = $this->getProcessForCode('sleep(35);');
$process->setTimeout(0.1);
try {
$process->run();
$this->fail('A ProcessTimedOutException should have been raised.');
} catch (ProcessTimedOutException $e) {
}
$this->assertFalse($process->isRunning());
$process->start();
$this->assertTrue($process->isRunning());
$process->stop(0);
throw $e;
}
public function testGetPid()
{
$process = $this->getProcessForCode('sleep(36);');
$process->start();
$this->assertGreaterThan(0, $process->getPid());
$process->stop(0);
}
public function testGetPidIsNullBeforeStart()
{
$process = $this->getProcess('foo');
$this->assertNull($process->getPid());
}
public function testGetPidIsNullAfterRun()
{
$process = $this->getProcess('echo foo');
$process->run();
$this->assertNull($process->getPid());
}
/**
* @requires extension pcntl
*/
public function testSignal()
{
$process = $this->getProcess(array(self::$phpBin, __DIR__.'/SignalListener.php'));
$process->start();
while (false === strpos($process->getOutput(), 'Caught')) {
usleep(1000);
}
$process->signal(SIGUSR1);
$process->wait();
$this->assertEquals('Caught SIGUSR1', $process->getOutput());
}
/**
* @requires extension pcntl
*/
public function testExitCodeIsAvailableAfterSignal()
{
$process = $this->getProcess('sleep 4');
$process->start();
$process->signal(SIGKILL);
while ($process->isRunning()) {
usleep(10000);
}
$this->assertFalse($process->isRunning());
$this->assertTrue($process->hasBeenSignaled());
$this->assertFalse($process->isSuccessful());
$this->assertEquals(137, $process->getExitCode());
}
/**
* @expectedException \Symfony\Component\Process\Exception\LogicException
* @expectedExceptionMessage Can not send signal on a non running process.
*/
public function testSignalProcessNotRunning()
{
$process = $this->getProcess('foo');
$process->signal(1); // SIGHUP
}
/**
* @dataProvider provideMethodsThatNeedARunningProcess
*/
public function testMethodsThatNeedARunningProcess($method)
{
$process = $this->getProcess('foo');
if (method_exists($this, 'expectException')) {
$this->expectException('Symfony\Component\Process\Exception\LogicException');
$this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method));
} else {
$this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
}
$process->{$method}();
}
public function provideMethodsThatNeedARunningProcess()
{
return array(
array('getOutput'),
array('getIncrementalOutput'),
array('getErrorOutput'),
array('getIncrementalErrorOutput'),
array('wait'),
);
}
/**
* @dataProvider provideMethodsThatNeedATerminatedProcess
* @expectedException \Symfony\Component\Process\Exception\LogicException
* @expectedExceptionMessage Process must be terminated before calling
*/
public function testMethodsThatNeedATerminatedProcess($method)
{
$process = $this->getProcessForCode('sleep(37);');
$process->start();
try {
$process->{$method}();
$process->stop(0);
$this->fail('A LogicException must have been thrown');
} catch (\Exception $e) {
}
$process->stop(0);
throw $e;
}
public function provideMethodsThatNeedATerminatedProcess()
{
return array(
array('hasBeenSignaled'),
array('getTermSignal'),
array('hasBeenStopped'),
array('getStopSignal'),
);
}
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
*/
public function testWrongSignal()
{
if ('\\' === DIRECTORY_SEPARATOR) {
$this->markTestSkipped('POSIX signals do not work on Windows');
}
$process = $this->getProcessForCode('sleep(38);');
$process->start();
try {
$process->signal(-4);
$this->fail('A RuntimeException must have been thrown');
} catch (RuntimeException $e) {
$process->stop(0);
}
throw $e;
}
public function testDisableOutputDisablesTheOutput()
{
$p = $this->getProcess('foo');
$this->assertFalse($p->isOutputDisabled());
$p->disableOutput();
$this->assertTrue($p->isOutputDisabled());
$p->enableOutput();
$this->assertFalse($p->isOutputDisabled());
}
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
* @expectedExceptionMessage Disabling output while the process is running is not possible.
*/
public function testDisableOutputWhileRunningThrowsException()
{
$p = $this->getProcessForCode('sleep(39);');
$p->start();
$p->disableOutput();
}
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
* @expectedExceptionMessage Enabling output while the process is running is not possible.
*/
public function testEnableOutputWhileRunningThrowsException()
{
$p = $this->getProcessForCode('sleep(40);');
$p->disableOutput();
$p->start();
$p->enableOutput();
}
public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
{
$p = $this->getProcess('echo foo');
$p->disableOutput();
$p->run();
$p->enableOutput();
$p->disableOutput();
$this->assertTrue($p->isOutputDisabled());
}
/**
* @expectedException \Symfony\Component\Process\Exception\LogicException
* @expectedExceptionMessage Output can not be disabled while an idle timeout is set.
*/
public function testDisableOutputWhileIdleTimeoutIsSet()
{
$process = $this->getProcess('foo');
$process->setIdleTimeout(1);
$process->disableOutput();
}
/**
* @expectedException \Symfony\Component\Process\Exception\LogicException
* @expectedExceptionMessage timeout can not be set while the output is disabled.
*/
public function testSetIdleTimeoutWhileOutputIsDisabled()
{
$process = $this->getProcess('foo');
$process->disableOutput();
$process->setIdleTimeout(1);
}
public function testSetNullIdleTimeoutWhileOutputIsDisabled()
{
$process = $this->getProcess('foo');
$process->disableOutput();
$this->assertSame($process, $process->setIdleTimeout(null));
}
/**
* @dataProvider provideOutputFetchingMethods
* @expectedException \Symfony\Component\Process\Exception\LogicException
* @expectedExceptionMessage Output has been disabled.
*/
public function testGetOutputWhileDisabled($fetchMethod)
{
$p = $this->getProcessForCode('sleep(41);');
$p->disableOutput();
$p->start();
$p->{$fetchMethod}();
}
public function provideOutputFetchingMethods()
{
return array(
array('getOutput'),
array('getIncrementalOutput'),
array('getErrorOutput'),
array('getIncrementalErrorOutput'),
);
}
public function testStopTerminatesProcessCleanly()
{
$process = $this->getProcessForCode('echo 123; sleep(42);');
$process->run(function () use ($process) {
$process->stop();
});
$this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
}
public function testKillSignalTerminatesProcessCleanly()
{
$process = $this->getProcessForCode('echo 123; sleep(43);');
$process->run(function () use ($process) {
$process->signal(9); // SIGKILL
});
$this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
}
public function testTermSignalTerminatesProcessCleanly()
{
$process = $this->getProcessForCode('echo 123; sleep(44);');
$process->run(function () use ($process) {
$process->signal(15); // SIGTERM
});
$this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
}
public function responsesCodeProvider()
{
return array(
//expected output / getter / code to execute
//array(1,'getExitCode','exit(1);'),
//array(true,'isSuccessful','exit();'),
array('output', 'getOutput', 'echo \'output\';'),
);
}
public function pipesCodeProvider()
{
$variations = array(
'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
);
if ('\\' === DIRECTORY_SEPARATOR) {
// Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
$sizes = array(1, 2, 4, 8);
} else {
$sizes = array(1, 16, 64, 1024, 4096);
}
$codes = array();
foreach ($sizes as $size) {
foreach ($variations as $code) {
$codes[] = array($code, $size);
}
}
return $codes;
}
/**
* @dataProvider provideVariousIncrementals
*/
public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
{
$process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
$process->start();
$result = '';
$limit = microtime(true) + 3;
$expected = '012';
while ($result !== $expected && microtime(true) < $limit) {
$result .= $process->$method();
}
$this->assertSame($expected, $result);
$process->stop();
}
public function provideVariousIncrementals()
{
return array(
array('php://stdout', 'getIncrementalOutput'),
array('php://stderr', 'getIncrementalErrorOutput'),
);
}
public function testIteratorInput()
{
$input = function () {
yield 'ping';
yield 'pong';
};
$process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
$process->run();
$this->assertSame('pingpong', $process->getOutput());
}
public function testSimpleInputStream()
{
$input = new InputStream();
$process = $this->getProcessForCode('echo \'ping\'; echo fread(STDIN, 4); echo fread(STDIN, 4);');
$process->setInput($input);
$process->start(function ($type, $data) use ($input) {
if ('ping' === $data) {
$input->write('pang');
} elseif (!$input->isClosed()) {
$input->write('pong');
$input->close();
}
});
$process->wait();
$this->assertSame('pingpangpong', $process->getOutput());
}
public function testInputStreamWithCallable()
{
$i = 0;
$stream = fopen('php://memory', 'w+');
$stream = function () use ($stream, &$i) {
if ($i < 3) {
rewind($stream);
fwrite($stream, ++$i);
rewind($stream);
return $stream;
}
};
$input = new InputStream();
$input->onEmpty($stream);
$input->write($stream());
$process = $this->getProcessForCode('echo fread(STDIN, 3);');
$process->setInput($input);
$process->start(function ($type, $data) use ($input) {
$input->close();
});
$process->wait();
$this->assertSame('123', $process->getOutput());
}
public function testInputStreamWithGenerator()
{
$input = new InputStream();
$input->onEmpty(function ($input) {
yield 'pong';
$input->close();
});
$process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
$process->setInput($input);
$process->start();
$input->write('ping');
$process->wait();
$this->assertSame('pingpong', $process->getOutput());
}
public function testInputStreamOnEmpty()
{
$i = 0;
$input = new InputStream();
$input->onEmpty(function () use (&$i) { ++$i; });
$process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
$process->setInput($input);
$process->start(function ($type, $data) use ($input) {
if ('123' === $data) {
$input->close();
}
});
$process->wait();
$this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
$this->assertSame('123456', $process->getOutput());
}
public function testIteratorOutput()
{
$input = new InputStream();
$process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
$process->setInput($input);
$process->start();
$output = array();
foreach ($process as $type => $data) {
$output[] = array($type, $data);
break;
}
$expectedOutput = array(
array($process::OUT, '123'),
);
$this->assertSame($expectedOutput, $output);
$input->write(345);
foreach ($process as $type => $data) {
$output[] = array($type, $data);
}
$this->assertSame('', $process->getOutput());
$this->assertFalse($process->isRunning());
$expectedOutput = array(
array($process::OUT, '123'),
array($process::ERR, '234'),
array($process::OUT, '345'),
array($process::ERR, '456'),
);
$this->assertSame($expectedOutput, $output);
}
public function testNonBlockingNorClearingIteratorOutput()
{
$input = new InputStream();
$process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
$process->setInput($input);
$process->start();
$output = array();
foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
$output[] = array($type, $data);
break;
}
$expectedOutput = array(
array($process::OUT, ''),
);
$this->assertSame($expectedOutput, $output);
$input->write(123);
foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
if ('' !== $data) {
$output[] = array($type, $data);
}
}
$this->assertSame('123', $process->getOutput());
$this->assertFalse($process->isRunning());
$expectedOutput = array(
array($process::OUT, ''),
array($process::OUT, '123'),
);
$this->assertSame($expectedOutput, $output);
}
public function testChainedProcesses()
{
$p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
$p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
$p2->setInput($p1);
$p1->start();
$p2->run();
$this->assertSame('123', $p1->getErrorOutput());
$this->assertSame('', $p1->getOutput());
$this->assertSame('', $p2->getErrorOutput());
$this->assertSame('456', $p2->getOutput());
}
public function testSetBadEnv()
{
$process = $this->getProcess('echo hello');
$process->setEnv(array('bad%%' => '123'));
$process->inheritEnvironmentVariables(true);
$process->run();
$this->assertSame('hello'.PHP_EOL, $process->getOutput());
$this->assertSame('', $process->getErrorOutput());
}
public function testEnvBackupDoesNotDeleteExistingVars()
{
putenv('existing_var=foo');
$_ENV['existing_var'] = 'foo';
$process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
$process->setEnv(array('existing_var' => 'bar', 'new_test_var' => 'foo'));
$process->inheritEnvironmentVariables();
$process->run();
$this->assertSame('foo', $process->getOutput());
$this->assertSame('foo', getenv('existing_var'));
$this->assertFalse(getenv('new_test_var'));
putenv('existing_var');
unset($_ENV['existing_var']);
}
public function testEnvIsInherited()
{
$process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ', 'EMPTY' => ''));
putenv('FOO=BAR');
$_ENV['FOO'] = 'BAR';
$process->run();
$expected = array('BAR' => 'BAZ', 'EMPTY' => '', 'FOO' => 'BAR');
$env = array_intersect_key(unserialize($process->getOutput()), $expected);
$this->assertEquals($expected, $env);
putenv('FOO');
unset($_ENV['FOO']);
}
public function testGetCommandLine()
{
$p = new Process(array('/usr/bin/php'));
$expected = '\\' === DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
$this->assertSame($expected, $p->getCommandLine());
}
/**
* @dataProvider provideEscapeArgument
*/
public function testEscapeArgument($arg)
{
$p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg));
$p->run();
$this->assertSame($arg, $p->getOutput());
}
public function testRawCommandLine()
{
$p = new Process(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);')));
$p->run();
$expected = <<<EOTXT
Array
(
[0] => -
[1] => a
[2] =>
[3] => b
)
EOTXT;
$this->assertSame($expected, str_replace('Standard input code', '-', $p->getOutput()));
}
public function provideEscapeArgument()
{
yield array('a"b%c%');
yield array('a"b^c^');
yield array("a\nb'c");
yield array('a^b c!');
yield array("a!b\tc");
yield array('a\\\\"\\"');
yield array('éÉèÈàÀöä');
}
public function testEnvArgument()
{
$env = array('FOO' => 'Foo', 'BAR' => 'Bar');
$cmd = '\\' === DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
$p = new Process($cmd, null, $env);
$p->run(null, array('BAR' => 'baR', 'BAZ' => 'baZ'));
$this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
$this->assertSame($env, $p->getEnv());
}
/**
* @param string $commandline
* @param null|string $cwd
* @param null|array $env
* @param null|string $input
* @param int $timeout
* @param array $options
*
* @return Process
*/
private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60)
{
$process = new Process($commandline, $cwd, $env, $input, $timeout);
$process->inheritEnvironmentVariables();
if (self::$process) {
self::$process->stop(0);
}
return self::$process = $process;
}
/**
* @return Process
*/
private function getProcessForCode($code, $cwd = null, array $env = null, $input = null, $timeout = 60)
{
return $this->getProcess(array(self::$phpBin, '-r', $code), $cwd, $env, $input, $timeout);
}
}
class NonStringifiable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
pcntl_signal(SIGUSR1, function () { echo 'SIGUSR1'; exit; });
echo 'Caught ';
$n = 0;
while ($n++ < 400) {
usleep(10000);
pcntl_signal_dispatch();
}
{
"name": "symfony/process",
"type": "library",
"description": "Symfony Process Component",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": "^7.1.3"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Process\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "4.1-dev"
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony Process Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
Yii2 Queue Extension Change Log
===============================
2.1.0 May 24, 2018
------------------
- Bug #210: Worker option to define php bin path to run child process (zhuravljov)
- Enh: Worker loop event (zhuravljov)
- Bug #207: Console params validation (zhuravljov)
- Bug #224: Invalid identifier "DELAY" (lar-dragon)
- Enh #192: AWS SQS implementation (elitemaks, manoj-girnar)
- Bug #126: Handles a fatal error of the job execution in isolate mode (zhuravljov)
2.0.2 December 26, 2017
-----------------------
- Bug #92: Resolve issue in debug panel (farmani-eigital)
- Bug #99: Retry connecting after connection has timed out for redis driver (cebe)
- Bug #180: Fixed info command of file driver (victorruan)
- Enh #158: Add Amqp Interop driver (makasim)
- Enh #185: Loop object instead of Signal helper (zhuravljov)
- Enh #188: Configurable verbose mode (zhuravljov)
- Enh: Start and stop events of a worker (zhuravljov)
2.0.1 November 13, 2017
-----------------------
- Bug #98: Fixed timeout error handler (zhuravljov)
- Bug #112: Queue command inside module (tsingsun)
- Bug #118: Synchronized moving of delayed and reserved jobs to waiting list (zhuravljov)
- Bug #155: Slave DB breaks listener (zhuravljov)
- Enh #97: `Queue::status` is public method (zhuravljov)
- Enh #116: Add Chinese Guide (kids-return)
- Enh #122: Rename `Job` to `JobInterface` (zhuravljov)
- Enh #137: All throwable errors caused by jobs are now caught (brandonkelly)
- Enh #141: Clear and remove commands for File, DB, Beanstalk and Redis drivers (zhuravljov)
- Enh #147: Igbinary job serializer (xutl)
- Enh #148: Allow to change vhost setting for RabbitMQ (ischenko)
- Enh #151: Compatibility with Yii 2.0.13 and PHP 7.2 (zhuravljov)
- Enh #160: Benchmark of job wait time (zhuravljov)
- Enh: Rename `cli\Verbose` behavior to `cli\VerboseBehavior` (zhuravljov)
- Enh: Rename `serializers\Serializer` interface to `serializers\SerializerInterface` (zhuravljov)
- Enh: Added `Signal::setExitFlag()` to stop `Queue::run()` loop manually (silverfire)
2.0.0 July 15, 2017
-------------------
- Enh: The package is moved to yiisoft/yii2-queue (zhuravljov)
1.1.0 July 12, 2017
-------------------
- Enh #50 Documentation about worker starting control (zhuravljov)
- Enh #70: Durability for rabbitmq queues (mkubenka)
- Enh: Detailed error about job type in message handling (zhuravljov)
- Enh #60: Enhanced event handling (zhuravljov)
- Enh: Job priority for DB driver (zhuravljov)
- Enh: File mode options of file driver (zhuravljov)
- Enh #47: Redis queue listen timeout (zhuravljov)
- Enh #23: Retryable jobs (zhuravljov)
1.0.1 June 7, 2017
------------------
- Enh #58: Deleting failed jobs from queue (zhuravljov)
- Enh #55: Job priority (zhuravljov)
1.0.0 May 4, 2017
-----------------
- Enh: Improvements of log behavior (zhuravljov)
- Enh: File driver stat info (zhuravljov)
- Enh: Beanstalk stat info (zhuravljov)
- Enh: Colorized driver info actions (zhuravljov)
- Enh: Colorized verbose mode (zhuravljov)
- Enh: Improvements of debug panel (zhuravljov)
- Enh: Queue job message statuses (zhuravljov)
- Enh: Gii job generator (zhuravljov)
- Enh: Enhanced gearman driver (zhuravljov)
- Enh: Queue message identifiers (zhuravljov)
- Enh: File queue (zhuravljov)
0.12.2 April 29, 2017
---------------------
- Enh #10: Separate option that turn off isolate mode of job execute (zhuravljov)
0.12.1 April 20, 2017
---------------------
- Bug #37: Fixed opening of a child process (zhuravljov)
- Enh: Ability to push a closure (zhuravljov)
- Enh: Before push event (zhuravljov)
0.12.0 April 14, 2017
---------------------
- Enh #18: Executes a job in a child process (zhuravljov)
- Bug #25: Enabled output buffer breaks output streams (luke-)
- Enh: After push event (zhuravljov)
0.11.0 April 2, 2017
--------------------
- Enh #21: Delayed jobs for redis queue (zhuravljov)
- Enh: Info action for db and redis queue command (zhuravljov)
0.10.1 March 29, 2017
---------------------
- Bug: Fixed db driver for pgsql (zhuravljov)
- Bug #16: Timeout of  queue reading lock for db driver (zhuravljov)
- Enh: Minor code style enhancements (SilverFire)
0.10.0 March 22, 2017
---------------------
- Enh #14: Json job serializer (zhuravljov)
- Enh: Delayed running of a job (zhuravljov)
0.9.1 March 6, 2017
-------------------
- Bug #13: Fixed reading of DB queue (zhuravljov)
0.9.0 March 6, 2017
-------------------
- Enh: Signal handlers (zhuravljov)
- Enh: Add exchange for AMQP driver (airani)
- Enh: Beanstalk driver (zhuravljov)
- Enh: Added English docs (samdark)
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
<p align="center">
<a href="https://github.com/yiisoft" target="_blank">
<img src="https://avatars0.githubusercontent.com/u/993323" height="100px">
</a>
<h1 align="center">Yii2 Queue Extension</h1>
<br>
</p>
An extension for running tasks asynchronously via queues.
It supports queues based on **DB**, **Redis**, **RabbitMQ**, **AMQP**, **Beanstalk** and **Gearman**.
Documentation is at [docs/guide/README.md](docs/guide/README.md).
[![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-queue/v/stable.svg)](https://packagist.org/packages/yiisoft/yii2-queue)
[![Total Downloads](https://poser.pugx.org/yiisoft/yii2-queue/downloads.svg)](https://packagist.org/packages/yiisoft/yii2-queue)
[![Build Status](https://travis-ci.org/yiisoft/yii2-queue.svg?branch=master)](https://travis-ci.org/yiisoft/yii2-queue)
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require --prefer-dist yiisoft/yii2-queue
```
or add
```
"yiisoft/yii2-queue": "~2.0.0"
```
to the require section of your `composer.json` file.
Basic Usage
-----------
Each task which is sent to queue should be defined as a separate class.
For example, if you need to download and save a file the class may look like the following:
```php
class DownloadJob extends BaseObject implements \yii\queue\JobInterface
{
public $url;
public $file;
public function execute($queue)
{
file_put_contents($this->file, file_get_contents($this->url));
}
}
```
Here's how to send a task into the queue:
```php
Yii::$app->queue->push(new DownloadJob([
'url' => 'http://example.com/image.jpg',
'file' => '/tmp/image.jpg',
]));
```
To push a job into the queue that should run after 5 minutes:
```php
Yii::$app->queue->delay(5 * 60)->push(new DownloadJob([
'url' => 'http://example.com/image.jpg',
'file' => '/tmp/image.jpg',
]));
```
The exact way a task is executed depends on the used driver. Most drivers can be run using
console commands, which the component automatically registers in your application.
This command obtains and executes tasks in a loop until the queue is empty:
```sh
yii queue/run
```
This command launches a daemon which infinitely queries the queue:
```sh
yii queue/listen
```
See the documentation for more details about driver specific console commands and their options.
The component also has the ability to track the status of a job which was pushed into queue.
```php
// Push a job into the queue and get a message ID.
$id = Yii::$app->queue->push(new SomeJob());
// Check whether the job is waiting for execution.
Yii::$app->queue->isWaiting($id);
// Check whether a worker got the job from the queue and executes it.
Yii::$app->queue->isReserved($id);
// Check whether a worker has executed the job.
Yii::$app->queue->isDone($id);
```
For more details see [the guide](docs/guide/README.md).
Upgrading Instructions
======================
This file contains the upgrade notes. These notes highlight changes that could break your
application when you upgrade the package from one version to another.
Upgrade from 2.0.1 to 2.0.2
---------------------------
* The [Amqp driver](docs/guide/driver-amqp.md) has been deprecated and will be removed in `2.1`.
It is advised to migrate to [Amqp Interop](docs/guide/driver-amqp-interop.md) instead.
* Added `\yii\queue\cli\Command::isWorkerAction()` abstract method. If you use your own console
controllers to run queue listeners, you must implement it.
* `\yii\queue\cli\Signal` helper is deprecated and will be removed in `2.1`. The extension uses
`\yii\queue\cli\SignalLoop` instead of the helper.
* If you use your own console controller to listen to a queue, you must upgrade it. See the native
console controllers for how to upgrade.
Upgrade from 2.0.0 to 2.0.1
---------------------------
* `yii\queue\cli\Verbose` behavior was renamed to `yii\queue\cli\VerboseBehavior`. The old class was
marked as deprecated and will be removed in `2.1.0`.
* `Job`, `RetryableJob` and `Serializer` interfaces were renamed to `JobInterface`,
`RetryableJobInterface` and `SerializerInterface`. The old names are declared as deprecated
and will be removed in `2.1.0`.
Upgrade from 1.1.0 to 2.0.0
---------------------------
* Code has been moved to yii namespace. Check and replace `zhuravljov\yii` to `yii` namespace for
your project.
Upgrade from 1.0.0 to 1.1.0
---------------------------
* Event `Queue::EVENT_AFTER_EXEC_ERROR` renamed to `Queue::EVENT_AFTER_ERROR`.
* Removed method `Queue::later()`. Use method chain `Yii::$app->queue->delay(60)->push()` instead.
* Changed table schema for DB driver. Apply migration.
Upgrade from 0.x to 1.0.0
-------------------------
* Some methods and constants were modified.
- Method `Job::run()` modified to `Job::execute($queue)`.
- Const `Queue::EVENT_BEFORE_WORK` renamed to `Queue::EVENT_BEFORE_EXEC`.
- Const `Queue::EVENT_AFTER_WORK` renamed to `Queue::EVENT_AFTER_EXEC`.
- Const `Queue::EVENT_AFTER_ERROR` renamed to `Queue::EVENT_AFTER_EXEC_ERROR`.
* Method `Queue::sendMessage` renamed to `Queue::pushMessage`. Check it if you use it in your own
custom drivers.
Upgrade from 0.10.1
-------------------
* Driver property was removed and this functionality was moved into queue classes. If you use public
methods of `Yii::$app->queue->driver` you need to use the methods of `Yii::$app->queue`.
You also need to check your configs. For example, now the config for the db queue is:
```php
'queue' => [
'class' => \zhuravljov\yii\queue\db\Queue::class,
'db' => 'db',
'tableName' => '{{%queue}}',
'channel' => 'default',
'mutex' => \yii\mutex\MysqlMutex::class,
],
```
Instead of the old variant:
```php
'queue' => [
'class' => \zhuravljov\yii\queue\Queue::class,
'driver' => [
'class' => \yii\queue\db\Driver::class,
'db' => 'db',
'tableName' => '{{%queue}}'
'channel' => 'default',
'mutex' => \yii\mutex\MysqlMutex::class,
],
],
```
{
"name": "yiisoft/yii2-queue",
"description": "Yii2 Queue Extension which supported DB, Redis, RabbitMQ, Beanstalk, SQS and Gearman",
"type": "yii2-extension",
"keywords": ["yii", "queue", "async", "gii", "db", "redis", "rabbitmq", "beanstalk", "gearman", "sqs"],
"license": "BSD-3-Clause",
"authors": [
{
"name": "Roman Zhuravlev",
"email": "zhuravljov@gmail.com"
}
],
"support": {
"issues": "https://github.com/yiisoft/yii2-queue/issues",
"source": "https://github.com/yiisoft/yii2-queue",
"docs": "https://github.com/yiisoft/yii2-queue/blob/master/docs/guide"
},
"require": {
"php": ">=5.5.0",
"yiisoft/yii2": "~2.0.14",
"symfony/process": "*"
},
"require-dev": {
"yiisoft/yii2-redis": "*",
"php-amqplib/php-amqplib": "*",
"enqueue/amqp-lib": "^0.8",
"pda/pheanstalk": "*",
"jeremeamia/superclosure": "*",
"yiisoft/yii2-debug": "*",
"yiisoft/yii2-gii": "*",
"phpunit/phpunit": "~4.4",
"aws/aws-sdk-php": ">=2.4"
},
"suggest": {
"ext-pcntl": "Need for process signals.",
"yiisoft/yii2-redis": "Need for Redis queue.",
"pda/pheanstalk": "Need for Beanstalk queue.",
"php-amqplib/php-amqplib": "Need for AMQP queue.",
"enqueue/amqp-lib": "Need for AMQP interop queue.",
"ext-gearman": "Need for Gearman queue.",
"aws/aws-sdk-php": "Need for aws SQS."
},
"autoload": {
"psr-4": {
"yii\\queue\\": "src",
"yii\\queue\\amqp\\": "src/drivers/amqp",
"yii\\queue\\amqp_interop\\": "src/drivers/amqp_interop",
"yii\\queue\\beanstalk\\": "src/drivers/beanstalk",
"yii\\queue\\db\\": "src/drivers/db",
"yii\\queue\\file\\": "src/drivers/file",
"yii\\queue\\gearman\\": "src/drivers/gearman",
"yii\\queue\\redis\\": "src/drivers/redis",
"yii\\queue\\sync\\": "src/drivers/sync",
"yii\\queue\\sqs\\": "src/drivers/sqs"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
]
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
/**
* Error Event.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class ErrorEvent extends ExecEvent
{
/**
* @var \Exception|\Throwable
*/
public $error;
/**
* @var bool
*/
public $retry;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
/**
* Exec Event.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class ExecEvent extends JobEvent
{
/**
* @var int attempt number
*/
public $attempt;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
/**
* Job Interface.
*
* @deprecated Will be removed in 2.1.0. Use JobInterface instead of Job.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
interface Job extends JobInterface
{
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
use yii\base\Event;
/**
* Job Event.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class JobEvent extends Event
{
/**
* @var Queue
* @inheritdoc
*/
public $sender;
/**
* @var string|null unique id of a job
*/
public $id;
/**
* @var JobInterface
*/
public $job;
/**
* @var int time to reserve in seconds of the job
*/
public $ttr;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
/**
* Job Interface.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
interface JobInterface
{
/**
* @param Queue $queue which pushed and is handling the job
*/
public function execute($queue);
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
use Yii;
use yii\base\Behavior;
/**
* Log Behavior.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class LogBehavior extends Behavior
{
/**
* @var Queue
* @inheritdoc
*/
public $owner;
/**
* @var bool
*/
public $autoFlush = true;
/**
* @inheritdoc
*/
public function events()
{
return [
Queue::EVENT_AFTER_PUSH => 'afterPush',
Queue::EVENT_BEFORE_EXEC => 'beforeExec',
Queue::EVENT_AFTER_EXEC => 'afterExec',
Queue::EVENT_AFTER_ERROR => 'afterError',
cli\Queue::EVENT_WORKER_START => 'workerStart',
cli\Queue::EVENT_WORKER_STOP => 'workerStop',
];
}
/**
* @param PushEvent $event
*/
public function afterPush(PushEvent $event)
{
$title = $this->getJobTitle($event);
Yii::info("$title is pushed.", Queue::class);
}
/**
* @param ExecEvent $event
*/
public function beforeExec(ExecEvent $event)
{
$title = $this->getExecTitle($event);
Yii::info("$title is started.", Queue::class);
Yii::beginProfile($title, Queue::class);
}
/**
* @param ExecEvent $event
*/
public function afterExec(ExecEvent $event)
{
$title = $this->getExecTitle($event);
Yii::endProfile($title, Queue::class);
Yii::info("$title is finished.", Queue::class);
if ($this->autoFlush) {
Yii::getLogger()->flush(true);
}
}
/**
* @param ErrorEvent $event
*/
public function afterError(ErrorEvent $event)
{
$title = $this->getExecTitle($event);
Yii::endProfile($title, Queue::class);
Yii::error("$title is finished with error: $event->error.", Queue::class);
if ($this->autoFlush) {
Yii::getLogger()->flush(true);
}
}
/**
* @param cli\WorkerEvent $event
* @since 2.0.2
*/
public function workerStart(cli\WorkerEvent $event)
{
$title = 'Worker ' . $event->sender->getWorkerPid();
Yii::info("$title is started.", Queue::class);
Yii::beginProfile($title, Queue::class);
if ($this->autoFlush) {
Yii::getLogger()->flush(true);
}
}
/**
* @param cli\WorkerEvent $event
* @since 2.0.2
*/
public function workerStop(cli\WorkerEvent $event)
{
$title = 'Worker ' . $event->sender->getWorkerPid();
Yii::endProfile($title, Queue::class);
Yii::info("$title is stopped.", Queue::class);
if ($this->autoFlush) {
Yii::getLogger()->flush(true);
}
}
/**
* @param JobEvent $event
* @return string
* @since 2.0.2
*/
protected function getJobTitle(JobEvent $event)
{
$name = $event->job instanceof JobInterface ? get_class($event->job) : 'mixed data';
return "[$event->id] $name";
}
/**
* @param ExecEvent $event
* @return string
* @since 2.0.2
*/
protected function getExecTitle(ExecEvent $event)
{
$title = $this->getJobTitle($event);
$extra = "attempt: $event->attempt";
if ($pid = $event->sender->getWorkerPid()) {
$extra .= ", PID: $pid";
}
return "$title ($extra)";
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
/**
* Push Event.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class PushEvent extends JobEvent
{
/**
* @var int
*/
public $delay;
/**
* @var mixed
*/
public $priority;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
use Yii;
use yii\base\Component;
use yii\base\InvalidArgumentException;
use yii\di\Instance;
use yii\helpers\VarDumper;
use yii\queue\serializers\PhpSerializer;
use yii\queue\serializers\SerializerInterface;
/**
* Base Queue.
*
* @property null|int $workerPid
* @since 2.0.2
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
abstract class Queue extends Component
{
/**
* @event PushEvent
*/
const EVENT_BEFORE_PUSH = 'beforePush';
/**
* @event PushEvent
*/
const EVENT_AFTER_PUSH = 'afterPush';
/**
* @event ExecEvent
*/
const EVENT_BEFORE_EXEC = 'beforeExec';
/**
* @event ExecEvent
*/
const EVENT_AFTER_EXEC = 'afterExec';
/**
* @event ExecEvent
*/
const EVENT_AFTER_ERROR = 'afterError';
/**
* @see Queue::isWaiting()
*/
const STATUS_WAITING = 1;
/**
* @see Queue::isReserved()
*/
const STATUS_RESERVED = 2;
/**
* @see Queue::isDone()
*/
const STATUS_DONE = 3;
/**
* @var bool whether to enable strict job type control.
* Note that in order to enable type control, a pushing job must be [[JobInterface]] instance.
* @since 2.0.1
*/
public $strictJobType = true;
/**
* @var SerializerInterface|array
*/
public $serializer = PhpSerializer::class;
/**
* @var int default time to reserve a job
*/
public $ttr = 300;
/**
* @var int default attempt count
*/
public $attempts = 1;
private $pushTtr;
private $pushDelay;
private $pushPriority;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->serializer = Instance::ensure($this->serializer, SerializerInterface::class);
}
/**
* Sets TTR for job execute.
*
* @param int|mixed $value
* @return $this
*/
public function ttr($value)
{
$this->pushTtr = $value;
return $this;
}
/**
* Sets delay for later execute.
*
* @param int|mixed $value
* @return $this
*/
public function delay($value)
{
$this->pushDelay = $value;
return $this;
}
/**
* Sets job priority.
*
* @param mixed $value
* @return $this
*/
public function priority($value)
{
$this->pushPriority = $value;
return $this;
}
/**
* Pushes job into queue.
*
* @param JobInterface|mixed $job
* @return string|null id of a job message
*/
public function push($job)
{
$event = new PushEvent([
'job' => $job,
'ttr' => $this->pushTtr ?: (
$job instanceof RetryableJobInterface
? $job->getTtr()
: $this->ttr
),
'delay' => $this->pushDelay ?: 0,
'priority' => $this->pushPriority,
]);
$this->pushTtr = null;
$this->pushDelay = null;
$this->pushPriority = null;
$this->trigger(self::EVENT_BEFORE_PUSH, $event);
if ($event->handled) {
return null;
}
if ($this->strictJobType && !($event->job instanceof JobInterface)) {
throw new InvalidArgumentException('Job must be instance of JobInterface.');
}
$message = $this->serializer->serialize($event->job);
$event->id = $this->pushMessage($message, $event->ttr, $event->delay, $event->priority);
$this->trigger(self::EVENT_AFTER_PUSH, $event);
return $event->id;
}
/**
* @param string $message
* @param int $ttr time to reserve in seconds
* @param int $delay
* @param mixed $priority
* @return string id of a job message
*/
abstract protected function pushMessage($message, $ttr, $delay, $priority);
/**
* Uses for CLI drivers and gets process ID of a worker.
*
* @since 2.0.2
*/
public function getWorkerPid()
{
return null;
}
/**
* @param string $id of a job message
* @param string $message
* @param int $ttr time to reserve
* @param int $attempt number
* @return bool
*/
protected function handleMessage($id, $message, $ttr, $attempt)
{
$job = $this->serializer->unserialize($message);
if (!($job instanceof JobInterface)) {
$dump = VarDumper::dumpAsString($job);
throw new InvalidArgumentException("Job $id must be a JobInterface instance instead of $dump.");
}
$event = new ExecEvent([
'id' => $id,
'job' => $job,
'ttr' => $ttr,
'attempt' => $attempt,
]);
$this->trigger(self::EVENT_BEFORE_EXEC, $event);
if ($event->handled) {
return true;
}
try {
$event->job->execute($this);
} catch (\Exception $error) {
return $this->handleError($event->id, $event->job, $event->ttr, $event->attempt, $error);
} catch (\Throwable $error) {
return $this->handleError($event->id, $event->job, $event->ttr, $event->attempt, $error);
}
$this->trigger(self::EVENT_AFTER_EXEC, $event);
return true;
}
/**
* @param string|null $id
* @param JobInterface $job
* @param int $ttr
* @param int $attempt
* @param \Exception|\Throwable $error
* @return bool
* @internal
*/
public function handleError($id, $job, $ttr, $attempt, $error)
{
$event = new ErrorEvent([
'id' => $id,
'job' => $job,
'ttr' => $ttr,
'attempt' => $attempt,
'error' => $error,
'retry' => $job instanceof RetryableJobInterface
? $job->canRetry($attempt, $error)
: $attempt < $this->attempts,
]);
$this->trigger(self::EVENT_AFTER_ERROR, $event);
return !$event->retry;
}
/**
* @param string $id of a job message
* @return bool
*/
public function isWaiting($id)
{
return $this->status($id) === self::STATUS_WAITING;
}
/**
* @param string $id of a job message
* @return bool
*/
public function isReserved($id)
{
return $this->status($id) === self::STATUS_RESERVED;
}
/**
* @param string $id of a job message
* @return bool
*/
public function isDone($id)
{
return $this->status($id) === self::STATUS_DONE;
}
/**
* @param string $id of a job message
* @return int status code
*/
abstract public function status($id);
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
/**
* Retryable Job Interface.
*
* @deprecated Will be removed in 2.1.0. Use RetryableJobInterface instead of RetryableJob.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
interface RetryableJob extends RetryableJobInterface
{
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue;
/**
* Retryable Job Interface.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
interface RetryableJobInterface extends JobInterface
{
/**
* @return int time to reserve in seconds
*/
public function getTtr();
/**
* @param int $attempt number
* @param \Exception|\Throwable $error from last execute of the job
* @return bool
*/
public function canRetry($attempt, $error);
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
use yii\base\Action as BaseAction;
use yii\base\InvalidConfigException;
use yii\console\Controller as ConsoleController;
/**
* Base Command Action.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
abstract class Action extends BaseAction
{
/**
* @var Queue
*/
public $queue;
/**
* @var Command|ConsoleController
*/
public $controller;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if (!$this->queue && ($this->controller instanceof Command)) {
$this->queue = $this->controller->queue;
}
if (!($this->controller instanceof ConsoleController)) {
throw new InvalidConfigException('The controller must be console controller.');
}
if (!($this->queue instanceof Queue)) {
throw new InvalidConfigException('The queue must be cli queue.');
}
}
/**
* @param string $string
* @return string
*/
protected function format($string)
{
return call_user_func_array([$this->controller, 'ansiFormat'], func_get_args());
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\RuntimeException as ProcessRuntimeException;
use Symfony\Component\Process\Process;
use yii\console\Controller;
/**
* Base Command.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
abstract class Command extends Controller
{
/**
* The exit code of the exec action which is returned when job was done.
*/
const EXEC_DONE = 0;
/**
* The exit code of the exec action which is returned when job wasn't done and wanted next attempt.
*/
const EXEC_RETRY = 3;
/**
* @var Queue
*/
public $queue;
/**
* @var bool verbose mode of a job execute. If enabled, execute result of each job
* will be printed.
*/
public $verbose = false;
/**
* @var array additional options to the verbose behavior.
* @since 2.0.2
*/
public $verboseConfig = [
'class' => VerboseBehavior::class,
];
/**
* @var bool isolate mode. It executes a job in a child process.
*/
public $isolate = true;
/**
* @var string path to php interpreter that uses to run child processes.
* If it is undefined, PHP_BINARY will be used.
* @since 2.0.3
*/
public $phpBinary;
/**
* @inheritdoc
*/
public function options($actionID)
{
$options = parent::options($actionID);
if ($this->canVerbose($actionID)) {
$options[] = 'verbose';
}
if ($this->canIsolate($actionID)) {
$options[] = 'isolate';
$options[] = 'phpBinary';
}
return $options;
}
/**
* @inheritdoc
*/
public function optionAliases()
{
return array_merge(parent::optionAliases(), [
'v' => 'verbose',
]);
}
/**
* @param string $actionID
* @return bool
* @since 2.0.2
*/
abstract protected function isWorkerAction($actionID);
/**
* @param string $actionID
* @return bool
*/
protected function canVerbose($actionID)
{
return $actionID === 'exec' || $this->isWorkerAction($actionID);
}
/**
* @param string $actionID
* @return bool
*/
protected function canIsolate($actionID)
{
return $this->isWorkerAction($actionID);
}
/**
* @inheritdoc
*/
public function beforeAction($action)
{
if ($this->canVerbose($action->id) && $this->verbose) {
$this->queue->attachBehavior('verbose', ['command' => $this] + $this->verboseConfig);
}
if ($this->canIsolate($action->id) && $this->isolate) {
if ($this->phpBinary === null) {
$this->phpBinary = PHP_BINARY;
}
$this->queue->messageHandler = function ($id, $message, $ttr, $attempt) {
return $this->handleMessage($id, $message, $ttr, $attempt);
};
}
return parent::beforeAction($action);
}
/**
* Executes a job.
* The command is internal, and used to isolate a job execution. Manual usage is not provided.
*
* @param string|null $id of a message
* @param int $ttr time to reserve
* @param int $attempt number
* @param int $pid of a worker
* @return int exit code
* @internal It is used with isolate mode.
*/
public function actionExec($id, $ttr, $attempt, $pid)
{
if ($this->queue->execute($id, file_get_contents('php://stdin'), $ttr, $attempt, $pid)) {
return self::EXEC_DONE;
}
return self::EXEC_RETRY;
}
/**
* Handles message using child process.
*
* @param string|null $id of a message
* @param string $message
* @param int $ttr time to reserve
* @param int $attempt number
* @return bool
* @throws
* @see actionExec()
*/
protected function handleMessage($id, $message, $ttr, $attempt)
{
// Executes child process
$cmd = strtr('php yii queue/exec "id" "ttr" "attempt" "pid"', [
'php' => $this->phpBinary,
'yii' => $_SERVER['SCRIPT_FILENAME'],
'queue' => $this->uniqueId,
'id' => $id,
'ttr' => $ttr,
'attempt' => $attempt,
'pid' => $this->queue->getWorkerPid(),
]);
foreach ($this->getPassedOptions() as $name) {
if (in_array($name, $this->options('exec'), true)) {
$cmd .= ' --' . $name . '=' . $this->$name;
}
}
if (!in_array('color', $this->getPassedOptions(), true)) {
$cmd .= ' --color=' . $this->isColorEnabled();
}
$process = new Process($cmd, null, null, $message, $ttr);
try {
$result = $process->run(function ($type, $buffer) {
if ($type === Process::ERR) {
$this->stderr($buffer);
} else {
$this->stdout($buffer);
}
});
if (!in_array($result, [self::EXEC_DONE, self::EXEC_RETRY])) {
throw new ProcessFailedException($process);
}
return $result === self::EXEC_DONE;
} catch (ProcessRuntimeException $error) {
$job = $this->queue->serializer->unserialize($message);
return $this->queue->handleError($id, $job, $ttr, $attempt, $error);
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
/**
* Loop Interface.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
* @since 2.0.2
*/
interface LoopInterface
{
/**
* @return bool whether to continue listening of the queue.
*/
public function canContinue();
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
use Yii;
use yii\base\BootstrapInterface;
use yii\base\InvalidConfigException;
use yii\console\Application as ConsoleApp;
use yii\helpers\Inflector;
use yii\queue\Queue as BaseQueue;
/**
* Queue with CLI.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
abstract class Queue extends BaseQueue implements BootstrapInterface
{
/**
* @event WorkerEvent that is triggered when the worker is started.
* @since 2.0.2
*/
const EVENT_WORKER_START = 'workerStart';
/**
* @event WorkerEvent that is triggered each iteration between requests to queue.
* @since 2.0.3
*/
const EVENT_WORKER_LOOP = 'workerLoop';
/**
* @event WorkerEvent that is triggered when the worker is stopped.
* @since 2.0.2
*/
const EVENT_WORKER_STOP = 'workerStop';
/**
* @var array|string
* @since 2.0.2
*/
public $loopConfig = SignalLoop::class;
/**
* @var string command class name
*/
public $commandClass = Command::class;
/**
* @var array of additional options of command
*/
public $commandOptions = [];
/**
* @var callable|null
* @internal for worker command only
*/
public $messageHandler;
/**
* @var int current process ID of a worker.
* @since 2.0.2
*/
private $_workerPid;
/**
* @return string command id
* @throws
*/
protected function getCommandId()
{
foreach (Yii::$app->getComponents(false) as $id => $component) {
if ($component === $this) {
return Inflector::camel2id($id);
}
}
throw new InvalidConfigException('Queue must be an application component.');
}
/**
* @inheritdoc
*/
public function bootstrap($app)
{
if ($app instanceof ConsoleApp) {
$app->controllerMap[$this->getCommandId()] = [
'class' => $this->commandClass,
'queue' => $this,
] + $this->commandOptions;
}
}
/**
* Runs worker.
*
* @param callable $handler
* @return null|int exit code
* @since 2.0.2
*/
protected function runWorker(callable $handler)
{
$this->_workerPid = getmypid();
/** @var LoopInterface $loop */
$loop = Yii::createObject($this->loopConfig, [$this]);
$event = new WorkerEvent(['loop' => $loop]);
$this->trigger(self::EVENT_WORKER_START, $event);
if ($event->exitCode !== null) {
return $event->exitCode;
}
$exitCode = null;
try {
call_user_func($handler, function () use ($loop, $event) {
$this->trigger(self::EVENT_WORKER_LOOP, $event);
return $event->exitCode === null && $loop->canContinue();
});
} finally {
$this->trigger(self::EVENT_WORKER_STOP, $event);
$this->_workerPid = null;
}
return $event->exitCode;
}
/**
* Gets process ID of a worker.
*
* @inheritdoc
* @return int
* @since 2.0.2
*/
public function getWorkerPid()
{
return $this->_workerPid;
}
/**
* @inheritdoc
*/
protected function handleMessage($id, $message, $ttr, $attempt)
{
if ($this->messageHandler) {
return call_user_func($this->messageHandler, $id, $message, $ttr, $attempt);
}
return parent::handleMessage($id, $message, $ttr, $attempt);
}
/**
* @param string $id of a message
* @param string $message
* @param int $ttr time to reserve
* @param int $attempt number
* @param int $workerPid of worker process
* @return bool
* @internal for worker command only
*/
public function execute($id, $message, $ttr, $attempt, $workerPid)
{
$this->_workerPid = $workerPid;
return parent::handleMessage($id, $message, $ttr, $attempt);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
/**
* Process Signal Helper.
*
* @deprecated since 2.0.2 and will be removed in 2.1. Use SignalLoop instead.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Signal
{
private static $exit = false;
/**
* Checks exit signals
* Used mainly by [[yii\queue\Queue]] to check, whether job execution
* loop can be continued.
* @return bool
*/
public static function isExit()
{
if (function_exists('pcntl_signal')) {
// Installs a signal handler
static $handled = false;
if (!$handled) {
foreach ([SIGTERM, SIGINT, SIGHUP] as $signal) {
pcntl_signal($signal, function () {
static::setExitFlag();
});
}
$handled = true;
}
// Checks signal
if (!static::$exit) {
pcntl_signal_dispatch();
}
}
return static::$exit;
}
/**
* Sets exit flag to `true`
* Method can be used to simulate exit signal for methods that use
* [[isExit()]] to check whether execution loop can be continued.
*/
public static function setExitFlag()
{
static::$exit = true;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
use yii\base\BaseObject;
/**
* Signal Loop.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
* @since 2.0.2
*/
class SignalLoop extends BaseObject implements LoopInterface
{
/**
* @var array of signals to exit from listening of the queue.
*/
public $exitSignals = [
15, // SIGTERM
2, // SIGINT
1, // SIGHUP
];
/**
* @var array of signals to suspend listening of the queue.
* For example: SIGTSTP
*/
public $suspendSignals = [];
/**
* @var array of signals to resume listening of the queue.
* For example: SIGCONT
*/
public $resumeSignals = [];
/**
* @var Queue
*/
protected $queue;
/**
* @var bool status when exit signal was got.
*/
private static $exit = false;
/**
* @var bool status when suspend or resume signal was got.
*/
private static $pause = false;
/**
* @param Queue $queue
* @inheritdoc
*/
public function __construct($queue, array $config = [])
{
$this->queue = $queue;
parent::__construct($config);
}
/**
* Sets signal handlers.
*
* @inheritdoc
*/
public function init()
{
parent::init();
if (extension_loaded('pcntl')) {
foreach ($this->exitSignals as $signal) {
pcntl_signal($signal, function () {
self::$exit = true;
});
}
foreach ($this->suspendSignals as $signal) {
pcntl_signal($signal, function () {
self::$pause = true;
});
}
foreach ($this->resumeSignals as $signal) {
pcntl_signal($signal, function () {
self::$pause = false;
});
}
}
}
/**
* Checks signals state.
*
* @inheritdoc
*/
public function canContinue()
{
if (extension_loaded('pcntl')) {
pcntl_signal_dispatch();
// Wait for resume signal until loop is suspended
while (self::$pause && !self::$exit) {
usleep(10000);
pcntl_signal_dispatch();
}
}
return !self::$exit;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
/**
* Verbose Behavior.
*
* @deprecated Will be removed in 2.1.0. Use VerboseBehavior instead of Verbose.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Verbose extends VerboseBehavior
{
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
use yii\base\Behavior;
use yii\console\Controller;
use yii\helpers\Console;
use yii\queue\ErrorEvent;
use yii\queue\ExecEvent;
/**
* Verbose Behavior.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class VerboseBehavior extends Behavior
{
/**
* @var Queue
*/
public $owner;
/**
* @var Controller
*/
public $command;
/**
* @var float timestamp
*/
private $jobStartedAt;
/**
* @var int timestamp
*/
private $workerStartedAt;
/**
* @inheritdoc
*/
public function events()
{
return [
Queue::EVENT_BEFORE_EXEC => 'beforeExec',
Queue::EVENT_AFTER_EXEC => 'afterExec',
Queue::EVENT_AFTER_ERROR => 'afterError',
Queue::EVENT_WORKER_START => 'workerStart',
Queue::EVENT_WORKER_STOP => 'workerStop',
];
}
/**
* @param ExecEvent $event
*/
public function beforeExec(ExecEvent $event)
{
$this->jobStartedAt = microtime(true);
$this->command->stdout(date('Y-m-d H:i:s'), Console::FG_YELLOW);
$this->command->stdout($this->jobTitle($event), Console::FG_GREY);
$this->command->stdout(' - ', Console::FG_YELLOW);
$this->command->stdout('Started', Console::FG_GREEN);
$this->command->stdout(PHP_EOL);
}
/**
* @param ExecEvent $event
*/
public function afterExec(ExecEvent $event)
{
$this->command->stdout(date('Y-m-d H:i:s'), Console::FG_YELLOW);
$this->command->stdout($this->jobTitle($event), Console::FG_GREY);
$this->command->stdout(' - ', Console::FG_YELLOW);
$this->command->stdout('Done', Console::FG_GREEN);
$duration = number_format(round(microtime(true) - $this->jobStartedAt, 3), 3);
$this->command->stdout(" ($duration s)", Console::FG_YELLOW);
$this->command->stdout(PHP_EOL);
}
/**
* @param ErrorEvent $event
*/
public function afterError(ErrorEvent $event)
{
$this->command->stdout(date('Y-m-d H:i:s'), Console::FG_YELLOW);
$this->command->stdout($this->jobTitle($event), Console::FG_GREY);
$this->command->stdout(' - ', Console::FG_YELLOW);
$this->command->stdout('Error', Console::BG_RED);
if ($this->jobStartedAt) {
$duration = number_format(round(microtime(true) - $this->jobStartedAt, 3), 3);
$this->command->stdout(" ($duration s)", Console::FG_YELLOW);
}
$this->command->stdout(PHP_EOL);
$this->command->stdout('> ' . get_class($event->error) . ': ', Console::FG_RED);
$message = explode("\n", ltrim($event->error->getMessage()), 2)[0]; // First line
$this->command->stdout($message, Console::FG_GREY);
$this->command->stdout(PHP_EOL);
}
/**
* @param ExecEvent $event
* @return string
* @since 2.0.2
*/
protected function jobTitle(ExecEvent $event)
{
$class = get_class($event->job);
$extra = "attempt: $event->attempt";
if ($pid = $event->sender->getWorkerPid()) {
$extra .= ", pid: $pid";
}
return " [$event->id] $class ($extra)";
}
/**
* @param WorkerEvent $event
* @since 2.0.2
*/
public function workerStart(WorkerEvent $event)
{
$this->workerStartedAt = time();
$this->command->stdout(date('Y-m-d H:i:s'), Console::FG_YELLOW);
$pid = $event->sender->getWorkerPid();
$this->command->stdout(" [pid: $pid]", Console::FG_GREY);
$this->command->stdout(" - Worker is started\n", Console::FG_GREEN);
}
/**
* @param WorkerEvent $event
* @since 2.0.2
*/
public function workerStop(WorkerEvent $event)
{
$this->command->stdout(date('Y-m-d H:i:s'), Console::FG_YELLOW);
$pid = $event->sender->getWorkerPid();
$this->command->stdout(" [pid: $pid]", Console::FG_GREY);
$this->command->stdout(' - Worker is stopped ', Console::FG_GREEN);
$duration = $this->formatDuration(time() - $this->workerStartedAt);
$this->command->stdout("($duration)\n", Console::FG_YELLOW);
}
/**
* @param int $value
* @return string
* @since 2.0.2
*/
protected function formatDuration($value)
{
$seconds = $value % 60;
$value = ($value - $seconds) / 60;
$minutes = $value % 60;
$value = ($value - $minutes) / 60;
$hours = $value % 24;
$days = ($value - $hours) / 24;
if ($days > 0) {
return sprintf('%d:%02d:%02d:%02d', $days, $hours, $minutes, $seconds);
}
return sprintf('%d:%02d:%02d', $hours, $minutes, $seconds);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\cli;
use yii\base\Event;
/**
* Worker Event.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
* @since 2.0.2
*/
class WorkerEvent extends Event
{
/**
* @var Queue
* @inheritdoc
*/
public $sender;
/**
* @var LoopInterface
*/
public $loop;
/**
* @var null|int exit code
*/
public $exitCode;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\closure;
use SuperClosure\Serializer;
use yii\queue\PushEvent;
use yii\queue\Queue;
/**
* Closure Behavior.
*
* If you use the behavior, you can push closures into queue. For example:
*
* ```php
* $url = 'http://example.com/name.jpg';
* $file = '/tmp/name.jpg';
* Yii::$app->push(function () use ($url, $file) {
* file_put_contents($file, file_get_contents($url));
* });
* ```
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Behavior extends \yii\base\Behavior
{
/**
* @var Queue
*/
public $owner;
/**
* @inheritdoc
*/
public function events()
{
return [
Queue::EVENT_BEFORE_PUSH => 'beforePush',
];
}
/**
* Converts the closure to a job object.
* @param PushEvent $event
*/
public function beforePush(PushEvent $event)
{
if ($event->job instanceof \Closure) {
$serializer = new Serializer();
$serialized = $serializer->serialize($event->job);
$event->job = new Job();
$event->job->serialized = $serialized;
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\closure;
use SuperClosure\Serializer;
use yii\queue\JobInterface;
/**
* Closure Job.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Job implements JobInterface
{
/**
* @var string serialized closure
*/
public $serialized;
/**
* Unserializes and executes a closure.
* @inheritdoc
*/
public function execute($queue)
{
$serializer = new Serializer();
$closure = $serializer->unserialize($this->serialized);
$closure();
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\debug;
use Yii;
use yii\base\NotSupportedException;
use yii\base\ViewContextInterface;
use yii\helpers\VarDumper;
use yii\queue\JobInterface;
use yii\queue\PushEvent;
use yii\queue\Queue;
/**
* Debug Panel.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Panel extends \yii\debug\Panel implements ViewContextInterface
{
private $_jobs = [];
/**
* @inheritdoc
*/
public function getName()
{
return 'Queue';
}
/**
* @inheritdoc
*/
public function init()
{
parent::init();
PushEvent::on(Queue::class, Queue::EVENT_AFTER_PUSH, function (PushEvent $event) {
$this->_jobs[] = $this->getPushData($event);
});
}
/**
* @param PushEvent $event
* @return array
*/
protected function getPushData(PushEvent $event)
{
$data = [];
foreach (Yii::$app->getComponents(false) as $id => $component) {
if ($component === $event->sender) {
$data['sender'] = $id;
break;
}
}
$data['id'] = $event->id;
$data['ttr'] = $event->ttr;
$data['delay'] = $event->delay;
$data['priority'] = $event->priority;
if ($event->job instanceof JobInterface) {
$data['class'] = get_class($event->job);
$data['properties'] = [];
foreach (get_object_vars($event->job) as $property => $value) {
$data['properties'][$property] = VarDumper::dumpAsString($value);
}
} else {
$data['data'] = VarDumper::dumpAsString($event->job);
}
return $data;
}
/**
* @inheritdoc
*/
public function save()
{
return ['jobs' => $this->_jobs];
}
/**
* @inheritdoc
*/
public function getViewPath()
{
return __DIR__ . '/views';
}
/**
* @inheritdoc
*/
public function getSummary()
{
return Yii::$app->view->render('summary', [
'url' => $this->getUrl(),
'count' => isset($this->data['jobs']) ? count($this->data['jobs']) : 0,
], $this);
}
/**
* @inheritdoc
*/
public function getDetail()
{
$jobs = isset($this->data['jobs']) ? $this->data['jobs'] : [];
foreach ($jobs as &$job) {
$job['status'] = 'unknown';
/** @var Queue $queue */
if ($queue = Yii::$app->get($job['sender'], false)) {
try {
if ($queue->isWaiting($job['id'])) {
$job['status'] = 'waiting';
} elseif ($queue->isReserved($job['id'])) {
$job['status'] = 'reserved';
} elseif ($queue->isDone($job['id'])) {
$job['status'] = 'done';
}
} catch (NotSupportedException $e) {
} catch (\Exception $e) {
$job['status'] = $e->getMessage();
}
}
}
unset($job);
return Yii::$app->view->render('detail', compact('jobs'), $this);
}
}
<?php
/**
* @var \yii\web\View $this
* @var array $jobs
*/
use yii\helpers\Html;
$styles = [
'unknown' => 'default',
'waiting' => 'info',
'reserved' => 'warning',
'done' => 'success',
];
?>
<h1>Pushed <?= count($jobs) ?> jobs</h1>
<?php foreach ($jobs as $job): ?>
<div class="panel panel-<?= isset($styles[$job['status']]) ? $styles[$job['status']] : 'danger' ?>">
<div class="panel-heading">
<h3 class="panel-title">
<?php if (is_string($job['id'])): ?>
<?= Html::encode($job['id']) ?> -
<?php endif; ?>
<?= isset($job['class']) ? Html::encode($job['class']) : 'Mixed data' ?>
</h3>
</div>
<table class="table">
<tr>
<th>Sender</th>
<td><?= Html::encode($job['sender']) ?></td>
</tr>
<?php if (isset($job['id'])): ?>
<tr>
<th>ID</th>
<td><?= Html::encode($job['id']) ?></td>
</tr>
<?php endif; ?>
<tr>
<th>TTR</th>
<td><?= Html::encode($job['ttr']) ?></td>
</tr>
<?php if ($job['delay']): ?>
<tr>
<th>Delay</th>
<td><?= Html::encode($job['delay']) ?></td>
</tr>
<?php endif; ?>
<?php if (isset($job['priority'])): ?>
<tr>
<th>Priority</th>
<td><?= Html::encode($job['priority']) ?></td>
</tr>
<?php endif; ?>
<tr>
<th>Status</th>
<td><?= Html::encode($job['status']) ?></td>
</tr>
<?php if (isset($job['class'])): ?>
<tr>
<th>Class</th>
<td><?= Html::encode($job['class']) ?></td>
</tr>
<?php foreach ($job['properties'] as $property => $value): ?>
<tr>
<th><?= Html::encode($property) ?></th>
<td><?= Html::encode($value) ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<th>Data</th>
<td><?= Html::encode($job['data']) ?></td>
</tr>
<?php endif; ?>
</table>
</div>
<?php endforeach; ?>
<?php
$this->registerCss(
<<<'CSS'
.panel > .table th {
width: 25%;
}
CSS
);
<?php
/**
* @var \yii\web\View $this
* @var string $url
* @var int $count
*/
?>
<div class="yii-debug-toolbar__block">
<a href="<?= $url ?>">
Queue
<span class="yii-debug-toolbar__label yii-debug-toolbar__label_<?= $count ? 'info' : 'default' ?>">
<?= $count ?>
</span>
</a>
</div>
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\amqp;
use yii\queue\cli\Command as CliCommand;
/**
* Manages application amqp-queue.
*
* @deprecated since 2.0.2 and will be removed in 3.0. Consider using amqp_interop driver instead
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Command extends CliCommand
{
/**
* @var Queue
*/
public $queue;
/**
* @inheritdoc
*/
protected function isWorkerAction($actionID)
{
return $actionID === 'listen';
}
/**
* Listens amqp-queue and runs new jobs.
* It can be used as daemon process.
*/
public function actionListen()
{
$this->queue->listen();
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\amqp;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use yii\base\Application as BaseApp;
use yii\base\Event;
use yii\base\NotSupportedException;
use yii\queue\cli\Queue as CliQueue;
/**
* Amqp Queue.
*
* @deprecated since 2.0.2 and will be removed in 2.1. Consider using amqp_interop driver instead.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Queue extends CliQueue
{
public $host = 'localhost';
public $port = 5672;
public $user = 'guest';
public $password = 'guest';
public $queueName = 'queue';
public $exchangeName = 'exchange';
public $vhost = '/';
/**
* @var string command class name
*/
public $commandClass = Command::class;
/**
* @var AMQPStreamConnection
*/
protected $connection;
/**
* @var AMQPChannel
*/
protected $channel;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
Event::on(BaseApp::class, BaseApp::EVENT_AFTER_REQUEST, function () {
$this->close();
});
}
/**
* Listens amqp-queue and runs new jobs.
*/
public function listen()
{
$this->open();
$callback = function (AMQPMessage $payload) {
$id = $payload->get('message_id');
list($ttr, $message) = explode(';', $payload->body, 2);
if ($this->handleMessage($id, $message, $ttr, 1)) {
$payload->delivery_info['channel']->basic_ack($payload->delivery_info['delivery_tag']);
}
};
$this->channel->basic_qos(null, 1, null);
$this->channel->basic_consume($this->queueName, '', false, false, false, false, $callback);
while (count($this->channel->callbacks)) {
$this->channel->wait();
}
}
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
if ($delay) {
throw new NotSupportedException('Delayed work is not supported in the driver.');
}
if ($priority !== null) {
throw new NotSupportedException('Job priority is not supported in the driver.');
}
$this->open();
$id = uniqid('', true);
$this->channel->basic_publish(
new AMQPMessage("$ttr;$message", [
'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT,
'message_id' => $id,
]),
$this->exchangeName
);
return $id;
}
/**
* @inheritdoc
*/
public function status($id)
{
throw new NotSupportedException('Status is not supported in the driver.');
}
/**
* Opens connection and channel.
*/
protected function open()
{
if ($this->channel) {
return;
}
$this->connection = new AMQPStreamConnection($this->host, $this->port, $this->user, $this->password, $this->vhost);
$this->channel = $this->connection->channel();
$this->channel->queue_declare($this->queueName, false, true, false, false);
$this->channel->exchange_declare($this->exchangeName, 'direct', false, true, false);
$this->channel->queue_bind($this->queueName, $this->exchangeName);
}
/**
* Closes connection and channel.
*/
protected function close()
{
if (!$this->channel) {
return;
}
$this->channel->close();
$this->connection->close();
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\amqp_interop;
use yii\queue\cli\Command as CliCommand;
/**
* Manages application amqp-queue.
*
* @author Maksym Kotliar <kotlyar.maksim@gmail.com>
* @since 2.0.2
*/
class Command extends CliCommand
{
/**
* @var Queue
*/
public $queue;
/**
* @inheritdoc
*/
protected function isWorkerAction($actionID)
{
return $actionID === 'listen';
}
/**
* Listens amqp-queue and runs new jobs.
* It can be used as daemon process.
*/
public function actionListen()
{
$this->queue->listen();
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\amqp_interop;
use Enqueue\AmqpBunny\AmqpConnectionFactory as AmqpBunnyConnectionFactory;
use Enqueue\AmqpExt\AmqpConnectionFactory as AmqpExtConnectionFactory;
use Enqueue\AmqpLib\AmqpConnectionFactory as AmqpLibConnectionFactory;
use Enqueue\AmqpTools\DelayStrategyAware;
use Enqueue\AmqpTools\RabbitMqDlxDelayStrategy;
use Interop\Amqp\AmqpConnectionFactory;
use Interop\Amqp\AmqpConsumer;
use Interop\Amqp\AmqpContext;
use Interop\Amqp\AmqpMessage;
use Interop\Amqp\AmqpQueue;
use Interop\Amqp\AmqpTopic;
use Interop\Amqp\Impl\AmqpBind;
use yii\base\Application as BaseApp;
use yii\base\Event;
use yii\base\NotSupportedException;
use yii\queue\cli\Queue as CliQueue;
/**
* Amqp Queue.
*
* @author Maksym Kotliar <kotlyar.maksim@gmail.com>
* @since 2.0.2
*/
class Queue extends CliQueue
{
const ATTEMPT = 'yii-attempt';
const TTR = 'yii-ttr';
const DELAY = 'yii-delay';
const PRIORITY = 'yii-priority';
const ENQUEUE_AMQP_LIB = 'enqueue/amqp-lib';
const ENQUEUE_AMQP_EXT = 'enqueue/amqp-ext';
const ENQUEUE_AMQP_BUNNY = 'enqueue/amqp-bunny';
/**
* The connection to the borker could be configured as an array of options
* or as a DSN string like amqp:, amqps:, amqps://user:pass@localhost:1000/vhost.
*
* @var string
*/
public $dsn;
/**
* The message queue broker's host.
*
* @var string|null
*/
public $host;
/**
* The message queue broker's port.
*
* @var string|null
*/
public $port;
/**
* This is RabbitMQ user which is used to login on the broker.
*
* @var string|null
*/
public $user;
/**
* This is RabbitMQ password which is used to login on the broker.
*
* @var string|null
*/
public $password;
/**
* Virtual hosts provide logical grouping and separation of resources.
*
* @var string|null
*/
public $vhost;
/**
* The time PHP socket waits for an information while reading. In seconds.
*
* @var float|null
*/
public $readTimeout;
/**
* The time PHP socket waits for an information while witting. In seconds.
*
* @var float|null
*/
public $writeTimeout;
/**
* The time RabbitMQ keeps the connection on idle. In seconds.
*
* @var float|null
*/
public $connectionTimeout;
/**
* The periods of time PHP pings the broker in order to prolong the connection timeout. In seconds.
*
* @var float|null
*/
public $heartbeat;
/**
* PHP uses one shared connection if set true.
*
* @var bool|null
*/
public $persisted;
/**
* The connection will be established as later as possible if set true.
*
* @var bool|null
*/
public $lazy;
/**
* If false prefetch_count option applied separately to each new consumer on the channel
* If true prefetch_count option shared across all consumers on the channel.
*
* @var bool|null
*/
public $qosGlobal;
/**
* Defines number of message pre-fetched in advance on a channel basis.
*
* @var int|null
*/
public $qosPrefetchSize;
/**
* Defines number of message pre-fetched in advance per consumer.
*
* @var int|null
*/
public $qosPrefetchCount;
/**
* Defines whether secure connection should be used or not.
*
* @var bool|null
*/
public $sslOn;
/**
* Require verification of SSL certificate used.
*
* @var bool|null
*/
public $sslVerify;
/**
* Location of Certificate Authority file on local filesystem which should be used with the verify_peer context option to authenticate the identity of the remote peer.
*
* @var string|null
*/
public $sslCacert;
/**
* Path to local certificate file on filesystem.
*
* @var string|null
*/
public $sslCert;
/**
* Path to local private key file on filesystem in case of separate files for certificate (local_cert) and private key.
*
* @var string|null
*/
public $sslKey;
/**
* The queue used to consume messages from.
*
* @var string
*/
public $queueName = 'interop_queue';
/**
* The exchange used to publish messages to.
*
* @var string
*/
public $exchangeName = 'exchange';
/**
* Defines the amqp interop transport being internally used. Currently supports lib, ext and bunny values.
*
* @var string
*/
public $driver = self::ENQUEUE_AMQP_LIB;
/**
* This property should be an integer indicating the maximum priority the queue should support. Default is 10.
*
* @var int
*/
public $maxPriority = 10;
/**
* The property contains a command class which used in cli.
*
* @var string command class name
*/
public $commandClass = Command::class;
/**
* Amqp interop context.
*
* @var AmqpContext
*/
protected $context;
/**
* List of supported amqp interop drivers.
*
* @var string[]
*/
protected $supportedDrivers = [self::ENQUEUE_AMQP_LIB, self::ENQUEUE_AMQP_EXT, self::ENQUEUE_AMQP_BUNNY];
/**
* The property tells whether the setupBroker method was called or not.
* Having it we can do broker setup only once per process.
*
* @var bool
*/
protected $setupBrokerDone = false;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
Event::on(BaseApp::class, BaseApp::EVENT_AFTER_REQUEST, function () {
$this->close();
});
}
/**
* Listens amqp-queue and runs new jobs.
*/
public function listen()
{
$this->open();
$this->setupBroker();
$queue = $this->context->createQueue($this->queueName);
$consumer = $this->context->createConsumer($queue);
$this->context->subscribe($consumer, function (AmqpMessage $message, AmqpConsumer $consumer) {
if ($message->isRedelivered()) {
$consumer->acknowledge($message);
$this->redeliver($message);
return true;
}
$ttr = $message->getProperty(self::TTR);
$attempt = $message->getProperty(self::ATTEMPT, 1);
if ($this->handleMessage($message->getMessageId(), $message->getBody(), $ttr, $attempt)) {
$consumer->acknowledge($message);
} else {
$consumer->acknowledge($message);
$this->redeliver($message);
}
return true;
});
$this->context->consume();
}
/**
* @return AmqpContext
*/
public function getContext()
{
$this->open();
return $this->context;
}
/**
* @inheritdoc
*/
protected function pushMessage($payload, $ttr, $delay, $priority)
{
$this->open();
$this->setupBroker();
$topic = $this->context->createTopic($this->exchangeName);
$message = $this->context->createMessage($payload);
$message->setDeliveryMode(AmqpMessage::DELIVERY_MODE_PERSISTENT);
$message->setMessageId(uniqid('', true));
$message->setTimestamp(time());
$message->setProperty(self::ATTEMPT, 1);
$message->setProperty(self::TTR, $ttr);
$producer = $this->context->createProducer();
if ($delay) {
$message->setProperty(self::DELAY, $delay);
$producer->setDeliveryDelay($delay * 1000);
}
if ($priority) {
$message->setProperty(self::PRIORITY, $priority);
$producer->setPriority($priority);
}
$producer->send($topic, $message);
return $message->getMessageId();
}
/**
* @inheritdoc
*/
public function status($id)
{
throw new NotSupportedException('Status is not supported in the driver.');
}
/**
* Opens connection and channel.
*/
protected function open()
{
if ($this->context) {
return;
}
switch ($this->driver) {
case self::ENQUEUE_AMQP_LIB:
$connectionClass = AmqpLibConnectionFactory::class;
break;
case self::ENQUEUE_AMQP_EXT:
$connectionClass = AmqpExtConnectionFactory::class;
break;
case self::ENQUEUE_AMQP_BUNNY:
$connectionClass = AmqpBunnyConnectionFactory::class;
break;
default:
throw new \LogicException(sprintf('The given driver "%s" is not supported. Drivers supported are "%s"', $this->driver, implode('", "', $this->supportedDrivers)));
}
$config = [
'dsn' => $this->dsn,
'host' => $this->host,
'port' => $this->port,
'user' => $this->user,
'pass' => $this->password,
'vhost' => $this->vhost,
'read_timeout' => $this->readTimeout,
'write_timeout' => $this->writeTimeout,
'connection_timeout' => $this->connectionTimeout,
'heartbeat' => $this->heartbeat,
'persisted' => $this->persisted,
'lazy' => $this->lazy,
'qos_global' => $this->qosGlobal,
'qos_prefetch_size' => $this->qosPrefetchSize,
'qos_prefetch_count' => $this->qosPrefetchCount,
'ssl_on' => $this->sslOn,
'ssl_verify' => $this->sslVerify,
'ssl_cacert' => $this->sslCacert,
'ssl_cert' => $this->sslCert,
'ssl_key' => $this->sslKey,
];
$config = array_filter($config, function ($value) {
return null !== $value;
});
/** @var AmqpConnectionFactory $factory */
$factory = new $connectionClass($config);
$this->context = $factory->createContext();
if ($this->context instanceof DelayStrategyAware) {
$this->context->setDelayStrategy(new RabbitMqDlxDelayStrategy());
}
}
protected function setupBroker()
{
if ($this->setupBrokerDone) {
return;
}
$queue = $this->context->createQueue($this->queueName);
$queue->addFlag(AmqpQueue::FLAG_DURABLE);
$queue->setArguments(['x-max-priority' => $this->maxPriority]);
$this->context->declareQueue($queue);
$topic = $this->context->createTopic($this->exchangeName);
$topic->setType(AmqpTopic::TYPE_DIRECT);
$topic->addFlag(AmqpTopic::FLAG_DURABLE);
$this->context->declareTopic($topic);
$this->context->bind(new AmqpBind($queue, $topic));
$this->setupBrokerDone = true;
}
/**
* Closes connection and channel.
*/
protected function close()
{
if (!$this->context) {
return;
}
$this->context->close();
$this->context = null;
$this->setupBrokerDone = false;
}
/**
* {@inheritdoc}
*/
protected function redeliver(AmqpMessage $message)
{
$attempt = $message->getProperty(self::ATTEMPT, 1);
$newMessage = $this->context->createMessage($message->getBody(), $message->getProperties(), $message->getHeaders());
$newMessage->setDeliveryMode($message->getDeliveryMode());
$newMessage->setProperty(self::ATTEMPT, ++$attempt);
$this->context->createProducer()->send(
$this->context->createQueue($this->queueName),
$newMessage
);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\beanstalk;
use yii\console\Exception;
use yii\queue\cli\Command as CliCommand;
/**
* Manages application beanstalk-queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Command extends CliCommand
{
/**
* @var Queue
*/
public $queue;
/**
* @var string
*/
public $defaultAction = 'info';
/**
* @inheritdoc
*/
public function actions()
{
return [
'info' => InfoAction::class,
];
}
/**
* @inheritdoc
*/
protected function isWorkerAction($actionID)
{
return in_array($actionID, ['run', 'listen']);
}
/**
* Runs all jobs from beanstalk-queue.
* It can be used as cron job.
*
* @return null|int exit code.
*/
public function actionRun()
{
return $this->queue->run(false);
}
/**
* Listens beanstalk-queue and runs new jobs.
* It can be used as daemon process.
*
* @param int $timeout number of seconds to wait a job.
* @throws Exception when params are invalid.
* @return null|int exit code.
*/
public function actionListen($timeout = 3)
{
if (!is_numeric($timeout)) {
throw new Exception('Timeout must be numeric.');
}
if ($timeout < 1) {
throw new Exception('Timeout must be greater that zero.');
}
return $this->queue->run(true, $timeout);
}
/**
* Removes a job by id.
*
* @param int $id of the job.
* @throws Exception when the job is not found.
* @since 2.0.1
*/
public function actionRemove($id)
{
if (!$this->queue->remove($id)) {
throw new Exception('The job is not found.');
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\beanstalk;
use yii\helpers\Console;
use yii\queue\cli\Action;
/**
* Info about queue status.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class InfoAction extends Action
{
/**
* @var Queue
*/
public $queue;
/**
* Info about queue status.
*/
public function run()
{
Console::output($this->format('Statistical information about the tube:', Console::FG_GREEN));
foreach ($this->queue->getStatsTube() as $key => $value) {
Console::stdout($this->format("- $key: ", Console::FG_YELLOW));
Console::output($value);
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\beanstalk;
use Pheanstalk\Exception\ServerException;
use Pheanstalk\Pheanstalk;
use Pheanstalk\PheanstalkInterface;
use yii\base\InvalidArgumentException;
use yii\queue\cli\Queue as CliQueue;
/**
* Beanstalk Queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Queue extends CliQueue
{
/**
* @var string connection host
*/
public $host = 'localhost';
/**
* @var int connection port
*/
public $port = PheanstalkInterface::DEFAULT_PORT;
/**
* @var string beanstalk tube
*/
public $tube = 'queue';
/**
* @var string command class name
*/
public $commandClass = Command::class;
/**
* Listens queue and runs each job.
*
* @param bool $repeat whether to continue listening when queue is empty.
* @param int $timeout number of seconds to wait for next message.
* @return null|int exit code.
* @internal for worker command only.
* @since 2.0.2
*/
public function run($repeat, $timeout = 0)
{
return $this->runWorker(function (callable $canContinue) use ($repeat, $timeout) {
while ($canContinue()) {
if ($payload = $this->getPheanstalk()->reserveFromTube($this->tube, $timeout)) {
$info = $this->getPheanstalk()->statsJob($payload);
if ($this->handleMessage(
$payload->getId(),
$payload->getData(),
$info->ttr,
$info->reserves
)) {
$this->getPheanstalk()->delete($payload);
}
} elseif (!$repeat) {
break;
}
}
});
}
/**
* @inheritdoc
*/
public function status($id)
{
if (!is_numeric($id) || $id <= 0) {
throw new InvalidArgumentException("Unknown message ID: $id.");
}
try {
$stats = $this->getPheanstalk()->statsJob($id);
if ($stats['state'] === 'reserved') {
return self::STATUS_RESERVED;
}
return self::STATUS_WAITING;
} catch (ServerException $e) {
if ($e->getMessage() === 'Server reported NOT_FOUND') {
return self::STATUS_DONE;
}
throw $e;
}
}
/**
* Removes a job by ID.
*
* @param int $id of a job
* @return bool
* @since 2.0.1
*/
public function remove($id)
{
try {
$job = $this->getPheanstalk()->peek($id);
$this->getPheanstalk()->delete($job);
return true;
} catch (ServerException $e) {
if (strpos($e->getMessage(), 'NOT_FOUND') === 0) {
return false;
}
throw $e;
}
}
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
return $this->getPheanstalk()->putInTube(
$this->tube,
$message,
$priority ?: PheanstalkInterface::DEFAULT_PRIORITY,
$delay,
$ttr
);
}
/**
* @return object tube statistics
*/
public function getStatsTube()
{
return $this->getPheanstalk()->statsTube($this->tube);
}
/**
* @return Pheanstalk
*/
protected function getPheanstalk()
{
if (!$this->_pheanstalk) {
$this->_pheanstalk = new Pheanstalk($this->host, $this->port);
}
return $this->_pheanstalk;
}
private $_pheanstalk;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\db;
use yii\console\Exception;
use yii\queue\cli\Command as CliCommand;
/**
* Manages application db-queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Command extends CliCommand
{
/**
* @var Queue
*/
public $queue;
/**
* @var string
*/
public $defaultAction = 'info';
/**
* @inheritdoc
*/
public function actions()
{
return [
'info' => InfoAction::class,
];
}
/**
* @inheritdoc
*/
protected function isWorkerAction($actionID)
{
return in_array($actionID, ['run', 'listen'], true);
}
/**
* Runs all jobs from db-queue.
* It can be used as cron job.
*
* @return null|int exit code.
*/
public function actionRun()
{
return $this->queue->run(false);
}
/**
* Listens db-queue and runs new jobs.
* It can be used as daemon process.
*
* @param int $timeout number of seconds to sleep before next reading of the queue.
* @throws Exception when params are invalid.
* @return null|int exit code.
*/
public function actionListen($timeout = 3)
{
if (!is_numeric($timeout)) {
throw new Exception('Timeout must be numeric.');
}
if ($timeout < 1) {
throw new Exception('Timeout must be greater that zero.');
}
return $this->queue->run(true, $timeout);
}
/**
* Clears the queue.
*
* @since 2.0.1
*/
public function actionClear()
{
if ($this->confirm('Are you sure?')) {
$this->queue->clear();
}
}
/**
* Removes a job by id.
*
* @param int $id
* @throws Exception when the job is not found.
* @since 2.0.1
*/
public function actionRemove($id)
{
if (!$this->queue->remove($id)) {
throw new Exception('The job is not found.');
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\db;
use yii\db\Query;
use yii\helpers\Console;
use yii\queue\cli\Action;
/**
* Info about queue status.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class InfoAction extends Action
{
/**
* @var Queue
*/
public $queue;
/**
* Info about queue status.
*/
public function run()
{
Console::output($this->format('Jobs', Console::FG_GREEN));
Console::stdout($this->format('- waiting: ', Console::FG_YELLOW));
Console::output($this->getWaiting()->count('*', $this->queue->db));
Console::stdout($this->format('- delayed: ', Console::FG_YELLOW));
Console::output($this->getDelayed()->count('*', $this->queue->db));
Console::stdout($this->format('- reserved: ', Console::FG_YELLOW));
Console::output($this->getReserved()->count('*', $this->queue->db));
Console::stdout($this->format('- done: ', Console::FG_YELLOW));
Console::output($this->getDone()->count('*', $this->queue->db));
}
/**
* @return Query
*/
protected function getWaiting()
{
return (new Query())
->from($this->queue->tableName)
->andWhere(['channel' => $this->queue->channel])
->andWhere(['reserved_at' => null])
->andWhere(['delay' => 0]);
}
/**
* @return Query
*/
protected function getDelayed()
{
return (new Query())
->from($this->queue->tableName)
->andWhere(['channel' => $this->queue->channel])
->andWhere(['reserved_at' => null])
->andWhere(['>', 'delay', 0]);
}
/**
* @return Query
*/
protected function getReserved()
{
return (new Query())
->from($this->queue->tableName)
->andWhere(['channel' => $this->queue->channel])
->andWhere('[[reserved_at]] is not null')
->andWhere(['done_at' => null]);
}
/**
* @return Query
*/
protected function getDone()
{
return (new Query())
->from($this->queue->tableName)
->andWhere(['channel' => $this->queue->channel])
->andWhere('[[done_at]] is not null');
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\db;
use yii\base\Exception;
use yii\base\InvalidArgumentException;
use yii\db\Connection;
use yii\db\Query;
use yii\di\Instance;
use yii\mutex\Mutex;
use yii\queue\cli\Queue as CliQueue;
/**
* Db Queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Queue extends CliQueue
{
/**
* @var Connection|array|string
*/
public $db = 'db';
/**
* @var Mutex|array|string
*/
public $mutex = 'mutex';
/**
* @var int timeout
*/
public $mutexTimeout = 3;
/**
* @var string table name
*/
public $tableName = '{{%queue}}';
/**
* @var string
*/
public $channel = 'queue';
/**
* @var bool ability to delete released messages from table
*/
public $deleteReleased = true;
/**
* @var string command class name
*/
public $commandClass = Command::class;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->db = Instance::ensure($this->db, Connection::class);
$this->mutex = Instance::ensure($this->mutex, Mutex::class);
}
/**
* Listens queue and runs each job.
*
* @param bool $repeat whether to continue listening when queue is empty.
* @param int $timeout number of seconds to sleep before next iteration.
* @return null|int exit code.
* @internal for worker command only
* @since 2.0.2
*/
public function run($repeat, $timeout = 0)
{
return $this->runWorker(function (callable $canContinue) use ($repeat, $timeout) {
while ($canContinue()) {
if ($payload = $this->reserve()) {
if ($this->handleMessage(
$payload['id'],
$payload['job'],
$payload['ttr'],
$payload['attempt']
)) {
$this->release($payload);
}
} elseif (!$repeat) {
break;
} elseif ($timeout) {
sleep($timeout);
}
}
});
}
/**
* @inheritdoc
*/
public function status($id)
{
$payload = (new Query())
->from($this->tableName)
->where(['id' => $id])
->one($this->db);
if (!$payload) {
if ($this->deleteReleased) {
return self::STATUS_DONE;
}
throw new InvalidArgumentException("Unknown message ID: $id.");
}
if (!$payload['reserved_at']) {
return self::STATUS_WAITING;
}
if (!$payload['done_at']) {
return self::STATUS_RESERVED;
}
return self::STATUS_DONE;
}
/**
* Clears the queue.
*
* @since 2.0.1
*/
public function clear()
{
$this->db->createCommand()
->delete($this->tableName, ['channel' => $this->channel])
->execute();
}
/**
* Removes a job by ID.
*
* @param int $id of a job
* @return bool
* @since 2.0.1
*/
public function remove($id)
{
return (bool) $this->db->createCommand()
->delete($this->tableName, ['channel' => $this->channel, 'id' => $id])
->execute();
}
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
$this->db->createCommand()->insert($this->tableName, [
'channel' => $this->channel,
'job' => $message,
'pushed_at' => time(),
'ttr' => $ttr,
'delay' => $delay,
'priority' => $priority ?: 1024,
])->execute();
$tableSchema = $this->db->getTableSchema($this->tableName);
return $this->db->getLastInsertID($tableSchema->sequenceName);
}
/**
* Takes one message from waiting list and reserves it for handling.
*
* @return array|false payload
* @throws Exception in case it hasn't waited the lock
*/
protected function reserve()
{
return $this->db->useMaster(function () {
if (!$this->mutex->acquire(__CLASS__ . $this->channel, $this->mutexTimeout)) {
throw new Exception('Has not waited the lock.');
}
try {
$this->moveExpired();
// Reserve one message
$payload = (new Query())
->from($this->tableName)
->andWhere(['channel' => $this->channel, 'reserved_at' => null])
->andWhere('[[pushed_at]] <= :time - [[delay]]', [':time' => time()])
->orderBy(['priority' => SORT_ASC, 'id' => SORT_ASC])
->limit(1)
->one($this->db);
if (is_array($payload)) {
$payload['reserved_at'] = time();
$payload['attempt'] = (int) $payload['attempt'] + 1;
$this->db->createCommand()->update($this->tableName, [
'reserved_at' => $payload['reserved_at'],
'attempt' => $payload['attempt'],
], [
'id' => $payload['id'],
])->execute();
// pgsql
if (is_resource($payload['job'])) {
$payload['job'] = stream_get_contents($payload['job']);
}
}
} finally {
$this->mutex->release(__CLASS__ . $this->channel);
}
return $payload;
});
}
/**
* @param array $payload
*/
protected function release($payload)
{
if ($this->deleteReleased) {
$this->db->createCommand()->delete(
$this->tableName,
['id' => $payload['id']]
)->execute();
} else {
$this->db->createCommand()->update(
$this->tableName,
['done_at' => time()],
['id' => $payload['id']]
)->execute();
}
}
/**
* Moves expired messages into waiting list.
*/
private function moveExpired()
{
if ($this->reserveTime !== time()) {
$this->reserveTime = time();
$this->db->createCommand()->update(
$this->tableName,
['reserved_at' => null],
'[[reserved_at]] < :time - [[ttr]] and [[reserved_at]] is not null and [[done_at]] is null',
[':time' => $this->reserveTime]
)->execute();
}
}
private $reserveTime;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\db\migrations;
use yii\db\Migration;
/**
* Example of migration for queue message storage.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class M161119140200Queue extends Migration
{
public $tableName = '{{%queue}}';
public $tableOptions;
public function up()
{
$this->createTable($this->tableName, [
'id' => $this->primaryKey(),
'channel' => $this->string()->notNull(),
'job' => $this->binary()->notNull(),
'created_at' => $this->integer()->notNull(),
'started_at' => $this->integer(),
'finished_at' => $this->integer(),
], $this->tableOptions);
$this->createIndex('channel', $this->tableName, 'channel');
$this->createIndex('started_at', $this->tableName, 'started_at');
}
public function down()
{
$this->dropTable($this->tableName);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\db\migrations;
use yii\db\Migration;
/**
* Example of migration for queue message storage.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class M170307170300Later extends Migration
{
public $tableName = '{{%queue}}';
public function up()
{
$this->addColumn($this->tableName, 'timeout', $this->integer()->defaultValue(0)->notNull()->after('created_at'));
}
public function down()
{
$this->dropColumn($this->tableName, 'timeout');
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\db\migrations;
use yii\db\Migration;
/**
* Example of migration for queue message storage.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class M170509001400Retry extends Migration
{
public $tableName = '{{%queue}}';
public function up()
{
if ($this->db->driverName !== 'sqlite') {
$this->renameColumn($this->tableName, 'created_at', 'pushed_at');
$this->addColumn($this->tableName, 'ttr', $this->integer()->notNull()->after('pushed_at'));
$this->renameColumn($this->tableName, 'timeout', 'delay');
$this->dropIndex('started_at', $this->tableName);
$this->renameColumn($this->tableName, 'started_at', 'reserved_at');
$this->createIndex('reserved_at', $this->tableName, 'reserved_at');
$this->addColumn($this->tableName, 'attempt', $this->integer()->after('reserved_at'));
$this->renameColumn($this->tableName, 'finished_at', 'done_at');
} else {
$this->dropTable($this->tableName);
$this->createTable($this->tableName, [
'id' => $this->primaryKey(),
'channel' => $this->string()->notNull(),
'job' => $this->binary()->notNull(),
'pushed_at' => $this->integer()->notNull(),
'ttr' => $this->integer()->notNull(),
'delay' => $this->integer()->notNull(),
'reserved_at' => $this->integer(),
'attempt' => $this->integer(),
'done_at' => $this->integer(),
]);
$this->createIndex('channel', $this->tableName, 'channel');
$this->createIndex('reserved_at', $this->tableName, 'reserved_at');
}
}
public function down()
{
if ($this->db->driverName !== 'sqlite') {
$this->renameColumn($this->tableName, 'done_at', 'finished_at');
$this->dropColumn($this->tableName, 'attempt');
$this->dropIndex('reserved_at', $this->tableName);
$this->renameColumn($this->tableName, 'reserved_at', 'started_at');
$this->createIndex('started_at', $this->tableName, 'started_at');
$this->renameColumn($this->tableName, 'delay', 'timeout');
$this->dropColumn($this->tableName, 'ttr');
$this->renameColumn($this->tableName, 'pushed_at', 'created_at');
} else {
$this->dropTable($this->tableName);
$this->createTable($this->tableName, [
'id' => $this->primaryKey(),
'channel' => $this->string()->notNull(),
'job' => $this->binary()->notNull(),
'created_at' => $this->integer()->notNull(),
'timeout' => $this->integer()->notNull(),
'started_at' => $this->integer(),
'finished_at' => $this->integer(),
]);
$this->createIndex('channel', $this->tableName, 'channel');
$this->createIndex('started_at', $this->tableName, 'started_at');
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\db\migrations;
use yii\db\Migration;
/**
* Example of migration for queue message storage.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class M170601155600Priority extends Migration
{
public $tableName = '{{%queue}}';
public function up()
{
$this->addColumn($this->tableName, 'priority', $this->integer()->unsigned()->notNull()->defaultValue(1024)->after('delay'));
$this->createIndex('priority', $this->tableName, 'priority');
}
public function down()
{
$this->dropIndex('priority', $this->tableName);
$this->dropColumn($this->tableName, 'priority');
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\file;
use yii\console\Exception;
use yii\queue\cli\Command as CliCommand;
/**
* Manages application file-queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Command extends CliCommand
{
/**
* @var Queue
*/
public $queue;
/**
* @var string
*/
public $defaultAction = 'info';
/**
* @inheritdoc
*/
public function actions()
{
return [
'info' => InfoAction::class,
];
}
/**
* @inheritdoc
*/
protected function isWorkerAction($actionID)
{
return in_array($actionID, ['run', 'listen']);
}
/**
* Runs all jobs from file-queue.
* It can be used as cron job.
*
* @return null|int exit code.
*/
public function actionRun()
{
return $this->queue->run(false);
}
/**
* Listens file-queue and runs new jobs.
* It can be used as daemon process.
*
* @param int $timeout number of seconds to sleep before next reading of the queue.
* @throws Exception when params are invalid.
* @return null|int exit code.
*/
public function actionListen($timeout = 3)
{
if (!is_numeric($timeout)) {
throw new Exception('Timeout must be numeric.');
}
if ($timeout < 1) {
throw new Exception('Timeout must be greater that zero.');
}
return $this->queue->run(true, $timeout);
}
/**
* Clears the queue.
*
* @since 2.0.1
*/
public function actionClear()
{
if ($this->confirm('Are you sure?')) {
$this->queue->clear();
}
}
/**
* Removes a job by id.
*
* @param int $id
* @throws Exception when the job is not found.
* @since 2.0.1
*/
public function actionRemove($id)
{
if (!$this->queue->remove((int) $id)) {
throw new Exception('The job is not found.');
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\file;
use yii\helpers\Console;
use yii\queue\cli\Action;
/**
* Info about queue status.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class InfoAction extends Action
{
/**
* @var Queue
*/
public $queue;
/**
* Info about queue status.
*/
public function run()
{
Console::output($this->format('Jobs', Console::FG_GREEN));
Console::stdout($this->format('- waiting: ', Console::FG_YELLOW));
Console::output($this->getWaitingCount());
Console::stdout($this->format('- delayed: ', Console::FG_YELLOW));
Console::output($this->getDelayedCount());
Console::stdout($this->format('- reserved: ', Console::FG_YELLOW));
Console::output($this->getReservedCount());
Console::stdout($this->format('- done: ', Console::FG_YELLOW));
Console::output($this->getDoneCount());
}
/**
* @return int
*/
protected function getWaitingCount()
{
$data = $this->getIndexData();
return !empty($data['waiting']) ? count($data['waiting']) : 0;
}
/**
* @return int
*/
protected function getDelayedCount()
{
$data = $this->getIndexData();
return !empty($data['delayed']) ? count($data['delayed']) : 0;
}
/**
* @return int
*/
protected function getReservedCount()
{
$data = $this->getIndexData();
return !empty($data['reserved']) ? count($data['reserved']) : 0;
}
/**
* @return int
*/
protected function getDoneCount()
{
$data = $this->getIndexData();
$total = isset($data['lastId']) ? $data['lastId'] : 0;
return $total - $this->getDelayedCount() - $this->getWaitingCount();
}
protected function getIndexData()
{
static $data;
if ($data === null) {
$fileName = $this->queue->path . '/index.data';
if (file_exists($fileName)) {
$data = call_user_func($this->queue->indexDeserializer, file_get_contents($fileName));
} else {
$data = [];
}
}
return $data;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\file;
use Yii;
use yii\base\InvalidArgumentException;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
use yii\helpers\FileHelper;
use yii\queue\cli\Queue as CliQueue;
/**
* File Queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Queue extends CliQueue
{
/**
* @var string
*/
public $path = '@runtime/queue';
/**
* @var int
*/
public $dirMode = 0755;
/**
* @var int|null
*/
public $fileMode;
/**
* @var callable
*/
public $indexSerializer = 'serialize';
/**
* @var callable
*/
public $indexDeserializer = 'unserialize';
/**
* @var string
*/
public $commandClass = Command::class;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->path = Yii::getAlias($this->path);
if (!is_dir($this->path)) {
FileHelper::createDirectory($this->path, $this->dirMode, true);
}
}
/**
* Listens queue and runs each job.
*
* @param bool $repeat whether to continue listening when queue is empty.
* @param int $timeout number of seconds to sleep before next iteration.
* @return null|int exit code.
* @internal for worker command only.
* @since 2.0.2
*/
public function run($repeat, $timeout = 0)
{
return $this->runWorker(function (callable $canContinue) use ($repeat, $timeout) {
while ($canContinue()) {
if (($payload = $this->reserve()) !== null) {
list($id, $message, $ttr, $attempt) = $payload;
if ($this->handleMessage($id, $message, $ttr, $attempt)) {
$this->delete($payload);
}
} elseif (!$repeat) {
break;
} elseif ($timeout) {
sleep($timeout);
}
}
});
}
/**
* @inheritdoc
*/
public function status($id)
{
if (!is_numeric($id) || $id <= 0) {
throw new InvalidArgumentException("Unknown message ID: $id.");
}
if (file_exists("$this->path/job$id.data")) {
return self::STATUS_WAITING;
}
return self::STATUS_DONE;
}
/**
* Clears the queue.
*
* @since 2.0.1
*/
public function clear()
{
$this->touchIndex(function (&$data) {
$data = [];
foreach (glob("$this->path/job*.data") as $fileName) {
unlink($fileName);
}
});
}
/**
* Removes a job by ID.
*
* @param int $id of a job
* @return bool
* @since 2.0.1
*/
public function remove($id)
{
$removed = false;
$this->touchIndex(function (&$data) use ($id, &$removed) {
if (!empty($data['waiting'])) {
foreach ($data['waiting'] as $key => $payload) {
if ($payload[0] === $id) {
unset($data['waiting'][$key]);
$removed = true;
break;
}
}
}
if (!$removed && !empty($data['delayed'])) {
foreach ($data['delayed'] as $key => $payload) {
if ($payload[0] === $id) {
unset($data['delayed'][$key]);
$removed = true;
break;
}
}
}
if (!$removed && !empty($data['reserved'])) {
foreach ($data['reserved'] as $key => $payload) {
if ($payload[0] === $id) {
unset($data['reserved'][$key]);
$removed = true;
break;
}
}
}
if ($removed) {
unlink("$this->path/job$id.data");
}
});
return $removed;
}
/**
* Reserves message for execute.
*
* @return array|null payload
*/
protected function reserve()
{
$id = null;
$ttr = null;
$attempt = null;
$this->touchIndex(function (&$data) use (&$id, &$ttr, &$attempt) {
if (!empty($data['reserved'])) {
foreach ($data['reserved'] as $key => $payload) {
if ($payload[1] + $payload[3] < time()) {
list($id, $ttr, $attempt, $time) = $payload;
$data['reserved'][$key][2] = ++$attempt;
$data['reserved'][$key][3] = time();
return;
}
}
}
if (!empty($data['delayed']) && $data['delayed'][0][2] <= time()) {
list($id, $ttr, $time) = array_shift($data['delayed']);
} elseif (!empty($data['waiting'])) {
list($id, $ttr) = array_shift($data['waiting']);
}
if ($id) {
$attempt = 1;
$data['reserved']["job$id"] = [$id, $ttr, $attempt, time()];
}
});
if ($id) {
return [$id, file_get_contents("$this->path/job$id.data"), $ttr, $attempt];
}
return null;
}
/**
* Deletes reserved message.
*
* @param array $payload
*/
protected function delete($payload)
{
$id = $payload[0];
$this->touchIndex(function (&$data) use ($id) {
foreach ($data['reserved'] as $key => $payload) {
if ($payload[0] === $id) {
unset($data['reserved'][$key]);
break;
}
}
});
unlink("$this->path/job$id.data");
}
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
if ($priority !== null) {
throw new NotSupportedException('Job priority is not supported in the driver.');
}
$this->touchIndex(function (&$data) use ($message, $ttr, $delay, &$id) {
if (!isset($data['lastId'])) {
$data['lastId'] = 0;
}
$id = ++$data['lastId'];
$fileName = "$this->path/job$id.data";
file_put_contents($fileName, $message);
if ($this->fileMode !== null) {
chmod($fileName, $this->fileMode);
}
if (!$delay) {
$data['waiting'][] = [$id, $ttr, 0];
} else {
$data['delayed'][] = [$id, $ttr, time() + $delay];
usort($data['delayed'], function ($a, $b) {
if ($a[2] < $b[2]) {
return -1;
}
if ($a[2] > $b[2]) {
return 1;
}
if ($a[0] < $b[0]) {
return -1;
}
if ($a[0] > $b[0]) {
return 1;
}
return 0;
});
}
});
return $id;
}
/**
* @param callable $callback
* @throws InvalidConfigException
*/
private function touchIndex($callback)
{
$fileName = "$this->path/index.data";
$isNew = !file_exists($fileName);
touch($fileName);
if ($isNew && $this->fileMode !== null) {
chmod($fileName, $this->fileMode);
}
if (($file = fopen($fileName, 'rb+')) === false) {
throw new InvalidConfigException("Unable to open index file: $fileName");
}
flock($file, LOCK_EX);
$data = [];
$content = stream_get_contents($file);
if ($content !== '') {
$data = call_user_func($this->indexDeserializer, $content);
}
try {
$callback($data);
$newContent = call_user_func($this->indexSerializer, $data);
if ($newContent !== $content) {
ftruncate($file, 0);
rewind($file);
fwrite($file, $newContent);
fflush($file);
}
} finally {
flock($file, LOCK_UN);
fclose($file);
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\gearman;
use yii\queue\cli\Command as CliCommand;
/**
* Manages application gearman-queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Command extends CliCommand
{
/**
* @var Queue
*/
public $queue;
/**
* @inheritdoc
*/
protected function isWorkerAction($actionID)
{
return in_array($actionID, ['run', 'listen'], true);
}
/**
* Runs all jobs from gearman-queue.
* It can be used as cron job.
*
* @return null|int exit code.
*/
public function actionRun()
{
return $this->queue->run(false);
}
/**
* Listens gearman-queue and runs new jobs.
* It can be used as daemon process.
*
* @return null|int exit code.
*/
public function actionListen()
{
return $this->queue->run(true);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\gearman;
use yii\base\NotSupportedException;
use yii\queue\cli\Queue as CliQueue;
/**
* Gearman Queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Queue extends CliQueue
{
public $host = 'localhost';
public $port = 4730;
public $channel = 'queue';
/**
* @var string command class name
*/
public $commandClass = Command::class;
/**
* Listens queue and runs each job.
*
* @param bool $repeat whether to continue listening when queue is empty.
* @return null|int exit code.
* @internal for worker command only.
* @since 2.0.2
*/
public function run($repeat)
{
return $this->runWorker(function (callable $canContinue) use ($repeat) {
$worker = new \GearmanWorker();
$worker->addServer($this->host, $this->port);
$worker->addFunction($this->channel, function (\GearmanJob $payload) {
list($ttr, $message) = explode(';', $payload->workload(), 2);
$this->handleMessage($payload->handle(), $message, $ttr, 1);
});
$worker->setTimeout($repeat ? 1000 : 1);
while ($canContinue()) {
$result = $worker->work();
if (!$result && !$repeat) {
break;
}
}
});
}
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
if ($delay) {
throw new NotSupportedException('Delayed work is not supported in the driver.');
}
switch ($priority) {
case 'high':
return $this->getClient()->doHighBackground($this->channel, "$ttr;$message");
case 'low':
return $this->getClient()->doLowBackground($this->channel, "$ttr;$message");
default:
return $this->getClient()->doBackground($this->channel, "$ttr;$message");
}
}
/**
* @inheritdoc
*/
public function status($id)
{
$status = $this->getClient()->jobStatus($id);
if ($status[0] && !$status[1]) {
return self::STATUS_WAITING;
}
if ($status[0] && $status[1]) {
return self::STATUS_RESERVED;
}
return self::STATUS_DONE;
}
/**
* @return \GearmanClient
*/
protected function getClient()
{
if (!$this->_client) {
$this->_client = new \GearmanClient();
$this->_client->addServer($this->host, $this->port);
}
return $this->_client;
}
private $_client;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\redis;
use yii\console\Exception;
use yii\queue\cli\Command as CliCommand;
/**
* Manages application redis-queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Command extends CliCommand
{
/**
* @var Queue
*/
public $queue;
/**
* @var string
*/
public $defaultAction = 'info';
/**
* @inheritdoc
*/
public function actions()
{
return [
'info' => InfoAction::class,
];
}
/**
* @inheritdoc
*/
protected function isWorkerAction($actionID)
{
return in_array($actionID, ['run', 'listen'], true);
}
/**
* Runs all jobs from redis-queue.
* It can be used as cron job.
*
* @return null|int exit code.
*/
public function actionRun()
{
return $this->queue->run(false);
}
/**
* Listens redis-queue and runs new jobs.
* It can be used as daemon process.
*
* @param int $timeout number of seconds to wait a job.
* @throws Exception when params are invalid.
* @return null|int exit code.
*/
public function actionListen($timeout = 3)
{
if (!is_numeric($timeout)) {
throw new Exception('Timeout must be numeric.');
}
if ($timeout < 1) {
throw new Exception('Timeout must be greater that zero.');
}
return $this->queue->run(true, $timeout);
}
/**
* Clears the queue.
*
* @since 2.0.1
*/
public function actionClear()
{
if ($this->confirm('Are you sure?')) {
$this->queue->clear();
}
}
/**
* Removes a job by id.
*
* @param int $id
* @throws Exception when the job is not found.
* @since 2.0.1
*/
public function actionRemove($id)
{
if (!$this->queue->remove($id)) {
throw new Exception('The job is not found.');
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\redis;
use yii\helpers\Console;
use yii\queue\cli\Action;
/**
* Info about queue status.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class InfoAction extends Action
{
/**
* @var Queue
*/
public $queue;
/**
* Info about queue status.
*/
public function run()
{
$prefix = $this->queue->channel;
$waiting = $this->queue->redis->llen("$prefix.waiting");
$delayed = $this->queue->redis->zcount("$prefix.delayed", '-inf', '+inf');
$reserved = $this->queue->redis->zcount("$prefix.reserved", '-inf', '+inf');
$total = $this->queue->redis->get("$prefix.message_id");
$done = $total - $waiting - $delayed - $reserved;
Console::output($this->format('Jobs', Console::FG_GREEN));
Console::stdout($this->format('- waiting: ', Console::FG_YELLOW));
Console::output($waiting);
Console::stdout($this->format('- delayed: ', Console::FG_YELLOW));
Console::output($delayed);
Console::stdout($this->format('- reserved: ', Console::FG_YELLOW));
Console::output($reserved);
Console::stdout($this->format('- done: ', Console::FG_YELLOW));
Console::output($done);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\redis;
use yii\base\InvalidArgumentException;
use yii\base\NotSupportedException;
use yii\di\Instance;
use yii\queue\cli\Queue as CliQueue;
use yii\redis\Connection;
/**
* Redis Queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Queue extends CliQueue
{
/**
* @var Connection|array|string
*/
public $redis = 'redis';
/**
* @var string
*/
public $channel = 'queue';
/**
* @var string command class name
*/
public $commandClass = Command::class;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->redis = Instance::ensure($this->redis, Connection::class);
}
/**
* Listens queue and runs each job.
*
* @param bool $repeat whether to continue listening when queue is empty.
* @param int $timeout number of seconds to wait for next message.
* @return null|int exit code.
* @internal for worker command only.
* @since 2.0.2
*/
public function run($repeat, $timeout = 0)
{
return $this->runWorker(function (callable $canContinue) use ($repeat, $timeout) {
while ($canContinue()) {
if (($payload = $this->reserve($timeout)) !== null) {
list($id, $message, $ttr, $attempt) = $payload;
if ($this->handleMessage($id, $message, $ttr, $attempt)) {
$this->delete($id);
}
} elseif (!$repeat) {
break;
}
}
});
}
/**
* @inheritdoc
*/
public function status($id)
{
if (!is_numeric($id) || $id <= 0) {
throw new InvalidArgumentException("Unknown message ID: $id.");
}
if ($this->redis->hexists("$this->channel.attempts", $id)) {
return self::STATUS_RESERVED;
}
if ($this->redis->hexists("$this->channel.messages", $id)) {
return self::STATUS_WAITING;
}
return self::STATUS_DONE;
}
/**
* Clears the queue.
*
* @since 2.0.1
*/
public function clear()
{
while (!$this->redis->set("$this->channel.moving_lock", true, 'NX')) {
usleep(10000);
}
$this->redis->executeCommand('DEL', $this->redis->keys("$this->channel.*"));
}
/**
* Removes a job by ID.
*
* @param int $id of a job
* @return bool
* @since 2.0.1
*/
public function remove($id)
{
while (!$this->redis->set("$this->channel.moving_lock", true, 'NX', 'EX', 1)) {
usleep(10000);
}
if ($this->redis->hdel("$this->channel.messages", $id)) {
$this->redis->zrem("$this->channel.delayed", $id);
$this->redis->zrem("$this->channel.reserved", $id);
$this->redis->lrem("$this->channel.waiting", 0, $id);
$this->redis->hdel("$this->channel.attempts", $id);
return true;
}
return false;
}
/**
* @param int $timeout timeout
* @return array|null payload
*/
protected function reserve($timeout)
{
// Moves delayed and reserved jobs into waiting list with lock for one second
if ($this->redis->set("$this->channel.moving_lock", true, 'NX', 'EX', 1)) {
$this->moveExpired("$this->channel.delayed");
$this->moveExpired("$this->channel.reserved");
}
// Find a new waiting message
$id = null;
if (!$timeout) {
$id = $this->redis->rpop("$this->channel.waiting");
} elseif ($result = $this->redis->brpop("$this->channel.waiting", $timeout)) {
$id = $result[1];
}
if (!$id) {
return null;
}
$payload = $this->redis->hget("$this->channel.messages", $id);
list($ttr, $message) = explode(';', $payload, 2);
$this->redis->zadd("$this->channel.reserved", time() + $ttr, $id);
$attempt = $this->redis->hincrby("$this->channel.attempts", $id, 1);
return [$id, $message, $ttr, $attempt];
}
/**
* @param string $from
*/
protected function moveExpired($from)
{
$now = time();
if ($expired = $this->redis->zrevrangebyscore($from, $now, '-inf')) {
$this->redis->zremrangebyscore($from, '-inf', $now);
foreach ($expired as $id) {
$this->redis->rpush("$this->channel.waiting", $id);
}
}
}
/**
* Deletes message by ID.
*
* @param int $id of a message
*/
protected function delete($id)
{
$this->redis->zrem("$this->channel.reserved", $id);
$this->redis->hdel("$this->channel.attempts", $id);
$this->redis->hdel("$this->channel.messages", $id);
}
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
if ($priority !== null) {
throw new NotSupportedException('Job priority is not supported in the driver.');
}
$id = $this->redis->incr("$this->channel.message_id");
$this->redis->hset("$this->channel.messages", $id, "$ttr;$message");
if (!$delay) {
$this->redis->lpush("$this->channel.waiting", $id);
} else {
$this->redis->zadd("$this->channel.delayed", time() + $delay, $id);
}
return $id;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\sqs;
use yii\console\Exception;
use yii\queue\cli\Command as CliCommand;
/**
* Manages application aws sqs-queue.
*
* @author Max Kozlovsky <kozlovskymaxim@gmail.com>
* @author Manoj Malviya <manojm@girnarsoft.com>
*/
class Command extends CliCommand
{
/**
* @var Queue
*/
public $queue;
/**
* Runs all jobs from sqs.
* It can be used as cron job.
*
* @return null|int exit code.
*/
public function actionRun()
{
return $this->queue->run(false);
}
/**
* Listens sqs and runs new jobs.
* It can be used as demon process.
*
* @param int $timeout number of seconds to sleep before next reading of the queue.
* @throws Exception when params are invalid.
* @return null|int exit code.
*/
public function actionListen($timeout = 3)
{
if (!is_numeric($timeout)) {
throw new Exception('Timeout must be numeric.');
}
if ($timeout < 1 || $timeout > 20) {
throw new Exception('Timeout must be between 1 and 20');
}
return $this->queue->run(true, $timeout);
}
/**
* Clears the queue.
*/
public function actionClear()
{
if ($this->confirm('Are you sure?')) {
$this->queue->clear();
$this->stdout("Queue has been cleared.\n");
}
}
/**
* @inheritdoc
*/
protected function isWorkerAction($actionID)
{
return in_array($actionID, ['run', 'listen']);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\sqs;
use Aws\Credentials\CredentialProvider;
use Aws\Sqs\SqsClient;
use yii\base\NotSupportedException;
use yii\queue\cli\Queue as CliQueue;
use yii\queue\serializers\JsonSerializer;
/**
* SQS Queue.
*
* @author Max Kozlovsky <kozlovskymaxim@gmail.com>
* @author Manoj Malviya <manojm@girnarsoft.com>
*/
class Queue extends CliQueue
{
/**
* The SQS url.
* @var string
*/
public $url;
/**
* aws access key.
* @var string|null
*/
public $key;
/**
* aws secret.
* @var string|null
*/
public $secret;
/**
* region where queue is hosted.
* @var string
*/
public $region = '';
/**
* API version.
* @var string
*/
public $version = 'latest';
/**
* @var string command class name
* @inheritdoc
*/
public $commandClass = Command::class;
/**
* Json serializer by default.
* @inheritdoc
*/
public $serializer = JsonSerializer::class;
/**
* @var SqsClient
*/
private $_client;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
}
/**
* Listens queue and runs each job.
*
* @param bool $repeat whether to continue listening when queue is empty.
* @param int $timeout number of seconds to sleep before next iteration.
* @return null|int exit code.
* @internal for worker command only
*/
public function run($repeat, $timeout = 0)
{
return $this->runWorker(function (callable $canContinue) use ($repeat, $timeout) {
while ($canContinue()) {
if (($payload = $this->reserve($timeout)) !== null) {
$id = $payload['MessageId'];
$message = $payload['Body'];
$ttr = (int) $payload['MessageAttributes']['TTR']['StringValue'];
$attempt = (int) $payload['Attributes']['ApproximateReceiveCount'];
if ($this->handleMessage($id, $message, $ttr, $attempt)) {
$this->delete($payload);
}
} elseif (!$repeat) {
break;
}
}
});
}
/**
* Gets a single message from SQS queue and sets the visibility to reserve message.
*
* @param int $timeout number of seconds for long polling. Must be between 0 and 20.
* @return null|array payload.
*/
protected function reserve($timeout)
{
$response = $this->getClient()->receiveMessage([
'QueueUrl' => $this->url,
'AttributeNames' => ['ApproximateReceiveCount'],
'MessageAttributeNames' => ['TTR'],
'MaxNumberOfMessages' => 1,
'VisibilityTimeout' => $this->ttr,
'WaitTimeSeconds' => $timeout,
]);
if (!$response['Messages']) {
return null;
}
$payload = reset($response['Messages']);
$ttr = (int) $payload['MessageAttributes']['TTR']['StringValue'];
if ($ttr != $this->ttr) {
$this->getClient()->changeMessageVisibility([
'QueueUrl' => $this->url,
'ReceiptHandle' => $payload['ReceiptHandle'],
'VisibilityTimeout' => $ttr,
]);
}
return $payload;
}
/**
* Deletes the message after successfully handling.
*
* @param array $payload
*/
protected function delete($payload)
{
$this->getClient()->deleteMessage([
'QueueUrl' => $this->url,
'ReceiptHandle' => $payload['ReceiptHandle'],
]);
}
/**
* Clears the queue.
*/
public function clear()
{
$this->getClient()->purgeQueue([
'QueueUrl' => $this->url,
]);
}
/**
* @inheritdoc
*/
public function status($id)
{
throw new NotSupportedException('Status is not supported in the driver.');
}
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
if ($priority) {
throw new NotSupportedException('Priority is not supported in this driver');
}
$response = $this->getClient()->sendMessage([
'QueueUrl' => $this->url,
'MessageBody' => $message,
'DelaySeconds' => $delay,
'MessageAttributes' => [
'TTR' => [
'DataType' => 'Number',
'StringValue' => $ttr,
],
],
]);
return $response['MessageId'];
}
/**
* @return \Aws\Sqs\SqsClient
*/
protected function getClient()
{
if ($this->_client) {
return $this->_client;
}
if ($this->key !== null && $this->secret !== null) {
$credentials = [
'key' => $this->key,
'secret' => $this->secret,
];
} else {
// use default provider if no key and secret passed
//see - http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/credentials.html#credential-profiles
$credentials = CredentialProvider::defaultProvider();
}
$this->_client = new SqsClient([
'credentials' => $credentials,
'region' => $this->region,
'version' => $this->version,
]);
return $this->_client;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\sync;
use Yii;
use yii\base\Application;
use yii\base\InvalidArgumentException;
use yii\queue\Queue as BaseQueue;
/**
* Sync Queue.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Queue extends BaseQueue
{
/**
* @var bool
*/
public $handle = false;
/**
* @var array of payloads
*/
private $payloads = [];
/**
* @var int last pushed ID
*/
private $pushedId = 0;
/**
* @var int started ID
*/
private $startedId = 0;
/**
* @var int last finished ID
*/
private $finishedId = 0;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->handle) {
Yii::$app->on(Application::EVENT_AFTER_REQUEST, function () {
ob_start();
$this->run();
ob_end_clean();
});
}
}
/**
* Runs all jobs from queue.
*/
public function run()
{
while (($payload = array_shift($this->payloads)) !== null) {
list($ttr, $message) = $payload;
$this->startedId = $this->finishedId + 1;
$this->handleMessage($this->startedId, $message, $ttr, 1);
$this->finishedId = $this->startedId;
$this->startedId = 0;
}
}
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
array_push($this->payloads, [$ttr, $message]);
return ++$this->pushedId;
}
/**
* @inheritdoc
*/
public function status($id)
{
if (!is_int($id) || $id <= 0 || $id > $this->pushedId) {
throw new InvalidArgumentException("Unknown messages ID: $id.");
}
if ($id <= $this->finishedId) {
return self::STATUS_DONE;
}
if ($id === $this->startedId) {
return self::STATUS_RESERVED;
}
return self::STATUS_WAITING;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\gii;
use Yii;
use yii\base\BaseObject;
use yii\gii\CodeFile;
use yii\queue\JobInterface;
use yii\queue\RetryableJobInterface;
/**
* This generator will generate a job.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class Generator extends \yii\gii\Generator
{
public $jobClass;
public $properties;
public $retryable = false;
public $ns = 'app\jobs';
public $baseClass = BaseObject::class;
/**
* @inheritdoc
*/
public function getName()
{
return 'Job Generator';
}
/**
* @inheritdoc
*/
public function getDescription()
{
return 'This generator generates a Job class for the queue.';
}
/**
* @inheritdoc
*/
public function rules()
{
return array_merge(parent::rules(), [
[['jobClass', 'properties', 'ns', 'baseClass'], 'trim'],
[['jobClass', 'ns', 'baseClass'], 'required'],
['jobClass', 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'],
['jobClass', 'validateJobClass'],
['properties', 'match', 'pattern' => '/^[a-z_][a-z0-9_,\\s]*$/i', 'message' => 'Must be valid class properties.'],
['retryable', 'boolean'],
['ns', 'validateNamespace'],
['baseClass', 'validateClass'],
]);
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return array_merge(parent::attributeLabels(), [
'jobClass' => 'Job Class',
'properties' => 'Job Properties',
'retryable' => 'Retryable Job',
'ns' => 'Namespace',
'baseClass' => 'Base Class',
]);
}
/**
* @inheritdoc
*/
public function hints()
{
return array_merge(parent::hints(), [
'jobClass' => 'This is the name of the Job class to be generated, e.g., <code>SomeJob</code>.',
'properties' => 'Job object property names. Separate multiple properties with commas or spaces, e.g., <code>prop1, prop2</code>.',
'retryable' => 'Job object will implement <code>RetryableJobInterface</code> interface.',
'ns' => 'This is the namespace of the Job class to be generated.',
'baseClass' => 'This is the class that the new Job class will extend from.',
]);
}
/**
* @inheritdoc
*/
public function stickyAttributes()
{
return array_merge(parent::stickyAttributes(), ['ns', 'baseClass']);
}
/**
* @inheritdoc
*/
public function requiredTemplates()
{
return ['job.php'];
}
/**
* @inheritdoc
*/
public function generate()
{
$params = [];
$params['jobClass'] = $this->jobClass;
$params['ns'] = $this->ns;
$params['baseClass'] = '\\' . ltrim($this->baseClass, '\\');
$params['interfaces'] = [];
if (!$this->retryable) {
if (!is_a($this->baseClass, JobInterface::class, true)) {
$params['interfaces'][] = '\\' . JobInterface::class;
}
} else {
if (!is_a($this->baseClass, RetryableJobInterface::class, true)) {
$params['interfaces'][] = '\\' . RetryableJobInterface::class;
}
}
$params['properties'] = array_unique(preg_split('/[\s,]+/', $this->properties, -1, PREG_SPLIT_NO_EMPTY));
$jobFile = new CodeFile(
Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $this->jobClass . '.php',
$this->render('job.php', $params)
);
return [$jobFile];
}
/**
* Validates the job class.
*
* @param string $attribute job attribute name.
*/
public function validateJobClass($attribute)
{
if ($this->isReservedKeyword($this->$attribute)) {
$this->addError($attribute, 'Class name cannot be a reserved PHP keyword.');
}
}
/**
* Validates the namespace.
*
* @param string $attribute Namespace attribute name.
*/
public function validateNamespace($attribute)
{
$value = $this->$attribute;
$value = ltrim($value, '\\');
$path = Yii::getAlias('@' . str_replace('\\', '/', $value), false);
if ($path === false) {
$this->addError($attribute, 'Namespace must be associated with an existing directory.');
}
}
}
<?php
/**
* @var \yii\web\View $this
* @var \yii\queue\gii\Generator $generator
* @var string $jobClass
* @var string $$ns
* @var string $baseClass
* @var string[] $interfaces
* @var string[] $properties
*/
if ($interfaces) {
$implements = 'implements ' . implode(', ', $interfaces);
} else {
$implements = '';
}
echo "<?php\n";
?>
namespace <?= $ns ?>;
/**
* Class <?= $jobClass ?>.
*/
class <?= $jobClass ?> extends <?= $baseClass ?> <?= $implements ?>
{
<?php foreach ($properties as $property): ?>
public $<?= $property ?>;
<?php endforeach; ?>
/**
* @inheritdoc
*/
public function execute($queue)
{
}
<?php if ($generator->retryable): ?>
/**
* @inheritdoc
*/
public function getTtr()
{
return 60;
}
/**
* @inheritdoc
*/
public function canRetry($attempt, $error)
{
return $attempt < 3;
}
<?php endif; ?>
}
<?php
/**
* @var \yii\web\View $this
* @var \yii\widgets\ActiveForm $form
* @var \yii\queue\gii\Generator $generator
*/
?>
<?= $form->field($generator, 'jobClass')->textInput(['autofocus' => true]) ?>
<?= $form->field($generator, 'properties') ?>
<?= $form->field($generator, 'retryable')->checkbox() ?>
<?= $form->field($generator, 'ns') ?>
<?= $form->field($generator, 'baseClass') ?>
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\serializers;
use yii\base\BaseObject;
/**
* Igbinary Serializer.
*
* It uses an alternative serializer available via PECL extension which produces
* more compact data chunks significantly faster that native PHP one.
*
* @author xutl <xutongle@gmail.com>
*/
class IgbinarySerializer extends BaseObject implements SerializerInterface
{
/**
* @inheritdoc
*/
public function serialize($job)
{
return igbinary_serialize($job);
}
/**
* @inheritdoc
*/
public function unserialize($serialized)
{
return igbinary_unserialize($serialized);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\serializers;
use Yii;
use yii\base\BaseObject;
use yii\base\InvalidConfigException;
use yii\helpers\Json;
/**
* Json Serializer.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class JsonSerializer extends BaseObject implements SerializerInterface
{
/**
* @var string
*/
public $classKey = 'class';
/**
* @var int
*/
public $options = 0;
/**
* @inheritdoc
*/
public function serialize($job)
{
return Json::encode($this->toArray($job), $this->options);
}
/**
* @inheritdoc
*/
public function unserialize($serialized)
{
return $this->fromArray(Json::decode($serialized));
}
/**
* @param mixed $data
* @return array|mixed
* @throws InvalidConfigException
*/
protected function toArray($data)
{
if (is_object($data)) {
$result = [$this->classKey => get_class($data)];
foreach (get_object_vars($data) as $property => $value) {
if ($property === $this->classKey) {
throw new InvalidConfigException("Object cannot contain $this->classKey property.");
}
$result[$property] = $this->toArray($value);
}
return $result;
}
if (is_array($data)) {
$result = [];
foreach ($data as $key => $value) {
if ($key === $this->classKey) {
throw new InvalidConfigException("Array cannot contain $this->classKey key.");
}
$result[$key] = $this->toArray($value);
}
return $result;
}
return $data;
}
/**
* @param array $data
* @return mixed
*/
protected function fromArray($data)
{
if (!is_array($data)) {
return $data;
}
if (!isset($data[$this->classKey])) {
$result = [];
foreach ($data as $key => $value) {
$result[$key] = $this->fromArray($value);
}
return $result;
}
$config = ['class' => $data[$this->classKey]];
unset($data[$this->classKey]);
foreach ($data as $property => $value) {
$config[$property] = $this->fromArray($value);
}
return Yii::createObject($config);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\serializers;
use yii\base\BaseObject;
/**
* Php Serializer.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
class PhpSerializer extends BaseObject implements SerializerInterface
{
/**
* @inheritdoc
*/
public function serialize($job)
{
return serialize($job);
}
/**
* @inheritdoc
*/
public function unserialize($serialized)
{
return unserialize($serialized);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\serializers;
/**
* Interface Serializer.
*
* @deprecated Will be removed in 2.1.0. Use SerializerInterface instead of Serializer.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
interface Serializer extends SerializerInterface
{
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\queue\serializers;
use yii\queue\JobInterface;
/**
* Serializer Interface.
*
* @author Roman Zhuravlev <zhuravljov@gmail.com>
*/
interface SerializerInterface
{
/**
* @param JobInterface|mixed $job
* @return string
*/
public function serialize($job);
/**
* @param string $serialized
* @return JobInterface
*/
public function unserialize($serialized);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment