This commit is contained in:
Билай Станислав 2024-07-03 14:41:15 +03:00
commit ec69208f05
1892 changed files with 181728 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea

View File

@ -0,0 +1,22 @@
<?php
namespace Controllers;
use Models\Answer;
use Models\Upvote;
class Answers {
public static function add_answer($answer,$question_id,$user_id)
{
return Answer::create(['answer'=>$answer,'question_id'=>$question_id,'user_id'=>$user_id]);
}
public static function upvote_answer($answer_id,$user_id)
{
return Upvote::create(['answer_id'=>$answer_id,'user_id'=>$user_id]);
}
public static function update_answer($answer_id,$new_answer)
{
$answer = Answer::find($answer_id);
$answer->answer = $new_answer;
return $answer->save();
}
}

13
app/controllers/Posts.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace Controllers;
use Models\Post;
class Posts
{
public static function create_post($post, $user_id)
{
return Post::create(['post'=>$post, 'user_id'=>$user_id]);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Controllers;
use Models\Question;
class Questions{
public static function create_question($question,$user_id)
{
return Question::create(['question'=>$question,'user_id'=>$user_id]);
}
public static function get_questions_with_answers()
{
return Question::with('Answers')->get()->toArray();
}
public static function get_questions_with_users()
{
return Question::with('user')->get()->toArray();
}
public static function get_question_answers_upvotes($question_id)
{
return Question::find($question_id)->answers()->with('upvotes')->get()->toArray();
}
}

16
app/controllers/Users.php Normal file
View File

@ -0,0 +1,16 @@
<?php
namespace Controllers;
use Models\User;
use Models\Question;
class Users {
public static function create_user($username, $email, $password)
{
return User::create(['username'=>$username,'email'=>$email,'password'=>$password]);
}
public static function question_count($user_id)
{
return Question::where('user_id', $user_id)->count();
}
}

13
app/models/Answer.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace Models;
use \Illuminate\Database\Eloquent\Model;
class Answer extends Model {
protected $table = 'Answers';
protected $fillable = ['answer','user_id','question_id'];
public function upvotes()
{
return $this->hasMany('\Models\Upvote');
}
}

22
app/models/Database.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace Models;
use Illuminate\Database\Capsule\Manager as Capsule;
class Database {
public function __construct()
{
$capsule = new Capsule;
$capsule->addConnection([
'driver' => DBDRIVER,
'host' => DBHOST,
'database' => DBNAME,
'username' => DBUSER,
'password' => DBPASS,
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
]);
// Setup the Eloquent ORM…
$capsule->bootEloquent();
}
}

9
app/models/Post.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace Models;
use \Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $table = 'Posts';
protected $fillable = ['post', 'user_id'];
}

19
app/models/Question.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace Models;
use \Illuminate\Database\Eloquent\Model;
class Question extends Model {
protected $table = 'Questions';
protected $fillable = ['question','user_id'];
public function answers()
{
return $this->hasMany('\Models\Answer');
}
public function user()
{
return $this->belongsTo('\Models\User');
}
}

10
app/models/Upvote.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace Models;
use \Illuminate\Database\Eloquent\Model;
class Upvote extends Model {
protected $table = 'Upvotes';
protected $fillable = ['answer_id', 'user_id'];
}

8
app/models/User.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace Models;
use \Illuminate\Database\Eloquent\Model;
class User extends Model {
protected $table = 'Users';
protected $fillable = ['username', 'email', 'password'];
}

1
app/views/intro.php Normal file
View File

@ -0,0 +1 @@
<?php

6
bootstrap.php Normal file
View File

@ -0,0 +1,6 @@
<?php
require './config.php';
require './vendor/autoload.php';
use Models\Database;
// initialize Illuminate database connection
new Database();

15
composer.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "illuminate-example/eloquent",
"description": "Implementation of Database Queries with illuminate and Eloquent",
"type": "project",
"require": {
"illuminate/database": "^7.30",
"craft-group/phroute": "^2.1"
},
"autoload": {
"psr-4": {
"Controllers\\": "app/controllers/",
"Models\\": "app/models/"
}
}
}

1767
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

6
config.php Normal file
View File

@ -0,0 +1,6 @@
<?php
defined('DBDRIVER') or define('DBDRIVER','mysql');
defined('DBHOST') or define('DBHOST','localhost');
defined('DBNAME') or define('DBNAME','Test');
defined('DBUSER') or define('DBUSER','test');
defined('DBPASS') or define('DBPASS','root');

26
index.php Normal file
View File

@ -0,0 +1,26 @@
<?php
require_once "vendor/autoload.php";
require 'bootstrap.php';
use Controllers\Users;
use Controllers\Questions;
use Controllers\Answers;
use Controllers\Posts;
use Phroute\Phroute\RouteCollector;
//$user = Users::create_user("user1", "user1@example.com", "user1_pass");
//$question = Questions::create_question("Каков смысл жизни?", 7);
//$answers = Answers::add_answer("Не знаю!", 2, 3);
//$answers = Answers::add_answer("Плохо", 1, 2);
//$upvote = Answers::upvote_answer(35, 7);
//$all = Questions::get_questions_with_answers();
//$post = Posts::create_post("что-то здесь явно написано!", 5);
//$all_with_users = Questions::get_questions_with_users();
//$one_question = Questions::get_question_answers_upvotes(2);
//$user_question_count = Users::question_count(7);
$update_answer = Answers::update_answer(1, "This is an updated answer");
echo "<pre>";
print_r($update_answer);
$router = new RouteCollector();

25
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitfe2a84e2cd498dd89c8e792659f7a55c::getLoader();

119
vendor/bin/carbon vendored Executable file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../nesbot/carbon/bin/carbon)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
}
}
return include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Carbon
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.

View File

@ -0,0 +1,14 @@
# carbonphp/carbon-doctrine-types
Types to use Carbon in Doctrine
## Documentation
[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/symfony/)
This package is an externalization of [src/Carbon/Doctrine](https://github.com/briannesbitt/Carbon/tree/2.71.0/src/Carbon/Doctrine)
from `nestbot/carbon` package.
Externalization allows to better deal with different versions of dbal. With
version 4.0 of dbal, it no longer sustainable to be compatible with all version
using a single code.

View File

@ -0,0 +1,36 @@
{
"name": "carbonphp/carbon-doctrine-types",
"description": "Types to use Carbon in Doctrine",
"type": "library",
"keywords": [
"date",
"time",
"DateTime",
"Carbon",
"Doctrine"
],
"require": {
"php": "^8.1"
},
"require-dev": {
"doctrine/dbal": "^4.0.0",
"nesbot/carbon": "^2.71.0 || ^3.0.0",
"phpunit/phpunit": "^10.3"
},
"conflict": {
"doctrine/dbal": "<4.0.0 || >=5.0.0"
},
"license": "MIT",
"autoload": {
"psr-4": {
"Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
}
},
"authors": [
{
"name": "KyleKatarn",
"email": "kylekatarnls@gmail.com"
}
],
"minimum-stability": "dev"
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
use Doctrine\DBAL\Platforms\AbstractPlatform;
interface CarbonDoctrineType
{
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform);
public function convertToPHPValue(mixed $value, AbstractPlatform $platform);
public function convertToDatabaseValue($value, AbstractPlatform $platform);
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
class CarbonImmutableType extends DateTimeImmutableType implements CarbonDoctrineType
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
class CarbonType extends DateTimeType implements CarbonDoctrineType
{
}

View File

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use DateTimeInterface;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Types\Exception\InvalidType;
use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
use Exception;
/**
* @template T of CarbonInterface
*/
trait CarbonTypeConverter
{
/**
* This property differentiates types installed by carbonphp/carbon-doctrine-types
* from the ones embedded previously in nesbot/carbon source directly.
*
* @readonly
*/
public bool $external = true;
/**
* @return class-string<T>
*/
protected function getCarbonClassName(): string
{
return Carbon::class;
}
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
$precision = min(
$fieldDeclaration['precision'] ?? DateTimeDefaultPrecision::get(),
$this->getMaximumPrecision($platform),
);
$type = parent::getSQLDeclaration($fieldDeclaration, $platform);
if (!$precision) {
return $type;
}
if (str_contains($type, '(')) {
return preg_replace('/\(\d+\)/', "($precision)", $type);
}
[$before, $after] = explode(' ', "$type ");
return trim("$before($precision) $after");
}
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if ($value === null) {
return $value;
}
if ($value instanceof DateTimeInterface) {
return $value->format('Y-m-d H:i:s.u');
}
throw InvalidType::new(
$value,
static::class,
['null', 'DateTime', 'Carbon']
);
}
private function doConvertToPHPValue(mixed $value)
{
$class = $this->getCarbonClassName();
if ($value === null || is_a($value, $class)) {
return $value;
}
if ($value instanceof DateTimeInterface) {
return $class::instance($value);
}
$date = null;
$error = null;
try {
$date = $class::parse($value);
} catch (Exception $exception) {
$error = $exception;
}
if (!$date) {
throw ValueNotConvertible::new(
$value,
static::class,
'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()',
$error
);
}
return $date;
}
private function getMaximumPrecision(AbstractPlatform $platform): int
{
if ($platform instanceof DB2Platform) {
return 12;
}
if ($platform instanceof OraclePlatform) {
return 9;
}
if ($platform instanceof SQLServerPlatform || $platform instanceof SQLitePlatform) {
return 3;
}
return 6;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
class DateTimeDefaultPrecision
{
private static $precision = 6;
/**
* Change the default Doctrine datetime and datetime_immutable precision.
*
* @param int $precision
*/
public static function set(int $precision): void
{
self::$precision = $precision;
}
/**
* Get the default Doctrine datetime and datetime_immutable precision.
*
* @return int
*/
public static function get(): int
{
return self::$precision;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
use Carbon\CarbonImmutable;
use DateTimeImmutable;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\VarDateTimeImmutableType;
class DateTimeImmutableType extends VarDateTimeImmutableType implements CarbonDoctrineType
{
/** @use CarbonTypeConverter<CarbonImmutable> */
use CarbonTypeConverter;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonImmutable
{
return $this->doConvertToPHPValue($value);
}
/**
* @return class-string<CarbonImmutable>
*/
protected function getCarbonClassName(): string
{
return CarbonImmutable::class;
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
use Carbon\Carbon;
use DateTime;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\VarDateTimeType;
class DateTimeType extends VarDateTimeType implements CarbonDoctrineType
{
/** @use CarbonTypeConverter<Carbon> */
use CarbonTypeConverter;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Carbon
{
return $this->doConvertToPHPValue($value);
}
}

579
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

359
vendor/composer/InstalledVersions.php vendored Normal file
View File

@ -0,0 +1,359 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}

21
vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
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.

17
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,17 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);

19
vendor/composer/autoload_files.php vendored Normal file
View File

@ -0,0 +1,19 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'72579e7bd17821bb1321b87411366eae' => $vendorDir . '/illuminate/support/helpers.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

34
vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,34 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
'Phroute\\Phroute\\' => array($vendorDir . '/craft-group/phroute/src/Phroute'),
'Models\\' => array($baseDir . '/app/models'),
'Illuminate\\Support\\' => array($vendorDir . '/illuminate/support'),
'Illuminate\\Database\\' => array($vendorDir . '/illuminate/database'),
'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'),
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'),
'Controllers\\' => array($baseDir . '/app/controllers'),
'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'),
'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'),
);

50
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,50 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitfe2a84e2cd498dd89c8e792659f7a55c
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitfe2a84e2cd498dd89c8e792659f7a55c', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInitfe2a84e2cd498dd89c8e792659f7a55c', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitfe2a84e2cd498dd89c8e792659f7a55c::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInitfe2a84e2cd498dd89c8e792659f7a55c::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}

194
vendor/composer/autoload_static.php vendored Normal file
View File

@ -0,0 +1,194 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitfe2a84e2cd498dd89c8e792659f7a55c
{
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'72579e7bd17821bb1321b87411366eae' => __DIR__ . '/..' . '/illuminate/support/helpers.php',
);
public static $prefixLengthsPsr4 = array (
'v' =>
array (
'voku\\' => 5,
),
'S' =>
array (
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Php73\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Contracts\\Translation\\' => 30,
'Symfony\\Contracts\\Service\\' => 26,
'Symfony\\Component\\Translation\\' => 30,
'Symfony\\Component\\String\\' => 25,
'Symfony\\Component\\Console\\' => 26,
),
'P' =>
array (
'Psr\\SimpleCache\\' => 16,
'Psr\\Container\\' => 14,
'Psr\\Clock\\' => 10,
'Phroute\\Phroute\\' => 16,
),
'M' =>
array (
'Models\\' => 7,
),
'I' =>
array (
'Illuminate\\Support\\' => 19,
'Illuminate\\Database\\' => 20,
'Illuminate\\Contracts\\' => 21,
'Illuminate\\Container\\' => 21,
),
'D' =>
array (
'Doctrine\\Inflector\\' => 19,
),
'C' =>
array (
'Controllers\\' => 12,
'Carbon\\Doctrine\\' => 16,
'Carbon\\' => 7,
),
);
public static $prefixDirsPsr4 = array (
'voku\\' =>
array (
0 => __DIR__ . '/..' . '/voku/portable-ascii/src/voku',
),
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Php73\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
),
'Symfony\\Polyfill\\Intl\\Grapheme\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/translation-contracts',
),
'Symfony\\Contracts\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/service-contracts',
),
'Symfony\\Component\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/translation',
),
'Symfony\\Component\\String\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/string',
),
'Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'Psr\\SimpleCache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'Psr\\Clock\\' =>
array (
0 => __DIR__ . '/..' . '/psr/clock/src',
),
'Phroute\\Phroute\\' =>
array (
0 => __DIR__ . '/..' . '/craft-group/phroute/src/Phroute',
),
'Models\\' =>
array (
0 => __DIR__ . '/../..' . '/app/models',
),
'Illuminate\\Support\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/support',
),
'Illuminate\\Database\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/database',
),
'Illuminate\\Contracts\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/contracts',
),
'Illuminate\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/container',
),
'Doctrine\\Inflector\\' =>
array (
0 => __DIR__ . '/..' . '/doctrine/inflector/lib/Doctrine/Inflector',
),
'Controllers\\' =>
array (
0 => __DIR__ . '/../..' . '/app/controllers',
),
'Carbon\\Doctrine\\' =>
array (
0 => __DIR__ . '/..' . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine',
),
'Carbon\\' =>
array (
0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon',
),
);
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitfe2a84e2cd498dd89c8e792659f7a55c::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitfe2a84e2cd498dd89c8e792659f7a55c::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitfe2a84e2cd498dd89c8e792659f7a55c::$classMap;
}, null, ClassLoader::class);
}
}

