diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..2811e09 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,48 @@ +name: run-tests + +on: + push: + paths: + - '**.php' + - '.github/workflows/run-tests.yml' + - 'phpunit.xml.dist' + - 'composer.json' + - 'composer.lock' + +jobs: + test: + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + strategy: + fail-fast: true + matrix: + os: [ ubuntu-latest ] + php: [ 7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3 ] + stability: [ prefer-lowest, prefer-stable ] + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: xdebug + + - name: Setup problem matchers + run: | + echo "::add-matcher::${{ runner.tool_cache }}/php.json" + echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Install dependencies + run: | + composer update --${{ matrix.stability }} --prefer-dist --no-interaction + + - name: List Installed Dependencies + run: composer show -D + + - name: Execute tests + run: vendor/bin/pest --ci --coverage diff --git a/composer.json b/composer.json index 5464931..c070296 100755 --- a/composer.json +++ b/composer.json @@ -21,5 +21,15 @@ "psr-4": { "ParseHolyLandPhone\\": "src/" } + }, + "require-dev": { + "laravel/pint": "^1.17", + "pestphp/pest": "^2.35", + "phpstan/phpstan": "^1.11" + }, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + } } } diff --git a/example.php b/example.php index dd25170..4799d72 100644 --- a/example.php +++ b/example.php @@ -1,15 +1,15 @@ -isValid(); - -$number->isNotValid(); \ No newline at end of file +isValid(); + +$number->isNotValid(); diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..a46001a --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + + paths: + - src/ + + # Level 9 is the highest level + level: 5 diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..0c12bb9 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,17 @@ + + + + + ./tests + + + + + ./src + + + diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..f1f25ba --- /dev/null +++ b/pint.json @@ -0,0 +1,41 @@ +{ + "preset": "laravel", + "rules": { + "not_operator_with_successor_space": false, + "control_structure_braces": false, + "cast_spaces": { + "space": "none" + }, + "binary_operator_spaces": { + "operators": { + "=>": "align" + } + }, + "braces_position": { + "allow_single_line_anonymous_functions": true, + "allow_single_line_empty_anonymous_classes": true, + "functions_opening_brace": "same_line" + }, + "function_declaration": { + "closure_fn_spacing": "none" + }, + "concat_space": { + "spacing": "one" + }, + "phpdoc_align": { + "align": "vertical", + "tags": [] + }, + "phpdoc_line_span": { + "method": "single", + "property": "single", + "const": "single" + }, + "simplified_null_return": true, + "braces": false, + "new_with_braces": { + "anonymous_class": false, + "named_class": false + } + } +} diff --git a/src/ParsePhone.php b/src/ParsePhone.php index cce1352..ea78a17 100644 --- a/src/ParsePhone.php +++ b/src/ParsePhone.php @@ -15,172 +15,94 @@ * @method bool isNotTollFree() * @method bool isNotPremium() * @method bool isNotKosher() + * @method bool isNotSmsable() * @method bool isNotErotic() - * - * @package ParseHolyLandPhone */ class ParsePhone { - /** @var int|string */ + /** @var string */ protected $phoneNumber; - /** - * ParsePhone constructor. - * - * @param string|int $phoneNumber phone number to be parsed - */ - public function __construct($phoneNumber) - { + /** ParsePhone constructor. */ + public function __construct(string $phoneNumber) { $this->phoneNumber = $phoneNumber; $this->normalize(); } - /** - * @param string|int $phoneNumber phone number to be parsed - * - * @return static - */ - public static function create($phoneNumber) - { - return new static($phoneNumber); + /** @param string $phoneNumber phone number to be parsed */ + public static function create(string $phoneNumber): self { + return new self($phoneNumber); } - protected function normalize() - { + protected function normalize() { // Remove any non digits - $this->phoneNumber = preg_replace('/[^\d]/', '', $this->phoneNumber); + $this->phoneNumber = preg_replace('/\D/', '', $this->phoneNumber); // Assure is local format $this->phoneNumber = preg_replace('/^(972)(\d{8,9})$/', '0$2', $this->phoneNumber); } - /** - * Checks if phone number is a valid Israeli/Palestinian phone number - * - * @return bool - */ - public function isValid() - { + /** Checks if phone number is a valid Israeli/Palestinian phone number */ + public function isValid(): bool { return (bool)preg_match('/^((0[23489][2356789]|0[57][102345689]\d|1(2(00|12)|599|70[05]|80[019]|90[012]|919))\d{6}|\*\d{4})$/', $this->phoneNumber); } - /** - * Checks if phone number is Israeli - * - * @return bool - */ - public function isIsraeli() - { + /** Checks if phone number is Israeli */ + public function isIsraeli(): bool { return (bool)preg_match('/^((0[23489][356789]|0[57][1023458]\d|1(2(00|12)|599|70[05]|80[019]|90[012]|919))\d{6}|\*\d{4})$/', $this->phoneNumber); } - /** - * Checks if phone number is Palestinian - * - * @return bool - */ - public function isPalestinian() - { + /** Checks if phone number is Palestinian */ + public function isPalestinian(): bool { return (bool)preg_match('/^(0[23489]2|05[69]\d|)\d{6}$/', $this->phoneNumber); } - /** - * Checks if phone number is land line - * - * @return bool - */ - public function isLandLine() - { + /** Checks if phone number is landline */ + public function isLandLine(): bool { return (bool)preg_match('/^0([23489][2356789]|7\d{2})\d{6}$/', $this->phoneNumber); } - /** - * Checks if phone number is mobile - * - * @return bool - */ - public function isMobile() - { - return (bool)preg_match('/^0[5][102345689]\d{7}$/', $this->phoneNumber); + /** Checks if phone number is mobile */ + public function isMobile(): bool { + return (bool)preg_match('/^05[102345689]\d{7}$/', $this->phoneNumber); } - /** - * Checks if phone number is special (*1234) - * - * @return bool - */ - public function isSpecial() - { + /** Checks if phone number is special (*1234) */ + public function isSpecial(): bool { return (bool)preg_match('/^\*\d{4}$/', $this->phoneNumber); } - /** - * Checks if phone number is business (1800, etc) - * - * @return bool - */ - public function isBusiness() - { + /** Checks if phone number is business (1800, etc.) */ + public function isBusiness(): bool { return (bool)preg_match('/^1(2(00|12)|599|70[05]|80[019]|90[012]|919)\d{6}$/', $this->phoneNumber); } - /** - * Checks if phone number is toll free (1800) - * - * @return bool - */ - public function isTollFree() - { + /** Checks if phone number is toll-free (1800) */ + public function isTollFree(): bool { return (bool)preg_match('/^180[019]\d{6}$/', $this->phoneNumber); } - /** - * Checks if phone number is premium (1900) - * - * @return bool - */ - public function isPremium() - { + /** Checks if phone number is premium (1900) */ + public function isPremium(): bool { return (bool)preg_match('/^19(0[012]|19)\d{6}$/', $this->phoneNumber); } - /** - * Checks if phone number is Kosher (phone supports only calls) - * - * @return bool - */ - public function isKosher() - { + /** Checks if phone number is Kosher (phone supports only calls) */ + public function isKosher(): bool { return (bool)preg_match('/^0([23489]80|5041|5271|5276|5484|5485|5331|5341|5832|5567)\d{5}$/', $this->phoneNumber); } - - /** - * Checks if phone number can receive sms - * - * @return bool - */ - public function isSmsable() - { + /** Checks if phone number can receive sms */ + public function isSmsable(): bool { return $this->isNotKosher() && $this->isMobile(); } - - /** - * Checks if phone number is erotic (1919) - * - * @return bool - */ - public function isErotic() - { + /** Checks if phone number is erotic (1919) */ + public function isErotic(): bool { return (bool)preg_match('/^1919\d{6}$/', $this->phoneNumber); } - /** - * @return int|string - */ - public function getPhoneNumber() - { + public function getPhoneNumber(): string { return $this->phoneNumber; } @@ -189,41 +111,23 @@ public function getPhoneNumber() * 021231234 > 97221231234 * 0501231234 > 972501231234 */ - public function getInternational() - { + public function getInternational(): string { return preg_replace('/^(0)(\d{8,9})$/', '972$2', $this->phoneNumber); } - /** - * Returns the number in local format - * - * @return int|string - */ - public function getLocal() - { + /** Returns the number in local format */ + public function getLocal(): string { return $this->phoneNumber; } - /** - * isNot implementation - * - * @param $method - * @param $arguments - * - * @return bool - */ - public function __call($method, $arguments) - { - /** - * Check if starts with isNot - */ + /** isNot implementation */ + public function __call($method, $arguments): bool { + /** Check if starts with isNot */ if (substr($method, 0, 5) !== 'isNot') { trigger_error('Call to undefined method ' . __CLASS__ . '::' . $method . '()', E_USER_ERROR); } - /** - * Check if method exists - */ + /** Check if method exists */ $flippedMethod = 'is' . substr($method, 5); if (!method_exists($this, $flippedMethod)) { diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..87f792d --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,44 @@ +in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() { + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..cfb05b6 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +toBeInstanceOf(ParsePhone::class); +}); + +test('creates an instance via new', function () { + $number = new ParsePhone('025555555'); + expect($number)->toBeInstanceOf(ParsePhone::class); +}); + +test('removes any non digit from number', function () { + $number = new ParsePhone('02-(555)-5555'); + expect($number->getPhoneNumber())->toBe('025555555'); +}); + +test('converts to local format', function () { + $number = new ParsePhone('+972 2-(555)-5555'); + expect($number->getPhoneNumber())->toBe('025555555'); +}); + +test('has isNot methods for all is methods', function () { + $number = new ParsePhone('+972 2-(555)-5555'); + $isMethods = array_map( + function ($method) { return substr($method->getName(), 2); }, + array_filter( + (new ReflectionClass($number))->getMethods(ReflectionMethod::IS_PUBLIC), + function ($method) { return substr($method->getName(), 0, 2) === 'is'; } + ) + ); + + foreach ($isMethods as $method) { + expect($number->{'isNot' . $method}())->toBeBool(); + } +}); + +test('validates an valid israeli phone number as israeli', function () { + $number = ParsePhone::create('025555555'); + expect($number->isValid())->toBeTrue(); +}); + +// TODO add more tests