1826
vendor/composer/installed.json vendored Normal file

File diff suppressed because it is too large Load Diff

263
vendor/composer/installed.php vendored Normal file
View File

@ -0,0 +1,263 @@
<?php return array(
'root' => array(
'name' => 'illuminate-example/eloquent',
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'carbonphp/carbon-doctrine-types' => array(
'pretty_version' => '3.2.0',
'version' => '3.2.0.0',
'reference' => '18ba5ddfec8976260ead6e866180bd5d2f71aa1d',
'type' => 'library',
'install_path' => __DIR__ . '/../carbonphp/carbon-doctrine-types',
'aliases' => array(),
'dev_requirement' => false,
),
'craft-group/phroute' => array(
'pretty_version' => 'v2.1.2',
'version' => '2.1.2.0',
'reference' => '49180faae85e0ba3b0165901ad46e08508c81fac',
'type' => 'library',
'install_path' => __DIR__ . '/../craft-group/phroute',
'aliases' => array(),
'dev_requirement' => false,
),
'doctrine/inflector' => array(
'pretty_version' => '2.0.10',
'version' => '2.0.10.0',
'reference' => '5817d0659c5b50c9b950feb9af7b9668e2c436bc',
'type' => 'library',
'install_path' => __DIR__ . '/../doctrine/inflector',
'aliases' => array(),
'dev_requirement' => false,
),
'illuminate-example/eloquent' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'reference' => null,
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'illuminate/container' => array(
'pretty_version' => 'v7.30.6',
'version' => '7.30.6.0',
'reference' => '06456a2ea5656c2f1ebda37039ce14c1bfc973b3',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/container',
'aliases' => array(),
'dev_requirement' => false,
),
'illuminate/contracts' => array(
'pretty_version' => 'v7.30.6',
'version' => '7.30.6.0',
'reference' => '2449f2ea949ddf995a3dcffe5e21c768cf7d6478',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'illuminate/database' => array(
'pretty_version' => 'v7.30.6',
'version' => '7.30.6.0',
'reference' => 'e26b023f23c08968950470189e108e30f2e3b7ba',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/database',
'aliases' => array(),
'dev_requirement' => false,
),
'illuminate/support' => array(
'pretty_version' => 'v7.30.6',
'version' => '7.30.6.0',
'reference' => 'c7b42acd009c94a3f8b749a65f6835db90174d58',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/support',
'aliases' => array(),
'dev_requirement' => false,
),
'nesbot/carbon' => array(
'pretty_version' => '2.72.5',
'version' => '2.72.5.0',
'reference' => 'afd46589c216118ecd48ff2b95d77596af1e57ed',
'type' => 'library',
'install_path' => __DIR__ . '/../nesbot/carbon',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/clock' => array(
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/clock',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/clock-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/container' => array(
'pretty_version' => '1.1.2',
'version' => '1.1.2.0',
'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/container-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/log-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0|2.0',
),
),
'psr/simple-cache' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/simple-cache',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/console' => array(
'pretty_version' => 'v5.4.41',
'version' => '5.4.41.0',
'reference' => '6473d441a913cb997123b59ff2dbe3d1cf9e11ba',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.5.0',
'version' => '3.5.0.0',
'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.30.0',
'version' => '1.30.0.0',
'reference' => '0424dff1c58f028c451efff2045f5d92410bd540',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-intl-grapheme' => array(
'pretty_version' => 'v1.30.0',
'version' => '1.30.0.0',
'reference' => '64647a7c30b2283f5d49b874d84a18fc22054b7a',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.30.0',
'version' => '1.30.0.0',
'reference' => 'a95281b0be0d9ab48050ebd988b967875cdb9fdb',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.30.0',
'version' => '1.30.0.0',
'reference' => 'fd22ab50000ef01661e2a31d850ebaa297f8e03c',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php73' => array(
'pretty_version' => 'v1.30.0',
'version' => '1.30.0.0',
'reference' => 'ec444d3f3f6505bb28d11afa41e75faadebc10a1',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php73',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.30.0',
'version' => '1.30.0.0',
'reference' => '77fa7995ac1b21ab60769b7323d600a991a90433',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/service-contracts' => array(
'pretty_version' => 'v3.5.0',
'version' => '3.5.0.0',
'reference' => 'bd1d9e59a81d8fa4acdcea3f617c581f7475a80f',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/service-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/string' => array(
'pretty_version' => 'v6.4.9',
'version' => '6.4.9.0',
'reference' => '76792dbd99690a5ebef8050d9206c60c59e681d7',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/string',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/translation' => array(
'pretty_version' => 'v6.4.8',
'version' => '6.4.8.0',
'reference' => 'a002933b13989fc4bd0b58e04bf7eec5210e438a',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/translation-contracts' => array(
'pretty_version' => 'v3.5.0',
'version' => '3.5.0.0',
'reference' => 'b9d2189887bb6b2e0367a9fc7136c5239ab9b05a',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/translation-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '2.3|3.0',
),
),
'voku/portable-ascii' => array(
'pretty_version' => '1.6.1',
'version' => '1.6.1.0',
'reference' => '87337c91b9dfacee02452244ee14ab3c43bc485a',
'type' => 'library',
'install_path' => __DIR__ . '/../voku/portable-ascii',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

26
vendor/composer/platform_check.php vendored Normal file
View File

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

3
vendor/craft-group/phroute/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
vendor
composer.lock
.idea

17
vendor/craft-group/phroute/.travis.yml vendored Normal file
View File

@ -0,0 +1,17 @@
language: php
sudo: false
php:
- 5.6
- 7.0
- 7.1
before_script:
- composer install --dev
script:
- mkdir -p build/logs
- php vendor/bin/phpunit --coverage-clover build/logs/clover.xml
after_script:
- php vendor/bin/coveralls -v

31
vendor/craft-group/phroute/LICENSE vendored Normal file
View File

@ -0,0 +1,31 @@
Copyright (c) 2013 by Nikita Popov.
Some 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.
* The names of the contributors may not 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.

592
vendor/craft-group/phroute/README.md vendored Normal file
View File

@ -0,0 +1,592 @@
PHRoute - Fast request router for PHP
=======================================
[![Build Status](https://travis-ci.org/mrjgreen/phroute.svg)](https://travis-ci.org/mrjgreen/phroute)
[![Coverage Status](https://coveralls.io/repos/github/mrjgreen/phroute/badge.svg)](https://coveralls.io/github/mrjgreen/phroute)
[![Latest Stable Version](https://poser.pugx.org/phroute/phroute/v/stable)](https://packagist.org/packages/phroute/phroute)
[![License](https://poser.pugx.org/phroute/phroute/license)](https://packagist.org/packages/phroute/phroute)
[![Total Downloads](https://poser.pugx.org/phroute/phroute/downloads)](https://packagist.org/packages/phroute/phroute)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/92fb42d3-5254-4b3e-8a84-69d535941465/mini.png)](https://insight.sensiolabs.com/projects/92fb42d3-5254-4b3e-8a84-69d535941465)
## This library provides a fast implementation of a regular expression based router.
* [Super fast](#performance)
* [Route parameters and optional route parameters](#defining-routes)
* [Dependency Injection Resolving (Integrates easily with 3rd parties eg. Orno/Di)](#dependency-injection)
* [Named routes and reverse routing](#named-routes-for-reverse-routing)
* [Restful controller routing](#controllers)
* [Route filters and filter groups](#filters)
* [Route prefix groups](#prefix-groups)
### Credit to nikic/FastRoute.
While the bulk of the library and extensive unit tests are my own, credit for the regex matching core implementation and benchmarking goes to [nikic](https://github.com/nikic/FastRoute). Please go and read nikic's
[blog post explaining how the implementation works and why it's fast.](http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html)
Many modifications to the core have been made to suit the new library wrapper, and additional features added such as optional route parameters and reverse routing etc, but please head over and checkout nikic's library to see the origins of the core and how it works.
Installation
------------
Install via composer
```
composer require phroute/phroute
```
Usage
-----
### Example
~~~PHP
$router->get('/example', function(){
return 'This route responds to requests with the GET method at the path /example';
});
$router->post('/example/{id}', function($id){
return 'This route responds to requests with the POST method at the path /example/1234. It passes in the parameter as a function argument.';
});
$router->any('/example', function(){
return 'This route responds to any method (POST, GET, DELETE, OPTIONS, HEAD etc...) at the path /example';
});
~~~
### Defining routes
~~~PHP
use Phroute\Phroute\RouteCollector;
$router = new RouteCollector();
$router->get($route, $handler); # match only get requests
$router->post($route, $handler); # match only post requests
$router->delete($route, $handler); # match only delete requests
$router->any($route, $handler); # match any request method
etc...
~~~
> These helper methods are wrappers around `addRoute($method, $route, $handler)`
This method accepts the HTTP method the route must match, the route pattern and a callable handler, which can be a closure, function name or `['ClassName', 'method']` pair.
The methods also accept an additional parameter which is an array of middlewares: currently filters `before` and `after`, and route prefixing with `prefix` are supported. See the sections on Filters and Prefixes for more info and examples.
By default a route pattern syntax is used where `{foo}` specifies a placeholder with name `foo`
and matching the string `[^/]+`. To adjust the pattern the placeholder matches, you can specify
a custom pattern by writing `{bar:[0-9]+}`. However, it is also possible to adjust the pattern
syntax by passing a custom route parser to the router at construction.
```php
$router->any('/example', function(){
return 'This route responds to any method (POST, GET, DELETE etc...) at the URI /example';
});
// or '/page/{id:i}' (see shortcuts)
$router->post('/page/{id:\d+}', function($id){
// $id contains the url paramter
return 'This route responds to the post method at the URI /page/{param} where param is at least one number';
});
$router->any('/', function(){
return 'This responds to the default route';
});
// Lazy load autoloaded route handling classes using strings for classnames
// Calls the Controllers\User::displayUser($id) method with {id} parameter as an argument
$router->any('/users/{id}', ['Controllers\User','displayUser']);
// Optional Parameters
// simply add a '?' after the route name to make the parameter optional
// NB. be sure to add a default value for the function argument
$router->get('/user/{id}?', function($id = null) {
return 'second';
});
# NB. You can cache the return value from $router->getData() so you don't have to create the routes each request - massive speed gains
$dispatcher = new Phroute\Phroute\Dispatcher($router->getData());
$response = $dispatcher->dispatch($_SERVER['REQUEST_METHOD'], parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
// Print out the value returned from the dispatched function
echo $response;
```
### Regex Shortcuts
```
:i => :/d+ # numbers only
:a => :[a-zA-Z0-9]+ # alphanumeric
:c => :[a-zA-Z0-9+_\-\.]+ # alnumnumeric and + _ - . characters
:h => :[a-fA-F0-9]+ # hex
use in routes:
'/user/{name:i}'
'/user/{name:a}'
```
###Named Routes for Reverse Routing
Pass in an array as the first argument, where the first item is your route and the second item is a name with which to reference it later.
```php
$router->get(['/user/{name}', 'username'], function($name){
return 'Hello ' . $name;
})
->get(['/page/{slug}/{id:\d+}', 'page'], function($id){
return 'You must be authenticated to see this page: ' . $id;
});
// Use the routename and pass in any route parameters to reverse engineer an existing route path
// If you change your route path above, you won't need to go through your code updating any links/references to that route
$router->route('username', 'joe');
// string(9) '/user/joe'
$router->route('page', ['intro', 456]);
// string(15) '/page/intro/456'
```
###Filters
```php
$router->filter('statsStart', function(){
setPageStartTime(microtime(true));
});
$router->filter('statsComplete', function(){
var_dump('Page load time: ' . (microtime(true) - getPageStartTime()));
});
$router->get('/user/{name}', function($name){
return 'Hello ' . $name;
}, ['before' => 'statsStart', 'after' => 'statsComplete']);
```
###Filter Groups
Wrap multiple routes in a route group to apply that filter to every route defined within. You can nest route groups if required.
```php
// Any thing other than null returned from a filter will prevent the route handler from being dispatched
$router->filter('auth', function(){
if(!isset($_SESSION['user']))
{
header('Location: /login');
return false;
}
});
$router->group(['before' => 'auth'], function($router){
$router->get('/user/{name}', function($name){
return 'Hello ' . $name;
})
->get('/page/{id:\d+}', function($id){
return 'You must be authenticated to see this page: ' . $id;
});
});
```
###Prefix Groups
```php
// You can combine a prefix with a filter, eg. `['prefix' => 'admin', 'before' => 'auth']`
$router->group(['prefix' => 'admin'], function($router){
$router->get('pages', function(){
return 'page management';
});
$router->get('products', function(){
return 'product management';
});
$router->get('orders', function(){
return 'order management';
});
});
```
###Controllers
```php
namespace MyApp;
class Test {
public function anyIndex()
{
return 'This is the default page and will respond to /controller and /controller/index';
}
/**
* One required paramter and one optional parameter
*/
public function anyTest($param, $param2 = 'default')
{
return 'This will respond to /controller/test/{param}/{param2}? with any method';
}
public function getTest()
{
return 'This will respond to /controller/test with only a GET method';
}
public function postTest()
{
return 'This will respond to /controller/test with only a POST method';
}
public function putTest()
{
return 'This will respond to /controller/test with only a PUT method';
}
public function deleteTest()
{
return 'This will respond to /controller/test with only a DELETE method';
}
}
$router->controller('/controller', 'MyApp\\Test');
// Controller with associated filter
$router->controller('/controller', 'MyApp\\Test', ['before' => 'auth']);
```
### Dispatching a URI
A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method
accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them
appropriately) is your job - this library is not bound to the PHP web SAPIs.
$response = (new Phroute\Phroute\Dispatcher($router))
->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
The `dispatch()` method will call the matched route, or if no matches, throw one of the exceptions below:
# Route not found
Phroute\Phroute\Exception\HttpRouteNotFoundException;
# Route found, but method not allowed
Phroute\Phroute\Exception\HttpMethodNotAllowedException;
> **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the
`Allow:` header to detail available methods for the requested resource.
This information can be obtained from the thrown exception's message content:
which will look like: `"Allow: HEAD, GET, POST"` etc... depending on the methods you have set
You should catch the exception and use this to send a header to the client: `header($e->getMessage());`
###Dependency Injection
Defining your own dependency resolver is simple and easy. The router will attempt to resolve filters,
and route handlers via the dependency resolver.
The example below shows how you can define your own resolver to integrate with orno/di,
but pimple/pimple or others will work just as well.
~~~PHP
use Orno\Di\Container;
use Phroute\Phroute\HandlerResolverInterface;
class RouterResolver implements HandlerResolverInterface
{
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
public function resolve($handler)
{
/*
* Only attempt resolve uninstantiated objects which will be in the form:
*
* $handler = ['App\Controllers\Home', 'method'];
*/
if(is_array($handler) and is_string($handler[0]))
{
$handler[0] = $this->container[$handler[0]];
}
return $handler;
}
}
~~~
When you create your dispatcher:
~~~PHP
$appContainer = new Orno\Di;
// Attach your controllers as normal
// $appContainer->add('App\Controllers\Home')
$resolver = new RouterResolver($appContainer);
$response = (new Phroute\Phroute\Dispatcher($router, $resolver))->dispatch($requestMethod, $requestUri);
~~~
### A Note on HEAD Requests
The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]:
> The methods GET and HEAD MUST be supported by all general-purpose servers
To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an
available GET route for a given resource. The PHP web SAPI transparently removes the entity body
from HEAD responses so this behavior has no effect on the vast majority of users.
However, implementors using Phroute outside the web SAPI environment (e.g. a custom server) MUST
NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is
*your responsibility*; Phroute has no purview to prevent you from breaking HTTP in such cases.
Finally, note that applications MAY always specify their own HEAD method route for a given
resource to bypass this behavior entirely.
### Performance
Performed on a machine with :
* Processor 2.3 GHz Intel Core i7
* Memory 8 GB 1600 MHz DDR3
####Phroute
This test is to illustrate, in part, the efficiency of the lightweight routing-core, but mostly the lack of degradation of matching speed as the number of routes grows, as compared to conventional libraries.
##### With 10 routes, matching 1st route (best case)
~~~~
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:9943/
Finished 1000 requests
Time taken for tests: 3.062 seconds
Requests per second: 326.60 [#/sec] (mean)
Time per request: 306.181 [ms] (mean)
Time per request: 3.062 [ms] (mean, across all concurrent requests)
Transfer rate: 37.32 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 306
66% 307
75% 307
80% 308
90% 309
95% 309
98% 310
99% 310
100% 310 (longest request)
~~~~
##### With 10 routes, matching last route (worst case)
Note that the match is just as quick as against the first route
~~~
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:9943/thelastroute
Finished 1000 requests
Time taken for tests: 3.079 seconds
Requests per second: 324.80 [#/sec] (mean)
Time per request: 307.880 [ms] (mean)
Time per request: 3.079 [ms] (mean, across all concurrent requests)
Transfer rate: 37.11 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 307
66% 308
75% 309
80% 309
90% 310
95% 311
98% 312
99% 312
100% 313 (longest request)
~~~
##### With 100 routes, matching last route (worst case)
~~~
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:9943/thelastroute
Finished 1000 requests
Time taken for tests: 3.195 seconds
Requests per second: 312.97 [#/sec] (mean)
Time per request: 319.515 [ms] (mean)
Time per request: 3.195 [ms] (mean, across all concurrent requests)
Transfer rate: 35.76 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 318
66% 319
75% 320
80% 320
90% 322
95% 323
98% 323
99% 324
100% 324 (longest request)
~~~
##### With 1000 routes, matching the last route (worst case)
~~~
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:9943/thelastroute
Finished 1000 requests
Time taken for tests: 4.497 seconds
Complete requests: 1000
Requests per second: 222.39 [#/sec] (mean)
Time per request: 449.668 [ms] (mean)
Time per request: 4.497 [ms] (mean, across all concurrent requests)
Transfer rate: 25.41 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 445
66% 447
75% 448
80% 449
90% 454
95% 456
98% 457
99% 458
100% 478 (longest request)
~~~
###For comparison, Laravel 4.0 routing core
Please note, this is no slight against laravel - it is based on a routing loop, which is why the performance worsens as the number of routes grows
##### With 10 routes, matching first route (best case)
~~~
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:4968/
Finished 1000 requests
Time taken for tests: 13.366 seconds
Requests per second: 74.82 [#/sec] (mean)
Time per request: 1336.628 [ms] (mean)
Time per request: 13.366 [ms] (mean, across all concurrent requests)
Transfer rate: 8.55 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 1336
66% 1339
75% 1340
80% 1341
90% 1346
95% 1348
98% 1349
99% 1351
100% 1353 (longest request)
~~~
##### With 10 routes, matching last route (worst case)
~~~
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:4968/thelastroute
Finished 1000 requests
Time taken for tests: 14.621 seconds
Requests per second: 68.39 [#/sec] (mean)
Time per request: 1462.117 [ms] (mean)
Time per request: 14.621 [ms] (mean, across all concurrent requests)
Transfer rate: 7.81 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 1461
66% 1465
75% 1469
80% 1472
90% 1476
95% 1479
98% 1480
99% 1482
100% 1484 (longest request)
~~~
##### With 100 routes, matching last route (worst case)
~~~
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:4968/thelastroute
Finished 1000 requests
Time taken for tests: 31.254 seconds
Requests per second: 32.00 [#/sec] (mean)
Time per request: 3125.402 [ms] (mean)
Time per request: 31.254 [ms] (mean, across all concurrent requests)
Transfer rate: 3.66 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 3124
66% 3145
75% 3154
80% 3163
90% 3188
95% 3219
98% 3232
99% 3236
100% 3241 (longest request)
~~~
##### With 1000 routes, matching last route (worst case)
~~~
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:5740/thelastroute
Finished 1000 requests
Time taken for tests: 197.366 seconds
Requests per second: 5.07 [#/sec] (mean)
Time per request: 19736.598 [ms] (mean)
Time per request: 197.366 [ms] (mean, across all concurrent requests)
Transfer rate: 0.58 [Kbytes/sec] received
Percentage of the requests served within a certain time (ms)
50% 19736
66% 19802
75% 19827
80% 19855
90% 19898
95% 19918
98% 19945
99% 19960
100% 19975 (longest request)
~~~

View File

@ -0,0 +1,54 @@
<?php
include __DIR__ . '/../vendor/autoload.php';
$collector = new Phroute\Phroute\RouteCollector();
$collector->get('/test', function(){
});
$collector->get('/test2', function(){
});
$collector->get('/test3', function(){
});
$collector->get('/test1/{name}', function(){
});
$collector->get('/test2/{name2}', function(){
});
$collector->get('/test3/{name3}', function(){
});
$dispatcher = new Phroute\Phroute\Dispatcher($collector->getData());
$runTime = 10;
$time = microtime(true);
$count = 0;
$seconds = 0;
while($seconds < $runTime)
{
$count++;
$dispatcher->dispatch('GET', '/test2/joe');
if($time + 1 < microtime(true))
{
$time = microtime(true);
$seconds++;
echo $count . ' routes dispatched per second' . "\r";
$count = 0;
}
}
echo PHP_EOL;

View File

@ -0,0 +1,24 @@
{
"name": "craft-group/phroute",
"description": "Fast, fully featured restful request router for PHP",
"keywords": ["routing", "router"],
"license": "BSD-3-Clause",
"authors": [
{
"name": "Kavalar",
"email": "apuc06@mail.ru"
}
],
"autoload": {
"psr-4": {
"Phroute\\Phroute\\": "src/Phroute"
}
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"satooshi/php-coveralls": "^1.0",
"phpunit/phpunit": "^5.0"
}
}

View File

@ -0,0 +1,32 @@
<?php
include __DIR__ . '/../vendor/autoload.php';
use Phroute\Phroute\RouteCollector;
use Phroute\Phroute\Dispatcher;
$collector = new RouteCollector();
$USER_SESSION = false;
$collector->filter('auth', function() use(&$USER_SESSION){
if(!$USER_SESSION)
{
return "Nope! Must be authenticated";
}
});
$collector->group(array('before' => 'auth'), function(RouteCollector $collector){
$collector->get('/', function(){
return 'Hurrah! Home Page';
});
});
$dispatcher = new Dispatcher($collector->getData());
echo $dispatcher->dispatch('GET', '/'), "\n"; // Nope! Must be authenticated
$USER_SESSION = true;
echo $dispatcher->dispatch('GET', '/'), "\n"; // Hurrah! Home Page

View File

@ -0,0 +1,29 @@
<?php
include __DIR__ . '/../vendor/autoload.php';
use Phroute\Phroute\RouteCollector;
use Phroute\Phroute\Dispatcher;
$collector = new RouteCollector();
$collector->group(array('prefix' => 'admin'), function(RouteCollector $collector){
$collector->get('pages', function(){
return 'page management';
});
$collector->get('products', function(){
return 'product management';
});
$collector->get('orders', function(){
return 'order management';
});
});
$dispatcher = new Dispatcher($collector->getData());
echo $dispatcher->dispatch('GET', '/admin/pages'), "\n"; // page management
echo $dispatcher->dispatch('GET', '/admin/products'), "\n"; // product management
echo $dispatcher->dispatch('GET', '/admin/orders'), "\n"; // order management

View File

@ -0,0 +1,36 @@
<?php
include __DIR__ . '/../vendor/autoload.php';
use Phroute\Phroute\RouteCollector;
use Phroute\Phroute\Dispatcher;
$collector = new RouteCollector();
$collector->filter('auth', function(){
return "Nope!";
});
$collector->group(array('prefix' => 'admin'), function(RouteCollector $collector){
$collector->group(['before' => 'auth'], function(RouteCollector $collector){
$collector->get('pages', function(){
return 'page management';
});
$collector->get('products', function(){
return 'product management';
});
});
// Not inside auth group
$collector->get('orders', function(){
return 'Order management';
});
});
$dispatcher = new Dispatcher($collector->getData());
echo $dispatcher->dispatch('GET', '/admin/pages'), "\n"; // Nope!
echo $dispatcher->dispatch('GET', '/admin/products'), "\n"; // Nope!
echo $dispatcher->dispatch('GET', '/admin/orders'), "\n"; // order management

View File

@ -0,0 +1,27 @@
<?php
error_reporting(-1);
ini_set('display_errors', 1);
include __DIR__ . '/../vendor/autoload.php';
use Phroute\Phroute\RouteCollector;
use Phroute\Phroute\Dispatcher;
$collector = new RouteCollector();
$collector->get('/', function(){
return 'Home Page';
});
$collector->post('products', function(){
return 'Create Product';
});
$collector->put('items/{id}', function($id){
return 'Amend Item ' . $id;
});
$dispatcher = new Dispatcher($collector->getData());
echo $dispatcher->dispatch($_SERVER['REQUEST_METHOD'], parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)); // Home Page
//echo $dispatcher->dispatch('POST', '/products'), "\n"; // Create Product
//echo $dispatcher->dispatch('PUT', '/items/123'), "\n"; // Amend Item 123

25
vendor/craft-group/phroute/phpunit.xml vendored Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="true"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="Phroute Tests">
<directory>./test/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,232 @@
<?php namespace Phroute\Phroute;
use Phroute\Phroute\Exception\HttpMethodNotAllowedException;
use Phroute\Phroute\Exception\HttpRouteNotFoundException;
class Dispatcher {
private $staticRouteMap;
private $variableRouteData;
private $filters;
private $handlerResolver;
public $matchedRoute;
/**
* Create a new route dispatcher.
*
* @param RouteDataInterface $data
* @param HandlerResolverInterface $resolver
*/
public function __construct(RouteDataInterface $data, HandlerResolverInterface $resolver = null)
{
$this->staticRouteMap = $data->getStaticRoutes();
$this->variableRouteData = $data->getVariableRoutes();
$this->filters = $data->getFilters();
if ($resolver === null)
{
$this->handlerResolver = new HandlerResolver();
}
else
{
$this->handlerResolver = $resolver;
}
}
/**
* Dispatch a route for the given HTTP Method / URI.
*
* @param $httpMethod
* @param $uri
* @return mixed|null
*/
public function dispatch($httpMethod, $uri)
{
list($handler, $filters, $vars) = $this->dispatchRoute($httpMethod, trim($uri, '/'));
list($beforeFilter, $afterFilter) = $this->parseFilters($filters);
if(($response = $this->dispatchFilters($beforeFilter)) !== null)
{
return $response;
}
$resolvedHandler = $this->handlerResolver->resolve($handler);
$response = call_user_func_array($resolvedHandler, $vars);
return $this->dispatchFilters($afterFilter, $response);
}
/**
* Dispatch a route filter.
*
* @param $filters
* @param null $response
* @return mixed|null
*/
private function dispatchFilters($filters, $response = null)
{
while($filter = array_shift($filters))
{
$handler = $this->handlerResolver->resolve($filter['closure']);
$params = array_merge([$response], $filter['params']);
if(($filteredResponse = call_user_func_array($handler, $params)) !== null)
{
return $filteredResponse;
}
}
return $response;
}
/**
* Normalise the array filters attached to the route and merge with any global filters.
*
* @param $filters
* @return array
*/
private function parseFilters($filters)
{
$beforeFilter = array();
$afterFilter = array();
if(isset($filters[Route::BEFORE]))
{
foreach ($filters[Route::BEFORE] as $filter){
if(array_key_exists($filter['name'], $this->filters)){
$beforeFilter[] = array_merge($filter, ['closure' => $this->filters[$filter['name']]]);
}
}
//$beforeFilter = array_intersect_key($this->filters, array_flip((array) $filters[Route::BEFORE]));
}
if(isset($filters[Route::AFTER]))
{
foreach ($filters[Route::AFTER] as $filter){
if(array_key_exists($filter['name'], $this->filters)){
$afterFilter[] = array_merge($filter, ['closure' => $this->filters[$filter['name']]]);
}
}
//$afterFilter = array_intersect_key($this->filters, array_flip((array) $filters[Route::AFTER]));
}
return array($beforeFilter, $afterFilter);
}
/**
* Perform the route dispatching. Check static routes first followed by variable routes.
*
* @param $httpMethod
* @param $uri
* @throws Exception\HttpRouteNotFoundException
*/
private function dispatchRoute($httpMethod, $uri)
{
if (isset($this->staticRouteMap[$uri]))
{
return $this->dispatchStaticRoute($httpMethod, $uri);
}
return $this->dispatchVariableRoute($httpMethod, $uri);
}
/**
* Handle the dispatching of static routes.
*
* @param $httpMethod
* @param $uri
* @return mixed
* @throws Exception\HttpMethodNotAllowedException
*/
private function dispatchStaticRoute($httpMethod, $uri)
{
$routes = $this->staticRouteMap[$uri];
if (!isset($routes[$httpMethod]))
{
$httpMethod = $this->checkFallbacks($routes, $httpMethod);
}
return $routes[$httpMethod];
}
/**
* Check fallback routes: HEAD for GET requests followed by the ANY attachment.
*
* @param $routes
* @param $httpMethod
* @throws Exception\HttpMethodNotAllowedException
*/
private function checkFallbacks($routes, $httpMethod)
{
$additional = array(Route::ANY);
if($httpMethod === Route::HEAD)
{
$additional[] = Route::GET;
}
foreach($additional as $method)
{
if(isset($routes[$method]))
{
return $method;
}
}
$this->matchedRoute = $routes;
throw new HttpMethodNotAllowedException('Allow: ' . implode(', ', array_keys($routes)));
}
/**
* Handle the dispatching of variable routes.
*
* @param $httpMethod
* @param $uri
* @throws Exception\HttpMethodNotAllowedException
* @throws Exception\HttpRouteNotFoundException
*/
private function dispatchVariableRoute($httpMethod, $uri)
{
foreach ($this->variableRouteData as $data)
{
if (!preg_match($data['regex'], $uri, $matches))
{
continue;
}
$count = count($matches);
while(!isset($data['routeMap'][$count++]));
$routes = $data['routeMap'][$count - 1];
if (!isset($routes[$httpMethod]))
{
$httpMethod = $this->checkFallbacks($routes, $httpMethod);
}
foreach (array_values($routes[$httpMethod][2]) as $i => $varName)
{
if(!isset($matches[$i + 1]) || $matches[$i + 1] === '')
{
unset($routes[$httpMethod][2][$varName]);
}
else
{
$routes[$httpMethod][2][$varName] = $matches[$i + 1];
}
}
return $routes[$httpMethod];
}
throw new HttpRouteNotFoundException('Route ' . $uri . ' does not exist');
}
}

View File

@ -0,0 +1,4 @@
<?php namespace Phroute\Phroute\Exception;
class BadRouteException extends \LogicException {
}

View File

@ -0,0 +1,3 @@
<?php namespace Phroute\Phroute\Exception;
class HttpException extends \Exception {}

View File

@ -0,0 +1,3 @@
<?php namespace Phroute\Phroute\Exception;
class HttpMethodNotAllowedException extends HttpException {}

View File

@ -0,0 +1,4 @@
<?php namespace Phroute\Phroute\Exception;
class HttpRouteNotFoundException extends HttpException {}

View File

@ -0,0 +1,21 @@
<?php namespace Phroute\Phroute;
class HandlerResolver implements HandlerResolverInterface {
/**
* Create an instance of the given handler.
*
* @param $handler
* @return array
*/
public function resolve ($handler)
{
if(is_array($handler) && is_string($handler[0]))
{
$handler[0] = new $handler[0];
}
return $handler;
}
}

View File

@ -0,0 +1,13 @@
<?php namespace Phroute\Phroute;
interface HandlerResolverInterface {
/**
* Create an instance of the given handler.
*
* @param $handler
* @return array
*/
public function resolve($handler);
}

View File

@ -0,0 +1,33 @@
<?php namespace Phroute\Phroute;
class Route {
/**
* Constants for before and after filters
*/
const BEFORE = 'before';
const AFTER = 'after';
const PREFIX = 'prefix';
/**
* Constants for common HTTP methods
*/
const ANY = 'ANY';
const GET = 'GET';
const HEAD = 'HEAD';
const POST = 'POST';
const PUT = 'PUT';
const PATCH = 'PATCH';
const DELETE = 'DELETE';
const OPTIONS = 'OPTIONS';
}

View File

@ -0,0 +1,472 @@
<?php namespace Phroute\Phroute;
use ReflectionClass;
use ReflectionMethod;
use Phroute\Phroute\Exception\BadRouteException;
/**
* Class RouteCollector
* @package Phroute\Phroute
*/
class RouteCollector implements RouteDataProviderInterface {
/**
*
*/
const DEFAULT_CONTROLLER_ROUTE = 'index';
/**
*
*/
const APPROX_CHUNK_SIZE = 10;
/**
* @var RouteParser
*/
private $routeParser;
/**
* @var array
*/
private $filters = [];
/**
* @var array
*/
private $staticRoutes = [];
/**
* @var array
*/
private $regexToRoutesMap = [];
/**
* @var array
*/
private $reverse = [];
/**
* @var array
*/
private $globalFilters = [];
/**
* @var
*/
private $globalRoutePrefix;
/**
* @param RouteParser $routeParser
*/
public function __construct(RouteParser $routeParser = null) {
$this->routeParser = $routeParser ?: new RouteParser();
}
/**
* @param $name
* @return bool
*/
public function hasRoute($name) {
return isset($this->reverse[$name]);
}
/**
* @param $name
* @param array $args
* @return string
*/
public function route($name, array $args = null)
{
$url = [];
$replacements = is_null($args) ? [] : array_values($args);
$variable = 0;
foreach($this->reverse[$name] as $part)
{
if(!$part['variable'])
{
$url[] = $part['value'];
}
elseif(isset($replacements[$variable]))
{
if($part['optional'])
{
$url[] = '/';
}
$url[] = $replacements[$variable++];
}
elseif(!$part['optional'])
{
throw new BadRouteException("Expecting route variable '{$part['name']}'");
}
}
return implode('', $url);
}
/**
* @param $httpMethod
* @param $route
* @param $handler
* @param array $filters
* @return $this
*/
public function addRoute($httpMethod, $route, $handler, array $filters = []) {
if(is_array($route))
{
list($route, $name) = $route;
}
$route = $this->addPrefix($this->trim($route));
list($routeData, $reverseData) = $this->routeParser->parse($route);
if(isset($name))
{
$this->reverse[$name] = $reverseData;
}
if(array_key_exists(Route::BEFORE, $filters)){
$filters = [Route::BEFORE => [['name' => $filters[Route::BEFORE], 'params' => $filters['params']]]];
}
if(array_key_exists(Route::AFTER, $filters)){
$filters = [Route::AFTER => [['name' => $filters[Route::AFTER], 'params' => $filters['params']]]];
}
$filters = array_merge_recursive($this->globalFilters, $filters);
isset($routeData[1]) ?
$this->addVariableRoute($httpMethod, $routeData, $handler, $filters) :
$this->addStaticRoute($httpMethod, $routeData, $handler, $filters);
return $this;
}
/**
* @param $httpMethod
* @param $routeData
* @param $handler
* @param $filters
*/
private function addStaticRoute($httpMethod, $routeData, $handler, $filters)
{
$routeStr = $routeData[0];
if (isset($this->staticRoutes[$routeStr][$httpMethod]))
{
throw new BadRouteException("Cannot register two routes matching '$routeStr' for method '$httpMethod'");
}
foreach ($this->regexToRoutesMap as $regex => $routes) {
if (isset($routes[$httpMethod]) && preg_match('~^' . $regex . '$~', $routeStr))
{
throw new BadRouteException("Static route '$routeStr' is shadowed by previously defined variable route '$regex' for method '$httpMethod'");
}
}
$this->staticRoutes[$routeStr][$httpMethod] = array($handler, $filters, []);
}
/**
* @param $httpMethod
* @param $routeData
* @param $handler
* @param $filters
*/
private function addVariableRoute($httpMethod, $routeData, $handler, $filters)
{
list($regex, $variables) = $routeData;
if (isset($this->regexToRoutesMap[$regex][$httpMethod]))
{
throw new BadRouteException("Cannot register two routes matching '$regex' for method '$httpMethod'");
}
$this->regexToRoutesMap[$regex][$httpMethod] = [$handler, $filters, $variables];
}
/**
* @param array $filters
* @param \Closure $callback
*/
public function group(array $filters, \Closure $callback)
{
if(array_key_exists(Route::BEFORE, $filters)){
$filters = [Route::BEFORE => [['name' => $filters[Route::BEFORE], 'params' => isset($filters['params']) ? $filters['params'] : []]]];
}
if(array_key_exists(Route::AFTER, $filters)){
$filters = [Route::AFTER => [['name' => $filters[Route::AFTER], 'params' => isset($filters['params']) ? $filters['params'] : []]]];
}
$oldGlobalFilters = $this->globalFilters;
$oldGlobalPrefix = $this->globalRoutePrefix;
$this->globalFilters = array_merge_recursive($this->globalFilters, array_intersect_key($filters, [Route::AFTER => 1, Route::BEFORE => 1]));
$newPrefix = isset($filters[Route::PREFIX]) ? $this->trim($filters[Route::PREFIX]) : null;
$this->globalRoutePrefix = $this->addPrefix($newPrefix);
$callback($this);
$this->globalFilters = $oldGlobalFilters;
$this->globalRoutePrefix = $oldGlobalPrefix;
}
private function addPrefix($route)
{
return $this->trim($this->trim($this->globalRoutePrefix) . '/' . $route);
}
/**
* @param $name
* @param $handler
*/
public function filter($name, $handler)
{
$this->filters[$name] = $handler;
}
/**
* @param $route
* @param $handler
* @param array $filters
* @return RouteCollector
*/
public function get($route, $handler, array $filters = [])
{
return $this->addRoute(Route::GET, $route, $handler, $filters);
}
/**
* @param $route
* @param $handler
* @param array $filters
* @return RouteCollector
*/
public function head($route, $handler, array $filters = [])
{
return $this->addRoute(Route::HEAD, $route, $handler, $filters);
}
/**
* @param $route
* @param $handler
* @param array $filters
* @return RouteCollector
*/
public function post($route, $handler, array $filters = [])
{
return $this->addRoute(Route::POST, $route, $handler, $filters);
}
/**
* @param $route
* @param $handler
* @param array $filters
* @return RouteCollector
*/
public function put($route, $handler, array $filters = [])
{
return $this->addRoute(Route::PUT, $route, $handler, $filters);
}
/**
* @param $route
* @param $handler
* @param array $filters
* @return RouteCollector
*/
public function patch($route, $handler, array $filters = [])
{
return $this->addRoute(Route::PATCH, $route, $handler, $filters);
}
/**
* @param $route
* @param $handler
* @param array $filters
* @return RouteCollector
*/
public function delete($route, $handler, array $filters = [])
{
return $this->addRoute(Route::DELETE, $route, $handler, $filters);
}
/**
* @param $route
* @param $handler
* @param array $filters
* @return RouteCollector
*/
public function options($route, $handler, array $filters = [])
{
return $this->addRoute(Route::OPTIONS, $route, $handler, $filters);
}
/**
* @param $route
* @param $handler
* @param array $filters
* @return RouteCollector
*/
public function any($route, $handler, array $filters = [])
{
return $this->addRoute(Route::ANY, $route, $handler, $filters);
}
/**
* @param $route
* @param $classname
* @param array $filters
* @return $this
*/
public function controller($route, $classname, array $filters = [])
{
$reflection = new ReflectionClass($classname);
$validMethods = $this->getValidMethods();
$sep = $route === '/' ? '' : '/';
foreach($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method)
{
foreach($validMethods as $valid)
{
if(stripos($method->name, $valid) === 0)
{
$methodName = $this->camelCaseToDashed(substr($method->name, strlen($valid)));
$params = $this->buildControllerParameters($method);
if($methodName === self::DEFAULT_CONTROLLER_ROUTE)
{
$this->addRoute($valid, $route . $params, [$classname, $method->name], $filters);
}
$this->addRoute($valid, $route . $sep . $methodName . $params, [$classname, $method->name], $filters);
break;
}
}
}
return $this;
}
/**
* @param ReflectionMethod $method
* @return string
*/
private function buildControllerParameters(ReflectionMethod $method)
{
$params = '';
foreach($method->getParameters() as $param)
{
$params .= "/{" . $param->getName() . "}" . ($param->isOptional() ? '?' : '');
}
return $params;
}
/**
* @param $string
* @return string
*/
private function camelCaseToDashed($string)
{
return strtolower(preg_replace('/([A-Z])/', '-$1', lcfirst($string)));
}
/**
* @return array
*/
public function getValidMethods()
{
return [
Route::ANY,
Route::GET,
Route::POST,
Route::PUT,
Route::PATCH,
Route::DELETE,
Route::HEAD,
Route::OPTIONS,
];
}
/**
* @return RouteDataArray
*/
public function getData()
{
if (empty($this->regexToRoutesMap))
{
return new RouteDataArray($this->staticRoutes, [], $this->filters);
}
return new RouteDataArray($this->staticRoutes, $this->generateVariableRouteData(), $this->filters);
}
/**
* @param $route
* @return string
*/
private function trim($route)
{
if(is_null($route)) {
return null;
}
return trim($route, '/');
}
/**
* @return array
*/
private function generateVariableRouteData()
{
$chunkSize = $this->computeChunkSize(count($this->regexToRoutesMap));
$chunks = array_chunk($this->regexToRoutesMap, $chunkSize, true);
return array_map([$this, 'processChunk'], $chunks);
}
/**
* @param $count
* @return float
*/
private function computeChunkSize($count)
{
$numParts = max(1, round($count / self::APPROX_CHUNK_SIZE));
return ceil($count / $numParts);
}
/**
* @param $regexToRoutesMap
* @return array
*/
private function processChunk($regexToRoutesMap)
{
$routeMap = [];
$regexes = [];
$numGroups = 0;
foreach ($regexToRoutesMap as $regex => $routes) {
$firstRoute = reset($routes);
$numVariables = count($firstRoute[2]);
$numGroups = max($numGroups, $numVariables);
$regexes[] = $regex . str_repeat('()', $numGroups - $numVariables);
foreach ($routes as $httpMethod => $route) {
$routeMap[$numGroups + 1][$httpMethod] = $route;
}
$numGroups++;
}
$regex = '~^(?|' . implode('|', $regexes) . ')$~';
return ['regex' => $regex, 'routeMap' => $routeMap];
}
}

View File

@ -0,0 +1,57 @@
<?php namespace Phroute\Phroute;
class RouteDataArray implements RouteDataInterface {
/**
* @var array
*/
private $variableRoutes;
/**
* @var array
*/
private $staticRoutes;
/**
* @var array
*/
private $filters;
/**
* @param array $staticRoutes
* @param array $variableRoutes
* @param array $filters
*/
public function __construct(array $staticRoutes, array $variableRoutes, array $filters)
{
$this->staticRoutes = $staticRoutes;
$this->variableRoutes = $variableRoutes;
$this->filters = $filters;
}
/**
* @return array
*/
public function getStaticRoutes()
{
return $this->staticRoutes;
}
/**
* @return array
*/
public function getVariableRoutes()
{
return $this->variableRoutes;
}
/**
* @return mixed
*/
public function getFilters()
{
return $this->filters;
}
}

View File

@ -0,0 +1,24 @@
<?php namespace Phroute\Phroute;
/**
* Interface RouteDataInterface
* @package Phroute\Phroute
*/
interface RouteDataInterface {
/**
* @return array
*/
public function getStaticRoutes();
/**
* @return array
*/
public function getVariableRoutes();
/**
* @return array
*/
public function getFilters();
}

View File

@ -0,0 +1,14 @@
<?php namespace Phroute\Phroute;
/**
* Interface RouteDataProviderInterface
* @package Phroute\Phroute
*/
interface RouteDataProviderInterface {
/**
* @return mixed
*/
public function getData();
}

View File

@ -0,0 +1,207 @@
<?php namespace Phroute\Phroute;
use Phroute\Phroute\Exception\BadRouteException;
/**
* Parses routes of the following form:
*
* "/user/{name}/{id:[0-9]+}?"
*/
class RouteParser {
/**
* Search through the given route looking for dynamic portions.
*
* Using ~ as the regex delimiter.
*
* We start by looking for a literal '{' character followed by any amount of whitespace.
* The next portion inside the parentheses looks for a parameter name containing alphanumeric characters or underscore.
*
* After this we look for the ':\d+' and ':[0-9]+' style portion ending with a closing '}' character.
*
* Finally we look for an optional '?' which is used to signify an optional route.
*/
const VARIABLE_REGEX =
"~\{
\s* ([a-zA-Z0-9_]*) \s*
(?:
: \s* ([^{]+(?:\{.*?\})?)
)?
\}\??~x";
/**
* The default parameter character restriction (One or more characters that is not a '/').
*/
const DEFAULT_DISPATCH_REGEX = '[^/]+';
private $parts;
private $reverseParts;
private $partsCounter;
private $variables;
private $regexOffset;
/**
* Handy parameter type restrictions.
*
* @var array
*/
private $regexShortcuts = array(
':i}' => ':[0-9]+}',
':a}' => ':[0-9A-Za-z]+}',
':h}' => ':[0-9A-Fa-f]+}',
':c}' => ':[a-zA-Z0-9+_\-\.]+}'
);
/**
* Parse a route returning the correct data format to pass to the dispatch engine.
*
* @param $route
* @return array
*/
public function parse($route)
{
$this->reset();
$route = strtr($route, $this->regexShortcuts);
if (!$matches = $this->extractVariableRouteParts($route))
{
$reverse = array(
'variable' => false,
'value' => $route
);
return [[$route], array($reverse)];
}
foreach ($matches as $set) {
$this->staticParts($route, $set[0][1]);
$this->validateVariable($set[1][0]);
$regexPart = (isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX);
$this->regexOffset = $set[0][1] + strlen($set[0][0]);
$match = '(' . $regexPart . ')';
$isOptional = substr($set[0][0], -1) === '?';
if($isOptional)
{
$match = $this->makeOptional($match);
}
$this->reverseParts[$this->partsCounter] = array(
'variable' => true,
'optional' => $isOptional,
'name' => $set[1][0]
);
$this->parts[$this->partsCounter++] = $match;
}
$this->staticParts($route, strlen($route));
return [[implode('', $this->parts), $this->variables], array_values($this->reverseParts)];
}
/**
* Reset the parser ready for the next route.
*/
private function reset()
{
$this->parts = array();
$this->reverseParts = array();
$this->partsCounter = 0;
$this->variables = array();
$this->regexOffset = 0;
}
/**
* Return any variable route portions from the given route.
*
* @param $route
* @return mixed
*/
private function extractVariableRouteParts($route)
{
if(preg_match_all(self::VARIABLE_REGEX, $route, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER))
{
return $matches;
}
}
/**
* @param $route
* @param $nextOffset
*/
private function staticParts($route, $nextOffset)
{
$static = preg_split('~(/)~u', substr($route, $this->regexOffset, $nextOffset - $this->regexOffset), 0, PREG_SPLIT_DELIM_CAPTURE);
foreach($static as $staticPart)
{
if($staticPart)
{
$quotedPart = $this->quote($staticPart);
$this->parts[$this->partsCounter] = $quotedPart;
$this->reverseParts[$this->partsCounter] = array(
'variable' => false,
'value' => $staticPart
);
$this->partsCounter++;
}
}
}
/**
* @param $varName
*/
private function validateVariable($varName)
{
if (isset($this->variables[$varName]))
{
throw new BadRouteException("Cannot use the same placeholder '$varName' twice");
}
$this->variables[$varName] = $varName;
}
/**
* @param $match
* @return string
*/
private function makeOptional($match)
{
$previous = $this->partsCounter - 1;
if(isset($this->parts[$previous]) && $this->parts[$previous] === '/')
{
$this->partsCounter--;
$match = '(?:/' . $match . ')';
}
return $match . '?';
}
/**
* @param $part
* @return string
*/
private function quote($part)
{
return preg_quote($part, '~');
}
}

File diff suppressed because it is too large Load Diff

19
vendor/doctrine/inflector/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2006-2015 Doctrine Project
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.

7
vendor/doctrine/inflector/README.md vendored Normal file
View File

@ -0,0 +1,7 @@
# Doctrine Inflector
Doctrine Inflector is a small library that can perform string manipulations
with regard to uppercase/lowercase and singular/plural forms of words.
[![Build Status](https://github.com/doctrine/inflector/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/inflector/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x)
[![Code Coverage](https://codecov.io/gh/doctrine/inflector/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/inflector/branch/2.0.x)

41
vendor/doctrine/inflector/composer.json vendored Normal file
View File

@ -0,0 +1,41 @@
{
"name": "doctrine/inflector",
"type": "library",
"description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.",
"keywords": ["php", "strings", "words", "manipulation", "inflector", "inflection", "uppercase", "lowercase", "singular", "plural"],
"homepage": "https://www.doctrine-project.org/projects/inflector.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^11.0",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1",
"phpstan/phpstan-strict-rules": "^1.3",
"phpunit/phpunit": "^8.5 || ^9.5",
"vimeo/psalm": "^4.25 || ^5.4"
},
"autoload": {
"psr-4": {
"Doctrine\\Inflector\\": "lib/Doctrine/Inflector"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\Inflector\\": "tests/Doctrine/Tests/Inflector"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@ -0,0 +1,226 @@
Introduction
============
The Doctrine Inflector has methods for inflecting text. The features include pluralization,
singularization, converting between camelCase and under_score and capitalizing
words.
Installation
============
You can install the Inflector with composer:
.. code-block:: console
$ composer require doctrine/inflector
Usage
=====
Using the inflector is easy, you can create a new ``Doctrine\Inflector\Inflector`` instance by using
the ``Doctrine\Inflector\InflectorFactory`` class:
.. code-block:: php
use Doctrine\Inflector\InflectorFactory;
$inflector = InflectorFactory::create()->build();
By default it will create an English inflector. If you want to use another language, just pass the language
you want to create an inflector for to the ``createForLanguage()`` method:
.. code-block:: php
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Language;
$inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build();
The supported languages are as follows:
- ``Language::ENGLISH``
- ``Language::FRENCH``
- ``Language::NORWEGIAN_BOKMAL``
- ``Language::PORTUGUESE``
- ``Language::SPANISH``
- ``Language::TURKISH``
If you want to manually construct the inflector instead of using a factory, you can do so like this:
.. code-block:: php
use Doctrine\Inflector\CachedWordInflector;
use Doctrine\Inflector\RulesetInflector;
use Doctrine\Inflector\Rules\English;
$inflector = new Inflector(
new CachedWordInflector(new RulesetInflector(
English\Rules::getSingularRuleset()
)),
new CachedWordInflector(new RulesetInflector(
English\Rules::getPluralRuleset()
))
);
Adding Languages
----------------
If you are interested in adding support for your language, take a look at the other languages defined in the
``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy
one of the languages and update the rules for your language.
Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions.
Custom Setup
============
If you want to setup custom singular and plural rules, you can configure these in the factory:
.. code-block:: php
use Doctrine\Inflector\InflectorFactory;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Transformations;
use Doctrine\Inflector\Rules\Word;
$inflector = InflectorFactory::create()
->withSingularRules(
new Ruleset(
new Transformations(
new Transformation(new Pattern('/^(bil)er$/i'), '\1'),
new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta')
),
new Patterns(new Pattern('singulars')),
new Substitutions(new Substitution(new Word('spins'), new Word('spinor')))
)
)
->withPluralRules(
new Ruleset(
new Transformations(
new Transformation(new Pattern('^(bil)er$'), '\1'),
new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta')
),
new Patterns(new Pattern('noflect'), new Pattern('abtuse')),
new Substitutions(
new Substitution(new Word('amaze'), new Word('amazable')),
new Substitution(new Word('phone'), new Word('phonezes'))
)
)
)
->build();
No operation inflector
----------------------
The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for
pluralization and/or singularization. If will simply return the input as output.
This is an implementation of the `Null Object design pattern <https://sourcemaking.com/design_patterns/null_object>`_.
.. code-block:: php
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\NoopWordInflector;
$inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector());
Tableize
========
Converts ``ModelName`` to ``model_name``:
.. code-block:: php
echo $inflector->tableize('ModelName'); // model_name
Classify
========
Converts ``model_name`` to ``ModelName``:
.. code-block:: php
echo $inflector->classify('model_name'); // ModelName
Camelize
========
This method uses `Classify`_ and then converts the first character to lowercase:
.. code-block:: php
echo $inflector->camelize('model_name'); // modelName
Capitalize
==========
Takes a string and capitalizes all of the words, like PHP's built-in
``ucwords`` function. This extends that behavior, however, by allowing the
word delimiters to be configured, rather than only separating on
whitespace.
Here is an example:
.. code-block:: php
$string = 'top-o-the-morning to all_of_you!';
echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you!
echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You!
Pluralize
=========
Returns a word in plural form.
.. code-block:: php
echo $inflector->pluralize('browser'); // browsers
Singularize
===========
Returns a word in singular form.
.. code-block:: php
echo $inflector->singularize('browsers'); // browser
Urlize
======
Generate a URL friendly string from a string of text:
.. code-block:: php
echo $inflector->urlize('My first blog post'); // my-first-blog-post
Unaccent
========
You can unaccent a string of text using the ``unaccent()`` method:
.. code-block:: php
echo $inflector->unaccent('año'); // ano
Legacy API
==========
The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0.
Support for languages other than English is available in the 2.0 API only.
Acknowledgements
================
The language rules in this library have been adapted from several different sources, including but not limited to:
- `Ruby On Rails Inflector <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>`_
- `ICanBoogie Inflector <https://github.com/ICanBoogie/Inflector>`_
- `CakePHP Inflector <https://book.cakephp.org/3.0/en/core-libraries/inflector.html>`_

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
class CachedWordInflector implements WordInflector
{
/** @var WordInflector */
private $wordInflector;
/** @var string[] */
private $cache = [];
public function __construct(WordInflector $wordInflector)
{
$this->wordInflector = $wordInflector;
}
public function inflect(string $word): string
{
return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word);
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\Ruleset;
use function array_unshift;
abstract class GenericLanguageInflectorFactory implements LanguageInflectorFactory
{
/** @var Ruleset[] */
private $singularRulesets = [];
/** @var Ruleset[] */
private $pluralRulesets = [];
final public function __construct()
{
$this->singularRulesets[] = $this->getSingularRuleset();
$this->pluralRulesets[] = $this->getPluralRuleset();
}
final public function build(): Inflector
{
return new Inflector(
new CachedWordInflector(new RulesetInflector(
...$this->singularRulesets
)),
new CachedWordInflector(new RulesetInflector(
...$this->pluralRulesets
))
);
}
final public function withSingularRules(?Ruleset $singularRules, bool $reset = false): LanguageInflectorFactory
{
if ($reset) {
$this->singularRulesets = [];
}
if ($singularRules instanceof Ruleset) {
array_unshift($this->singularRulesets, $singularRules);
}
return $this;
}
final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): LanguageInflectorFactory
{
if ($reset) {
$this->pluralRulesets = [];
}
if ($pluralRules instanceof Ruleset) {
array_unshift($this->pluralRulesets, $pluralRules);
}
return $this;
}
abstract protected function getSingularRuleset(): Ruleset;
abstract protected function getPluralRuleset(): Ruleset;
}

View File

@ -0,0 +1,507 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use RuntimeException;
use function chr;
use function function_exists;
use function lcfirst;
use function mb_strtolower;
use function ord;
use function preg_match;
use function preg_replace;
use function sprintf;
use function str_replace;
use function strlen;
use function strtolower;
use function strtr;
use function trim;
use function ucwords;
class Inflector
{
private const ACCENTED_CHARACTERS = [
'À' => 'A',
'Á' => 'A',
'Â' => 'A',
'Ã' => 'A',
'Ä' => 'Ae',
'Æ' => 'Ae',
'Å' => 'Aa',
'æ' => 'a',
'Ç' => 'C',
'È' => 'E',
'É' => 'E',
'Ê' => 'E',
'Ë' => 'E',
'Ì' => 'I',
'Í' => 'I',
'Î' => 'I',
'Ï' => 'I',
'Ñ' => 'N',
'Ò' => 'O',
'Ó' => 'O',
'Ô' => 'O',
'Õ' => 'O',
'Ö' => 'Oe',
'Ù' => 'U',
'Ú' => 'U',
'Û' => 'U',
'Ü' => 'Ue',
'Ý' => 'Y',
'ß' => 'ss',
'à' => 'a',
'á' => 'a',
'â' => 'a',
'ã' => 'a',
'ä' => 'ae',
'å' => 'aa',
'ç' => 'c',
'è' => 'e',
'é' => 'e',
'ê' => 'e',
'ë' => 'e',
'ì' => 'i',
'í' => 'i',
'î' => 'i',
'ï' => 'i',
'ñ' => 'n',
'ò' => 'o',
'ó' => 'o',
'ô' => 'o',
'õ' => 'o',
'ö' => 'oe',
'ù' => 'u',
'ú' => 'u',
'û' => 'u',
'ü' => 'ue',
'ý' => 'y',
'ÿ' => 'y',
'Ā' => 'A',
'ā' => 'a',
'Ă' => 'A',
'ă' => 'a',
'Ą' => 'A',
'ą' => 'a',
'Ć' => 'C',
'ć' => 'c',
'Ĉ' => 'C',
'ĉ' => 'c',
'Ċ' => 'C',
'ċ' => 'c',
'Č' => 'C',
'č' => 'c',
'Ď' => 'D',
'ď' => 'd',
'Đ' => 'D',
'đ' => 'd',
'Ē' => 'E',
'ē' => 'e',
'Ĕ' => 'E',
'ĕ' => 'e',
'Ė' => 'E',
'ė' => 'e',
'Ę' => 'E',
'ę' => 'e',
'Ě' => 'E',
'ě' => 'e',
'Ĝ' => 'G',
'ĝ' => 'g',
'Ğ' => 'G',
'ğ' => 'g',
'Ġ' => 'G',
'ġ' => 'g',
'Ģ' => 'G',
'ģ' => 'g',
'Ĥ' => 'H',
'ĥ' => 'h',
'Ħ' => 'H',
'ħ' => 'h',
'Ĩ' => 'I',
'ĩ' => 'i',
'Ī' => 'I',
'ī' => 'i',
'Ĭ' => 'I',
'ĭ' => 'i',
'Į' => 'I',
'į' => 'i',
'İ' => 'I',
'ı' => 'i',
'IJ' => 'IJ',
'ij' => 'ij',
'Ĵ' => 'J',
'ĵ' => 'j',
'Ķ' => 'K',
'ķ' => 'k',
'ĸ' => 'k',
'Ĺ' => 'L',
'ĺ' => 'l',
'Ļ' => 'L',
'ļ' => 'l',
'Ľ' => 'L',
'ľ' => 'l',
'Ŀ' => 'L',
'ŀ' => 'l',
'Ł' => 'L',
'ł' => 'l',
'Ń' => 'N',
'ń' => 'n',
'Ņ' => 'N',
'ņ' => 'n',
'Ň' => 'N',
'ň' => 'n',
'ʼn' => 'N',
'Ŋ' => 'n',
'ŋ' => 'N',
'Ō' => 'O',
'ō' => 'o',
'Ŏ' => 'O',
'ŏ' => 'o',
'Ő' => 'O',
'ő' => 'o',
'Œ' => 'OE',
'œ' => 'oe',
'Ø' => 'O',
'ø' => 'o',
'Ŕ' => 'R',
'ŕ' => 'r',
'Ŗ' => 'R',
'ŗ' => 'r',
'Ř' => 'R',
'ř' => 'r',
'Ś' => 'S',
'ś' => 's',
'Ŝ' => 'S',
'ŝ' => 's',
'Ş' => 'S',
'ş' => 's',
'Š' => 'S',
'š' => 's',
'Ţ' => 'T',
'ţ' => 't',
'Ť' => 'T',
'ť' => 't',
'Ŧ' => 'T',
'ŧ' => 't',
'Ũ' => 'U',
'ũ' => 'u',
'Ū' => 'U',
'ū' => 'u',
'Ŭ' => 'U',
'ŭ' => 'u',
'Ů' => 'U',
'ů' => 'u',
'Ű' => 'U',
'ű' => 'u',
'Ų' => 'U',
'ų' => 'u',
'Ŵ' => 'W',
'ŵ' => 'w',
'Ŷ' => 'Y',
'ŷ' => 'y',
'Ÿ' => 'Y',
'Ź' => 'Z',
'ź' => 'z',
'Ż' => 'Z',
'ż' => 'z',
'Ž' => 'Z',
'ž' => 'z',
'ſ' => 's',
'€' => 'E',
'£' => '',
];
/** @var WordInflector */
private $singularizer;
/** @var WordInflector */
private $pluralizer;
public function __construct(WordInflector $singularizer, WordInflector $pluralizer)
{
$this->singularizer = $singularizer;
$this->pluralizer = $pluralizer;
}
/**
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
*/
public function tableize(string $word): string
{
$tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word);
if ($tableized === null) {
throw new RuntimeException(sprintf(
'preg_replace returned null for value "%s"',
$word
));
}
return mb_strtolower($tableized);
}
/**
* Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
*/
public function classify(string $word): string
{
return str_replace([' ', '_', '-'], '', ucwords($word, ' _-'));
}
/**
* Camelizes a word. This uses the classify() method and turns the first character to lowercase.
*/
public function camelize(string $word): string
{
return lcfirst($this->classify($word));
}
/**
* Uppercases words with configurable delimiters between words.
*
* Takes a string and capitalizes all of the words, like PHP's built-in
* ucwords function. This extends that behavior, however, by allowing the
* word delimiters to be configured, rather than only separating on
* whitespace.
*
* Here is an example:
* <code>
* <?php
* $string = 'top-o-the-morning to all_of_you!';
* echo $inflector->capitalize($string);
* // Top-O-The-Morning To All_of_you!
*
* echo $inflector->capitalize($string, '-_ ');
* // Top-O-The-Morning To All_Of_You!
* ?>
* </code>
*
* @param string $string The string to operate on.
* @param string $delimiters A list of word separators.
*
* @return string The string with all delimiter-separated words capitalized.
*/
public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-"): string
{
return ucwords($string, $delimiters);
}
/**
* Checks if the given string seems like it has utf8 characters in it.
*
* @param string $string The string to check for utf8 characters in.
*/
public function seemsUtf8(string $string): bool
{
for ($i = 0; $i < strlen($string); $i++) {
if (ord($string[$i]) < 0x80) {
continue; // 0bbbbbbb
}
if ((ord($string[$i]) & 0xE0) === 0xC0) {
$n = 1; // 110bbbbb
} elseif ((ord($string[$i]) & 0xF0) === 0xE0) {
$n = 2; // 1110bbbb
} elseif ((ord($string[$i]) & 0xF8) === 0xF0) {
$n = 3; // 11110bbb
} elseif ((ord($string[$i]) & 0xFC) === 0xF8) {
$n = 4; // 111110bb
} elseif ((ord($string[$i]) & 0xFE) === 0xFC) {
$n = 5; // 1111110b
} else {
return false; // Does not match any model
}
for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) {
return false;
}
}
}
return true;
}
/**
* Remove any illegal characters, accents, etc.
*
* @param string $string String to unaccent
*
* @return string Unaccented string
*/
public function unaccent(string $string): string
{
if (preg_match('/[\x80-\xff]/', $string) === false) {
return $string;
}
if ($this->seemsUtf8($string)) {
$string = strtr($string, self::ACCENTED_CHARACTERS);
} else {
$characters = [];
// Assume ISO-8859-1 if not UTF-8
$characters['in'] =
chr(128)
. chr(131)
. chr(138)
. chr(142)
. chr(154)
. chr(158)
. chr(159)
. chr(162)
. chr(165)
. chr(181)
. chr(192)
. chr(193)
. chr(194)
. chr(195)
. chr(196)
. chr(197)
. chr(199)
. chr(200)
. chr(201)
. chr(202)
. chr(203)
. chr(204)
. chr(205)
. chr(206)
. chr(207)
. chr(209)
. chr(210)
. chr(211)
. chr(212)
. chr(213)
. chr(214)
. chr(216)
. chr(217)
. chr(218)
. chr(219)
. chr(220)
. chr(221)
. chr(224)
. chr(225)
. chr(226)
. chr(227)
. chr(228)
. chr(229)
. chr(231)
. chr(232)
. chr(233)
. chr(234)
. chr(235)
. chr(236)
. chr(237)
. chr(238)
. chr(239)
. chr(241)
. chr(242)
. chr(243)
. chr(244)
. chr(245)
. chr(246)
. chr(248)
. chr(249)
. chr(250)
. chr(251)
. chr(252)
. chr(253)
. chr(255);
$characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy';
$string = strtr($string, $characters['in'], $characters['out']);
$doubleChars = [];
$doubleChars['in'] = [
chr(140),
chr(156),
chr(198),
chr(208),
chr(222),
chr(223),
chr(230),
chr(240),
chr(254),
];
$doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'];
$string = str_replace($doubleChars['in'], $doubleChars['out'], $string);
}
return $string;
}
/**
* Convert any passed string to a url friendly string.
* Converts 'My first blog post' to 'my-first-blog-post'
*
* @param string $string String to urlize.
*
* @return string Urlized string.
*/
public function urlize(string $string): string
{
// Remove all non url friendly characters with the unaccent function
$unaccented = $this->unaccent($string);
if (function_exists('mb_strtolower')) {
$lowered = mb_strtolower($unaccented);
} else {
$lowered = strtolower($unaccented);
}
$replacements = [
'/\W/' => ' ',
'/([A-Z]+)([A-Z][a-z])/' => '\1_\2',
'/([a-z\d])([A-Z])/' => '\1_\2',
'/[^A-Z^a-z^0-9^\/]+/' => '-',
];
$urlized = $lowered;
foreach ($replacements as $pattern => $replacement) {
$replaced = preg_replace($pattern, $replacement, $urlized);
if ($replaced === null) {
throw new RuntimeException(sprintf(
'preg_replace returned null for value "%s"',
$urlized
));
}
$urlized = $replaced;
}
return trim($urlized, '-');
}
/**
* Returns a word in singular form.
*
* @param string $word The word in plural form.
*
* @return string The word in singular form.
*/
public function singularize(string $word): string
{
return $this->singularizer->inflect($word);
}
/**
* Returns a word in plural form.
*
* @param string $word The word in singular form.
*
* @return string The word in plural form.
*/
public function pluralize(string $word): string
{
return $this->pluralizer->inflect($word);
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Turkish;
use InvalidArgumentException;
use function sprintf;
final class InflectorFactory
{
public static function create(): LanguageInflectorFactory
{
return self::createForLanguage(Language::ENGLISH);
}
public static function createForLanguage(string $language): LanguageInflectorFactory
{
switch ($language) {
case Language::ENGLISH:
return new English\InflectorFactory();
case Language::FRENCH:
return new French\InflectorFactory();
case Language::NORWEGIAN_BOKMAL:
return new NorwegianBokmal\InflectorFactory();
case Language::PORTUGUESE:
return new Portuguese\InflectorFactory();
case Language::SPANISH:
return new Spanish\InflectorFactory();
case Language::TURKISH:
return new Turkish\InflectorFactory();
default:
throw new InvalidArgumentException(sprintf(
'Language "%s" is not supported.',
$language
));
}
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
final class Language
{
public const ENGLISH = 'english';
public const FRENCH = 'french';
public const NORWEGIAN_BOKMAL = 'norwegian-bokmal';
public const PORTUGUESE = 'portuguese';
public const SPANISH = 'spanish';
public const TURKISH = 'turkish';
private function __construct()
{
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
use Doctrine\Inflector\Rules\Ruleset;
interface LanguageInflectorFactory
{
/**
* Applies custom rules for singularisation
*
* @param bool $reset If true, will unset default inflections for all new rules
*
* @return $this
*/
public function withSingularRules(?Ruleset $singularRules, bool $reset = false): self;
/**
* Applies custom rules for pluralisation
*
* @param bool $reset If true, will unset default inflections for all new rules
*
* @return $this
*/
public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): self;
/**
* Builds the inflector instance with all applicable rules
*/
public function build(): Inflector;
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector;
class NoopWordInflector implements WordInflector
{
public function inflect(string $word): string
{
return $word;
}
}

View File

@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/** @return Transformation[] */
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('(s)tatuses$'), '\1\2tatus');
yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatus');
yield new Transformation(new Pattern('(c)ampus$'), '\1\2ampus');
yield new Transformation(new Pattern('^(.*)(menu)s$'), '\1\2');
yield new Transformation(new Pattern('(quiz)zes$'), '\\1');
yield new Transformation(new Pattern('(matr)ices$'), '\1ix');
yield new Transformation(new Pattern('(vert|ind)ices$'), '\1ex');
yield new Transformation(new Pattern('^(ox)en'), '\1');
yield new Transformation(new Pattern('(alias)(es)*$'), '\1');
yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)oes$'), '\1o');
yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$'), '\1us');
yield new Transformation(new Pattern('([ftw]ax)es'), '\1');
yield new Transformation(new Pattern('(analys|ax|cris|test|thes)es$'), '\1is');
yield new Transformation(new Pattern('(shoe|slave)s$'), '\1');
yield new Transformation(new Pattern('(o)es$'), '\1');
yield new Transformation(new Pattern('ouses$'), 'ouse');
yield new Transformation(new Pattern('([^a])uses$'), '\1us');
yield new Transformation(new Pattern('([m|l])ice$'), '\1ouse');
yield new Transformation(new Pattern('(x|ch|ss|sh)es$'), '\1');
yield new Transformation(new Pattern('(m)ovies$'), '\1\2ovie');
yield new Transformation(new Pattern('(s)eries$'), '\1\2eries');
yield new Transformation(new Pattern('([^aeiouy]|qu)ies$'), '\1y');
yield new Transformation(new Pattern('([lr])ves$'), '\1f');
yield new Transformation(new Pattern('(tive)s$'), '\1');
yield new Transformation(new Pattern('(hive)s$'), '\1');
yield new Transformation(new Pattern('(drive)s$'), '\1');
yield new Transformation(new Pattern('(dive)s$'), '\1');
yield new Transformation(new Pattern('(olive)s$'), '\1');
yield new Transformation(new Pattern('([^fo])ves$'), '\1fe');
yield new Transformation(new Pattern('(^analy)ses$'), '\1sis');
yield new Transformation(new Pattern('(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$'), '\1\2sis');
yield new Transformation(new Pattern('(tax)a$'), '\1on');
yield new Transformation(new Pattern('(c)riteria$'), '\1riterion');
yield new Transformation(new Pattern('([ti])a(?<!regatta)$'), '\1um');
yield new Transformation(new Pattern('(p)eople$'), '\1\2erson');
yield new Transformation(new Pattern('(m)en$'), '\1an');
yield new Transformation(new Pattern('(c)hildren$'), '\1\2hild');
yield new Transformation(new Pattern('(f)eet$'), '\1oot');
yield new Transformation(new Pattern('(n)ews$'), '\1\2ews');
yield new Transformation(new Pattern('eaus$'), 'eau');
yield new Transformation(new Pattern('^tights$'), 'tights');
yield new Transformation(new Pattern('^shorts$'), 'shorts');
yield new Transformation(new Pattern('s$'), '');
}
/** @return Transformation[] */
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatuses');
yield new Transformation(new Pattern('(quiz)$'), '\1zes');
yield new Transformation(new Pattern('^(ox)$'), '\1\2en');
yield new Transformation(new Pattern('([m|l])ouse$'), '\1ice');
yield new Transformation(new Pattern('(matr|vert|ind)(ix|ex)$'), '\1ices');
yield new Transformation(new Pattern('(x|ch|ss|sh)$'), '\1es');
yield new Transformation(new Pattern('([^aeiouy]|qu)y$'), '\1ies');
yield new Transformation(new Pattern('(hive|gulf)$'), '\1s');
yield new Transformation(new Pattern('(?:([^f])fe|([lr])f)$'), '\1\2ves');
yield new Transformation(new Pattern('sis$'), 'ses');
yield new Transformation(new Pattern('([ti])um$'), '\1a');
yield new Transformation(new Pattern('(tax)on$'), '\1a');
yield new Transformation(new Pattern('(c)riterion$'), '\1riteria');
yield new Transformation(new Pattern('(p)erson$'), '\1eople');
yield new Transformation(new Pattern('(m)an$'), '\1en');
yield new Transformation(new Pattern('(c)hild$'), '\1hildren');
yield new Transformation(new Pattern('(f)oot$'), '\1eet');
yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)o$'), '\1\2oes');
yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$'), '\1i');
yield new Transformation(new Pattern('us$'), 'uses');
yield new Transformation(new Pattern('(alias)$'), '\1es');
yield new Transformation(new Pattern('(analys|ax|cris|test|thes)is$'), '\1es');
yield new Transformation(new Pattern('s$'), 's');
yield new Transformation(new Pattern('^$'), '');
yield new Transformation(new Pattern('$'), 's');
}
/** @return Substitution[] */
public static function getIrregular(): iterable
{
yield new Substitution(new Word('atlas'), new Word('atlases'));
yield new Substitution(new Word('axis'), new Word('axes'));
yield new Substitution(new Word('axe'), new Word('axes'));
yield new Substitution(new Word('beef'), new Word('beefs'));
yield new Substitution(new Word('blouse'), new Word('blouses'));
yield new Substitution(new Word('brother'), new Word('brothers'));
yield new Substitution(new Word('cafe'), new Word('cafes'));
yield new Substitution(new Word('cave'), new Word('caves'));
yield new Substitution(new Word('chateau'), new Word('chateaux'));
yield new Substitution(new Word('niveau'), new Word('niveaux'));
yield new Substitution(new Word('child'), new Word('children'));
yield new Substitution(new Word('canvas'), new Word('canvases'));
yield new Substitution(new Word('cookie'), new Word('cookies'));
yield new Substitution(new Word('brownie'), new Word('brownies'));
yield new Substitution(new Word('corpus'), new Word('corpuses'));
yield new Substitution(new Word('cow'), new Word('cows'));
yield new Substitution(new Word('criterion'), new Word('criteria'));
yield new Substitution(new Word('curriculum'), new Word('curricula'));
yield new Substitution(new Word('demo'), new Word('demos'));
yield new Substitution(new Word('domino'), new Word('dominoes'));
yield new Substitution(new Word('echo'), new Word('echoes'));
yield new Substitution(new Word('epoch'), new Word('epochs'));
yield new Substitution(new Word('foot'), new Word('feet'));
yield new Substitution(new Word('fungus'), new Word('fungi'));
yield new Substitution(new Word('ganglion'), new Word('ganglions'));
yield new Substitution(new Word('gas'), new Word('gases'));
yield new Substitution(new Word('genie'), new Word('genies'));
yield new Substitution(new Word('genus'), new Word('genera'));
yield new Substitution(new Word('goose'), new Word('geese'));
yield new Substitution(new Word('graffito'), new Word('graffiti'));
yield new Substitution(new Word('hippopotamus'), new Word('hippopotami'));
yield new Substitution(new Word('hoof'), new Word('hoofs'));
yield new Substitution(new Word('human'), new Word('humans'));
yield new Substitution(new Word('iris'), new Word('irises'));
yield new Substitution(new Word('larva'), new Word('larvae'));
yield new Substitution(new Word('leaf'), new Word('leaves'));
yield new Substitution(new Word('lens'), new Word('lenses'));
yield new Substitution(new Word('loaf'), new Word('loaves'));
yield new Substitution(new Word('man'), new Word('men'));
yield new Substitution(new Word('medium'), new Word('media'));
yield new Substitution(new Word('memorandum'), new Word('memoranda'));
yield new Substitution(new Word('money'), new Word('monies'));
yield new Substitution(new Word('mongoose'), new Word('mongooses'));
yield new Substitution(new Word('motto'), new Word('mottoes'));
yield new Substitution(new Word('move'), new Word('moves'));
yield new Substitution(new Word('mythos'), new Word('mythoi'));
yield new Substitution(new Word('niche'), new Word('niches'));
yield new Substitution(new Word('nucleus'), new Word('nuclei'));
yield new Substitution(new Word('numen'), new Word('numina'));
yield new Substitution(new Word('occiput'), new Word('occiputs'));
yield new Substitution(new Word('octopus'), new Word('octopuses'));
yield new Substitution(new Word('opus'), new Word('opuses'));
yield new Substitution(new Word('ox'), new Word('oxen'));
yield new Substitution(new Word('passerby'), new Word('passersby'));
yield new Substitution(new Word('penis'), new Word('penises'));
yield new Substitution(new Word('person'), new Word('people'));
yield new Substitution(new Word('plateau'), new Word('plateaux'));
yield new Substitution(new Word('runner-up'), new Word('runners-up'));
yield new Substitution(new Word('safe'), new Word('safes'));
yield new Substitution(new Word('sex'), new Word('sexes'));
yield new Substitution(new Word('sieve'), new Word('sieves'));
yield new Substitution(new Word('soliloquy'), new Word('soliloquies'));
yield new Substitution(new Word('son-in-law'), new Word('sons-in-law'));
yield new Substitution(new Word('syllabus'), new Word('syllabi'));
yield new Substitution(new Word('testis'), new Word('testes'));
yield new Substitution(new Word('thief'), new Word('thieves'));
yield new Substitution(new Word('tooth'), new Word('teeth'));
yield new Substitution(new Word('tornado'), new Word('tornadoes'));
yield new Substitution(new Word('trilby'), new Word('trilbys'));
yield new Substitution(new Word('turf'), new Word('turfs'));
yield new Substitution(new Word('valve'), new Word('valves'));
yield new Substitution(new Word('volcano'), new Word('volcanoes'));
yield new Substitution(new Word('abuse'), new Word('abuses'));
yield new Substitution(new Word('avalanche'), new Word('avalanches'));
yield new Substitution(new Word('cache'), new Word('caches'));
yield new Substitution(new Word('criterion'), new Word('criteria'));
yield new Substitution(new Word('curve'), new Word('curves'));
yield new Substitution(new Word('emphasis'), new Word('emphases'));
yield new Substitution(new Word('foe'), new Word('foes'));
yield new Substitution(new Word('grave'), new Word('graves'));
yield new Substitution(new Word('hoax'), new Word('hoaxes'));
yield new Substitution(new Word('medium'), new Word('media'));
yield new Substitution(new Word('neurosis'), new Word('neuroses'));
yield new Substitution(new Word('save'), new Word('saves'));
yield new Substitution(new Word('wave'), new Word('waves'));
yield new Substitution(new Word('oasis'), new Word('oases'));
yield new Substitution(new Word('valve'), new Word('valves'));
yield new Substitution(new Word('zombie'), new Word('zombies'));
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\English;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/** @return Pattern[] */
public static function getSingular(): iterable
{
yield from self::getDefault();
yield new Pattern('.*ss');
yield new Pattern('clothes');
yield new Pattern('data');
yield new Pattern('fascia');
yield new Pattern('fuchsia');
yield new Pattern('galleria');
yield new Pattern('mafia');
yield new Pattern('militia');
yield new Pattern('pants');
yield new Pattern('petunia');
yield new Pattern('sepia');
yield new Pattern('trivia');
yield new Pattern('utopia');
}
/** @return Pattern[] */
public static function getPlural(): iterable
{
yield from self::getDefault();
yield new Pattern('people');
yield new Pattern('trivia');
yield new Pattern('\w+ware$');
yield new Pattern('media');
}
/** @return Pattern[] */
private static function getDefault(): iterable
{
yield new Pattern('\w+media');
yield new Pattern('advice');
yield new Pattern('aircraft');
yield new Pattern('amoyese');
yield new Pattern('art');
yield new Pattern('audio');
yield new Pattern('baggage');
yield new Pattern('bison');
yield new Pattern('borghese');
yield new Pattern('bream');
yield new Pattern('breeches');
yield new Pattern('britches');
yield new Pattern('buffalo');
yield new Pattern('butter');
yield new Pattern('cantus');
yield new Pattern('carp');
yield new Pattern('cattle');
yield new Pattern('chassis');
yield new Pattern('clippers');
yield new Pattern('clothing');
yield new Pattern('coal');
yield new Pattern('cod');
yield new Pattern('coitus');
yield new Pattern('compensation');
yield new Pattern('congoese');
yield new Pattern('contretemps');
yield new Pattern('coreopsis');
yield new Pattern('corps');
yield new Pattern('cotton');
yield new Pattern('data');
yield new Pattern('debris');
yield new Pattern('deer');
yield new Pattern('diabetes');
yield new Pattern('djinn');
yield new Pattern('education');
yield new Pattern('eland');
yield new Pattern('elk');
yield new Pattern('emoji');
yield new Pattern('equipment');
yield new Pattern('evidence');
yield new Pattern('faroese');
yield new Pattern('feedback');
yield new Pattern('fish');
yield new Pattern('flounder');
yield new Pattern('flour');
yield new Pattern('foochowese');
yield new Pattern('food');
yield new Pattern('furniture');
yield new Pattern('gallows');
yield new Pattern('genevese');
yield new Pattern('genoese');
yield new Pattern('gilbertese');
yield new Pattern('gold');
yield new Pattern('headquarters');
yield new Pattern('herpes');
yield new Pattern('hijinks');
yield new Pattern('homework');
yield new Pattern('hottentotese');
yield new Pattern('impatience');
yield new Pattern('information');
yield new Pattern('innings');
yield new Pattern('jackanapes');
yield new Pattern('jeans');
yield new Pattern('jedi');
yield new Pattern('kin');
yield new Pattern('kiplingese');
yield new Pattern('knowledge');
yield new Pattern('kongoese');
yield new Pattern('leather');
yield new Pattern('love');
yield new Pattern('lucchese');
yield new Pattern('luggage');
yield new Pattern('mackerel');
yield new Pattern('Maltese');
yield new Pattern('management');
yield new Pattern('metadata');
yield new Pattern('mews');
yield new Pattern('money');
yield new Pattern('moose');
yield new Pattern('mumps');
yield new Pattern('music');
yield new Pattern('nankingese');
yield new Pattern('news');
yield new Pattern('nexus');
yield new Pattern('niasese');
yield new Pattern('nutrition');
yield new Pattern('offspring');
yield new Pattern('oil');
yield new Pattern('patience');
yield new Pattern('pekingese');
yield new Pattern('piedmontese');
yield new Pattern('pincers');
yield new Pattern('pistoiese');
yield new Pattern('plankton');
yield new Pattern('pliers');
yield new Pattern('pokemon');
yield new Pattern('police');
yield new Pattern('polish');
yield new Pattern('portuguese');
yield new Pattern('proceedings');
yield new Pattern('progress');
yield new Pattern('rabies');
yield new Pattern('rain');
yield new Pattern('research');
yield new Pattern('rhinoceros');
yield new Pattern('rice');
yield new Pattern('salmon');
yield new Pattern('sand');
yield new Pattern('sarawakese');
yield new Pattern('scissors');
yield new Pattern('sea[- ]bass');
yield new Pattern('series');
yield new Pattern('shavese');
yield new Pattern('shears');
yield new Pattern('sheep');
yield new Pattern('siemens');
yield new Pattern('silk');
yield new Pattern('sms');
yield new Pattern('soap');
yield new Pattern('social media');
yield new Pattern('spam');
yield new Pattern('species');
yield new Pattern('staff');
yield new Pattern('sugar');
yield new Pattern('swine');
yield new Pattern('talent');
yield new Pattern('toothpaste');
yield new Pattern('traffic');
yield new Pattern('travel');
yield new Pattern('trousers');
yield new Pattern('trout');
yield new Pattern('tuna');
yield new Pattern('us');
yield new Pattern('vermontese');
yield new Pattern('vinegar');
yield new Pattern('weather');
yield new Pattern('wenchowese');
yield new Pattern('wheat');
yield new Pattern('whiting');
yield new Pattern('wildebeest');
yield new Pattern('wood');
yield new Pattern('wool');
yield new Pattern('yengeese');
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/** @return Transformation[] */
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\1ail');
yield new Transformation(new Pattern('/ails$/'), 'ail');
yield new Transformation(new Pattern('/(journ|chev)aux$/'), '\1al');
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\1');
yield new Transformation(new Pattern('/s$/'), '');
}
/** @return Transformation[] */
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/(s|x|z)$/'), '\1');
yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/'), '\1aux');
yield new Transformation(new Pattern('/ail$/'), 'ails');
yield new Transformation(new Pattern('/(chacal|carnaval|festival|récital)$/'), '\1s');
yield new Transformation(new Pattern('/al$/'), 'aux');
yield new Transformation(new Pattern('/(bleu|émeu|landau|pneu|sarrau)$/'), '\1s');
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|lieu|pou|au|eu|eau)$/'), '\1x');
yield new Transformation(new Pattern('/$/'), 's');
}
/** @return Substitution[] */
public static function getIrregular(): iterable
{
yield new Substitution(new Word('monsieur'), new Word('messieurs'));
yield new Substitution(new Word('madame'), new Word('mesdames'));
yield new Substitution(new Word('mademoiselle'), new Word('mesdemoiselles'));
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\French;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/** @return Pattern[] */
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/** @return Pattern[] */
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/** @return Pattern[] */
private static function getDefault(): iterable
{
yield new Pattern('');
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/** @return Transformation[] */
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/re$/i'), 'r');
yield new Transformation(new Pattern('/er$/i'), '');
}
/** @return Transformation[] */
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/e$/i'), 'er');
yield new Transformation(new Pattern('/r$/i'), 're');
yield new Transformation(new Pattern('/$/'), 'er');
}
/** @return Substitution[] */
public static function getIrregular(): iterable
{
yield new Substitution(new Word('konto'), new Word('konti'));
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\NorwegianBokmal;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/** @return Pattern[] */
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/** @return Pattern[] */
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/** @return Pattern[] */
private static function getDefault(): iterable
{
yield new Pattern('barn');
yield new Pattern('fjell');
yield new Pattern('hus');
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use function preg_match;
final class Pattern
{
/** @var string */
private $pattern;
/** @var string */
private $regex;
public function __construct(string $pattern)
{
$this->pattern = $pattern;
if (isset($this->pattern[0]) && $this->pattern[0] === '/') {
$this->regex = $this->pattern;
} else {
$this->regex = '/' . $this->pattern . '/i';
}
}
public function getPattern(): string
{
return $this->pattern;
}
public function getRegex(): string
{
return $this->regex;
}
public function matches(string $word): bool
{
return preg_match($this->getRegex(), $word) === 1;
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
use function array_map;
use function implode;
use function preg_match;
class Patterns
{
/** @var Pattern[] */
private $patterns;
/** @var string */
private $regex;
public function __construct(Pattern ...$patterns)
{
$this->patterns = $patterns;
$patterns = array_map(static function (Pattern $pattern): string {
return $pattern->getPattern();
}, $this->patterns);
$this->regex = '/^(?:' . implode('|', $patterns) . ')$/i';
}
public function matches(string $word): bool
{
return preg_match($this->regex, $word, $regs) === 1;
}
}

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/** @return Transformation[] */
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/^(g|)ases$/i'), '\1ás');
yield new Transformation(new Pattern('/(japon|escoc|ingl|dinamarqu|fregu|portugu)eses$/i'), '\1ês');
yield new Transformation(new Pattern('/(ae|ao|oe)s$/'), 'ao');
yield new Transformation(new Pattern('/(ãe|ão|õe)s$/'), 'ão');
yield new Transformation(new Pattern('/^(.*[^s]s)es$/i'), '\1');
yield new Transformation(new Pattern('/sses$/i'), 'sse');
yield new Transformation(new Pattern('/ns$/i'), 'm');
yield new Transformation(new Pattern('/(r|t|f|v)is$/i'), '\1il');
yield new Transformation(new Pattern('/uis$/i'), 'ul');
yield new Transformation(new Pattern('/ois$/i'), 'ol');
yield new Transformation(new Pattern('/eis$/i'), 'ei');
yield new Transformation(new Pattern('/éis$/i'), 'el');
yield new Transformation(new Pattern('/([^p])ais$/i'), '\1al');
yield new Transformation(new Pattern('/(r|z)es$/i'), '\1');
yield new Transformation(new Pattern('/^(á|gá)s$/i'), '\1s');
yield new Transformation(new Pattern('/([^ê])s$/i'), '\1');
}
/** @return Transformation[] */
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/^(alem|c|p)ao$/i'), '\1aes');
yield new Transformation(new Pattern('/^(irm|m)ao$/i'), '\1aos');
yield new Transformation(new Pattern('/ao$/i'), 'oes');
yield new Transformation(new Pattern('/^(alem|c|p)ão$/i'), '\1ães');
yield new Transformation(new Pattern('/^(irm|m)ão$/i'), '\1ãos');
yield new Transformation(new Pattern('/ão$/i'), 'ões');
yield new Transformation(new Pattern('/^(|g)ás$/i'), '\1ases');
yield new Transformation(new Pattern('/^(japon|escoc|ingl|dinamarqu|fregu|portugu)ês$/i'), '\1eses');
yield new Transformation(new Pattern('/m$/i'), 'ns');
yield new Transformation(new Pattern('/([^aeou])il$/i'), '\1is');
yield new Transformation(new Pattern('/ul$/i'), 'uis');
yield new Transformation(new Pattern('/ol$/i'), 'ois');
yield new Transformation(new Pattern('/el$/i'), 'eis');
yield new Transformation(new Pattern('/al$/i'), 'ais');
yield new Transformation(new Pattern('/(z|r)$/i'), '\1es');
yield new Transformation(new Pattern('/(s)$/i'), '\1');
yield new Transformation(new Pattern('/$/'), 's');
}
/** @return Substitution[] */
public static function getIrregular(): iterable
{
yield new Substitution(new Word('abdomen'), new Word('abdomens'));
yield new Substitution(new Word('alemão'), new Word('alemães'));
yield new Substitution(new Word('artesã'), new Word('artesãos'));
yield new Substitution(new Word('álcool'), new Word('álcoois'));
yield new Substitution(new Word('árvore'), new Word('árvores'));
yield new Substitution(new Word('bencão'), new Word('bencãos'));
yield new Substitution(new Word('cão'), new Word('cães'));
yield new Substitution(new Word('campus'), new Word('campi'));
yield new Substitution(new Word('cadáver'), new Word('cadáveres'));
yield new Substitution(new Word('capelão'), new Word('capelães'));
yield new Substitution(new Word('capitão'), new Word('capitães'));
yield new Substitution(new Word('chão'), new Word('chãos'));
yield new Substitution(new Word('charlatão'), new Word('charlatães'));
yield new Substitution(new Word('cidadão'), new Word('cidadãos'));
yield new Substitution(new Word('consul'), new Word('consules'));
yield new Substitution(new Word('cristão'), new Word('cristãos'));
yield new Substitution(new Word('difícil'), new Word('difíceis'));
yield new Substitution(new Word('email'), new Word('emails'));
yield new Substitution(new Word('escrivão'), new Word('escrivães'));
yield new Substitution(new Word('fóssil'), new Word('fósseis'));
yield new Substitution(new Word('gás'), new Word('gases'));
yield new Substitution(new Word('germens'), new Word('germen'));
yield new Substitution(new Word('grão'), new Word('grãos'));
yield new Substitution(new Word('hífen'), new Word('hífens'));
yield new Substitution(new Word('irmão'), new Word('irmãos'));
yield new Substitution(new Word('liquens'), new Word('liquen'));
yield new Substitution(new Word('mal'), new Word('males'));
yield new Substitution(new Word('mão'), new Word('mãos'));
yield new Substitution(new Word('orfão'), new Word('orfãos'));
yield new Substitution(new Word('país'), new Word('países'));
yield new Substitution(new Word('pai'), new Word('pais'));
yield new Substitution(new Word('pão'), new Word('pães'));
yield new Substitution(new Word('projétil'), new Word('projéteis'));
yield new Substitution(new Word('réptil'), new Word('répteis'));
yield new Substitution(new Word('sacristão'), new Word('sacristães'));
yield new Substitution(new Word('sotão'), new Word('sotãos'));
yield new Substitution(new Word('tabelião'), new Word('tabeliães'));
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Portuguese;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/** @return Pattern[] */
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/** @return Pattern[] */
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/** @return Pattern[] */
private static function getDefault(): iterable
{
yield new Pattern('tórax');
yield new Pattern('tênis');
yield new Pattern('ônibus');
yield new Pattern('lápis');
yield new Pattern('fênix');
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules;
class Ruleset
{
/** @var Transformations */
private $regular;
/** @var Patterns */
private $uninflected;
/** @var Substitutions */
private $irregular;
public function __construct(Transformations $regular, Patterns $uninflected, Substitutions $irregular)
{
$this->regular = $regular;
$this->uninflected = $uninflected;
$this->irregular = $irregular;
}
public function getRegular(): Transformations
{
return $this->regular;
}
public function getUninflected(): Patterns
{
return $this->uninflected;
}
public function getIrregular(): Substitutions
{
return $this->irregular;
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Pattern;
use Doctrine\Inflector\Rules\Substitution;
use Doctrine\Inflector\Rules\Transformation;
use Doctrine\Inflector\Rules\Word;
class Inflectible
{
/** @return Transformation[] */
public static function getSingular(): iterable
{
yield new Transformation(new Pattern('/ereses$/'), 'erés');
yield new Transformation(new Pattern('/iones$/'), 'ión');
yield new Transformation(new Pattern('/ces$/'), 'z');
yield new Transformation(new Pattern('/es$/'), '');
yield new Transformation(new Pattern('/s$/'), '');
}
/** @return Transformation[] */
public static function getPlural(): iterable
{
yield new Transformation(new Pattern('/ú([sn])$/i'), 'u\1es');
yield new Transformation(new Pattern('/ó([sn])$/i'), 'o\1es');
yield new Transformation(new Pattern('/í([sn])$/i'), 'i\1es');
yield new Transformation(new Pattern('/é([sn])$/i'), 'e\1es');
yield new Transformation(new Pattern('/á([sn])$/i'), 'a\1es');
yield new Transformation(new Pattern('/z$/i'), 'ces');
yield new Transformation(new Pattern('/([aeiou]s)$/i'), '\1');
yield new Transformation(new Pattern('/([^aeéiou])$/i'), '\1es');
yield new Transformation(new Pattern('/$/'), 's');
}
/** @return Substitution[] */
public static function getIrregular(): iterable
{
yield new Substitution(new Word('el'), new Word('los'));
yield new Substitution(new Word('papá'), new Word('papás'));
yield new Substitution(new Word('mamá'), new Word('mamás'));
yield new Substitution(new Word('sofá'), new Word('sofás'));
yield new Substitution(new Word('mes'), new Word('meses'));
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\GenericLanguageInflectorFactory;
use Doctrine\Inflector\Rules\Ruleset;
final class InflectorFactory extends GenericLanguageInflectorFactory
{
protected function getSingularRuleset(): Ruleset
{
return Rules::getSingularRuleset();
}
protected function getPluralRuleset(): Ruleset
{
return Rules::getPluralRuleset();
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Patterns;
use Doctrine\Inflector\Rules\Ruleset;
use Doctrine\Inflector\Rules\Substitutions;
use Doctrine\Inflector\Rules\Transformations;
final class Rules
{
public static function getSingularRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getSingular()),
new Patterns(...Uninflected::getSingular()),
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
);
}
public static function getPluralRuleset(): Ruleset
{
return new Ruleset(
new Transformations(...Inflectible::getPlural()),
new Patterns(...Uninflected::getPlural()),
new Substitutions(...Inflectible::getIrregular())
);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Doctrine\Inflector\Rules\Spanish;
use Doctrine\Inflector\Rules\Pattern;
final class Uninflected
{
/** @return Pattern[] */
public static function getSingular(): iterable
{
yield from self::getDefault();
}
/** @return Pattern[] */
public static function getPlural(): iterable
{
yield from self::getDefault();
}
/** @return Pattern[] */
private static function getDefault(): iterable
{
yield new Pattern('lunes');
yield new Pattern('rompecabezas');
yield new Pattern('crisis');
}
}

Some files were not shown because too many files have changed in this diff Show More